明廷盛 嘻嘻😁

STEP1: cloudflare部署域名

STEP2 cloudeflare部署Worker

image.png|300 image.png|400 you’sha
export default { // 导出 Cloudflare Worker 入口对象
/**
* 处理所有进入 Worker 的 HTTP 请求
* @param {Request} request 当前 HTTP 请求对象
* @param {{ DB: D1Database }} env Cloudflare 绑定环境变量,要求绑定 D1 为 DB
* @returns {Promise<Response>} 标准 HTTP 响应
* @description 按路径分发健康检查、初始化表、设备注册、批量上传、导出查询接口
*/
async fetch(request, env) { // 定义 Worker 请求入口
const url = new URL(request.url) // 解析当前请求 URL
const pathname = url.pathname // 读取请求路径
const method = request.method.toUpperCase() // 标准化请求方法

if (method === 'OPTIONS') { // 处理浏览器预检请求
return createJsonResponse({ success: true }, 200) // 返回带 CORS 头的空成功响应
} // 结束预检处理

if (method === 'GET' && pathname === '/health') { // 匹配健康检查接口
return createJsonResponse({ success: true, message: 'usage api ok' }, 200) // 返回健康检查结果
} // 结束健康检查处理

if (method === 'POST' && pathname === '/v1/admin/init') { // 匹配初始化数据库接口
return await handleInitDatabase(env) // 执行建表逻辑
} // 结束数据库初始化处理

if (method === 'POST' && pathname === '/v1/device/register') { // 匹配设备注册接口
return await handleRegisterDevice(request, env) // 执行设备注册逻辑
} // 结束设备注册处理

if (method === 'POST' && pathname === '/v1/usage/batch') { // 匹配批量上传接口
return await handleUsageBatch(request, env) // 执行批量上传逻辑
} // 结束批量上传处理

if (method === 'GET' && pathname === '/v1/usage/export') { // 匹配数据导出接口
return await handleExportUsage(url, env) // 执行导出查询逻辑
} // 结束导出查询处理

return createJsonResponse({ success: false, message: 'Not Found' }, 404) // 未匹配接口时返回 404
} // 结束 Worker 请求入口
} // 结束默认导出对象

/**
* 创建统一的 JSON 响应
* @param {unknown} data 要返回的 JSON 数据
* @param {number} status HTTP 状态码
* @returns {Response} 带 CORS 头和 JSON 头的响应对象
* @description 统一封装所有接口返回格式,便于前后端调用
*/
function createJsonResponse(data, status = 200) { // 定义统一 JSON 响应函数
return new Response(JSON.stringify(data, null, 2), { // 返回标准 JSON 响应
status, // 写入响应状态码
headers: { // 定义统一响应头
'content-type': 'application/json; charset=utf-8', // 声明 JSON 编码格式
'access-control-allow-origin': '*', // 允许跨域访问
'access-control-allow-methods': 'GET,POST,OPTIONS', // 声明允许的请求方法
'access-control-allow-headers': 'content-type,authorization' // 声明允许的请求头
} // 结束响应头定义
}) // 结束 Response 创建
} // 结束统一 JSON 响应函数

/**
* 安全解析请求体 JSON
* @param {Request} request 当前 HTTP 请求对象
* @returns {Promise<any>} 解析后的 JSON 对象
* @description 当请求体不是合法 JSON 时抛出异常,交给上层统一处理
*/
async function readJsonBody(request) { // 定义请求体 JSON 解析函数
return await request.json() // 直接解析请求体为 JSON
} // 结束请求体 JSON 解析函数

/**
* 初始化 D1 数据表
* @param {{ DB: D1Database }} env Cloudflare 绑定环境变量
* @returns {Promise<Response>} 初始化执行结果
* @description 创建设备表和使用会话表,首次部署后手动调用一次即可
*/
async function handleInitDatabase(env) { // 定义数据库初始化处理函数
const sql = ` // 定义建表 SQL
CREATE TABLE IF NOT EXISTS device_info (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT NOT NULL UNIQUE,
brand TEXT,
manufacturer TEXT,
model TEXT,
token TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS usage_session (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_id TEXT NOT NULL UNIQUE,
device_id TEXT NOT NULL,
package_name TEXT NOT NULL,
app_name TEXT,
start_time TEXT NOT NULL,
end_time TEXT NOT NULL,
duration_sec INTEGER NOT NULL,
source TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_usage_device_id ON usage_session(device_id);
CREATE INDEX IF NOT EXISTS idx_usage_start_time ON usage_session(start_time);
` // 结束建表 SQL 定义

await env.DB.exec(sql) // 在 D1 中执行建表 SQL
return createJsonResponse({ success: true, message: 'database initialized' }, 200) // 返回初始化成功结果
} // 结束数据库初始化处理函数

/**
* 注册设备并生成最小鉴权记录
* @param {Request} request 当前 HTTP 请求对象
* @param {{ DB: D1Database }} env Cloudflare 绑定环境变量
* @returns {Promise<Response>} 设备注册结果
* @description 用设备 ID 作为唯一键写入 device_info,重复注册时更新基础信息
*/
async function handleRegisterDevice(request, env) { // 定义设备注册处理函数
try { // 开始执行设备注册主流程
const body = await readJsonBody(request) // 解析请求体
const deviceId = String(body.deviceId || '').trim() // 读取并标准化设备 ID
const brand = String(body.brand || '').trim() // 读取品牌字段
const manufacturer = String(body.manufacturer || '').trim() // 读取厂商字段
const model = String(body.model || '').trim() // 读取机型字段
const token = String(body.token || '').trim() // 读取设备 token

if (!deviceId || !token) { // 检查必要字段是否齐全
return createJsonResponse({ success: false, message: 'deviceId and token are required' }, 400) // 缺少必要字段时返回错误
} // 结束必要字段校验

await env.DB.prepare( // 预编译设备注册 SQL
`INSERT INTO device_info (device_id, brand, manufacturer, model, token)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(device_id) DO UPDATE SET
brand = excluded.brand,
manufacturer = excluded.manufacturer,
model = excluded.model,
token = excluded.token` // 定义 upsert SQL
).bind(deviceId, brand, manufacturer, model, token).run() // 绑定参数并执行写入

return createJsonResponse({ success: true, deviceId, message: 'device registered' }, 200) // 返回注册成功结果
} catch (error) { // 捕获设备注册异常
return createJsonResponse({ success: false, message: String(error) }, 500) // 返回服务端异常信息
} // 结束异常捕获
} // 结束设备注册处理函数

/**
* 批量写入使用会话数据
* @param {Request} request 当前 HTTP 请求对象
* @param {{ DB: D1Database }} env Cloudflare 绑定环境变量
* @returns {Promise<Response>} 批量上传结果
* @description 校验 deviceId 和 token 后,将会话数据逐条去重写入 usage_session
*/
async function handleUsageBatch(request, env) { // 定义批量上传处理函数
try { // 开始执行批量上传主流程
const body = await readJsonBody(request) // 解析请求体
const deviceId = String(body.deviceId || '').trim() // 读取设备 ID
const token = String(body.token || '').trim() // 读取设备 token
const items = Array.isArray(body.items) ? body.items : [] // 读取会话数组

if (!deviceId || !token) { // 校验鉴权必要字段
return createJsonResponse({ success: false, message: 'deviceId and token are required' }, 400) // 缺失鉴权字段时返回错误
} // 结束鉴权字段校验

const deviceRow = await env.DB.prepare( // 查询设备注册信息
`SELECT device_id, token FROM device_info WHERE device_id = ?` // 定义设备查询 SQL
).bind(deviceId).first() // 绑定设备 ID 并读取单条结果

if (!deviceRow) { // 判断设备是否已注册
return createJsonResponse({ success: false, message: 'device not registered' }, 403) // 未注册设备直接拒绝上传
} // 结束设备存在性校验

if (deviceRow.token !== token) { // 校验上传 token 是否匹配
return createJsonResponse({ success: false, message: 'invalid token' }, 403) // token 不匹配时拒绝上传
} // 结束 token 校验

let accepted = 0 // 记录成功写入数量
let duplicated = 0 // 记录重复数据数量

for (const item of items) { // 逐条处理上传会话数据
const eventId = String(item.eventId || '').trim() // 读取会话唯一 ID
const packageName = String(item.packageName || '').trim() // 读取应用包名
const appName = String(item.appName || '').trim() // 读取应用名称
const startTime = String(item.startTime || '').trim() // 读取开始时间
const endTime = String(item.endTime || '').trim() // 读取结束时间
const durationSec = Number(item.durationSec || 0) // 读取使用秒数
const source = String(item.source || 'UsageEvents').trim() // 读取数据来源

if (!eventId || !packageName || !startTime || !endTime || durationSec <= 0) { // 跳过不完整或无效数据
continue // 忽略当前坏数据项
} // 结束单项数据校验

const result = await env.DB.prepare( // 准备插入使用会话 SQL
`INSERT OR IGNORE INTO usage_session
(event_id, device_id, package_name, app_name, start_time, end_time, duration_sec, source)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)` // 定义去重插入 SQL
).bind(eventId, deviceId, packageName, appName, startTime, endTime, durationSec, source).run() // 绑定参数并执行写入

if ((result.meta?.changes || 0) > 0) { // 判断当前记录是否实际插入成功
accepted += 1 // 成功插入时累加 accepted
} else { // 未插入通常表示 event_id 已存在
duplicated += 1 // 将当前记录计为重复数据
} // 结束写入结果判断
} // 结束会话遍历处理

return createJsonResponse({ // 返回批量上传结果
success: true, // 标记请求成功
accepted, // 返回新写入数量
duplicated, // 返回重复数量
serverTime: new Date().toISOString() // 返回服务器时间
}, 200) // 结束成功响应构造
} catch (error) { // 捕获批量上传异常
return createJsonResponse({ success: false, message: String(error) }, 500) // 返回服务端异常信息
} // 结束异常捕获
} // 结束批量上传处理函数

/**
* 查询某设备某日或某时间范围内的使用会话
* @param {URL} url 当前请求 URL 对象
* @param {{ DB: D1Database }} env Cloudflare 绑定环境变量
* @returns {Promise<Response>} 导出查询结果
* @description 支持按 date 或 from/to 两种方式导出使用会话数据
*/
async function handleExportUsage(url, env) { // 定义导出查询处理函数
try { // 开始执行导出查询主流程
const deviceId = String(url.searchParams.get('deviceId') || '').trim() // 读取设备 ID 参数
const date = String(url.searchParams.get('date') || '').trim() // 读取按单日查询参数
const from = String(url.searchParams.get('from') || '').trim() // 读取起始日期参数
const to = String(url.searchParams.get('to') || '').trim() // 读取结束日期参数

if (!deviceId) { // 校验设备 ID 参数
return createJsonResponse({ success: false, message: 'deviceId is required' }, 400) // 缺失设备 ID 时返回错误
} // 结束设备 ID 校验

let sql = '' // 初始化查询 SQL 字符串
let params = [] // 初始化 SQL 参数数组

if (date) { // 优先处理按某一天查询
sql = `SELECT event_id, package_name, app_name, start_time, end_time, duration_sec, source
FROM usage_session
WHERE device_id = ?
AND substr(start_time, 1, 10) = ?
ORDER BY start_time ASC` // 定义按日期导出 SQL
params = [deviceId, date] // 绑定按日期查询参数
} else if (from && to) { // 处理按日期范围查询
sql = `SELECT event_id, package_name, app_name, start_time, end_time, duration_sec, source
FROM usage_session
WHERE device_id = ?
AND substr(start_time, 1, 10) >= ?
AND substr(start_time, 1, 10) <= ?
ORDER BY start_time ASC` // 定义按范围导出 SQL
params = [deviceId, from, to] // 绑定范围查询参数
} else { // 缺少合法查询条件时返回错误
return createJsonResponse({ success: false, message: 'date or from/to is required' }, 400) // 提示必须提供查询条件
} // 结束查询条件判断

const result = await env.DB.prepare(sql).bind(...params).all() // 执行导出查询并获取全部结果
const items = Array.isArray(result.results) ? result.results : [] // 兜底结果数组结构

return createJsonResponse({ // 返回导出接口成功结果
success: true, // 标记导出成功
deviceId, // 回传设备 ID
count: items.length, // 返回结果条数
items // 返回会话数据列表
}, 200) // 结束成功响应构造
} catch (error) { // 捕获导出查询异常
return createJsonResponse({ success: false, message: String(error) }, 500) // 返回服务端异常信息
} // 结束异常捕获
} // 结束导出查询处理函数
  • 测试: 访问: https://usage-api.1594365335.workers.dev/health 提示有{ "success": true, "message": "usage api ok"} 就对
  • Title:
  • Author: 明廷盛
  • Created at : 2026-06-12 12:28:25
  • Updated at : 2026-06-12 12:28:25
  • Link: https://blog.20040424.xyz/2026/06/12/⏸️VibeCoding/2. 部署api/
  • License: All Rights Reserved © 明廷盛