This commit is contained in:
maojindao55
2025-03-25 19:58:39 +08:00
parent 487621a645
commit 051a7da4c6
12 changed files with 482 additions and 66 deletions

View File

@@ -47,6 +47,14 @@ async function verifyToken(token, env) {
// 中间件函数
export async function onRequest(context) {
try {
//获取环境变量中的AUTH_ACCESS
const authAccess = context.env.AUTH_ACCESS;
console.log('authAccess', authAccess);
//如果AUTH_ACCESS为0则跳过权限校验
if (!authAccess || authAccess === '0') {
console.log('跳过权限校验');
return await context.next();
}
const request = context.request;
const env = context.env;
//跳过登录页面
@@ -66,6 +74,7 @@ export async function onRequest(context) {
return await context.next();
} catch (error) {
console.error(error.message, context.request.url);
return new Response(JSON.stringify({ error: error.message }), {
status: 401,
headers: {

View File

@@ -1,6 +1,7 @@
interface Env {
bgkv: KVNamespace;
JWT_SECRET: string;
DB: D1Database;
}
export const onRequestPost: PagesFunction<Env> = async (context) => {
@@ -61,19 +62,56 @@ export const onRequestPost: PagesFunction<Env> = async (context) => {
);
}
// 验证成功,生成 JWT token传入 env
// 验证成功后,处理用户数据
const db = env.DB; // 假设你的 D1 数据库实例名为 DB
// 查询用户是否存在
const existingUser = await db.prepare(
"SELECT id, phone, nickname FROM users WHERE phone = ?"
).bind(phone).first();
let userId;
if (!existingUser) {
// 用户不存在,创建新用户
const result = await db.prepare(`
INSERT INTO users (phone, nickname, status, created_at, updated_at, last_login_at)
VALUES (?, ?, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`).bind(phone, `用户${phone.substring(7)}`).run();
userId = result.lastRowId;
} else {
// 用户存在,更新登录时间
await db.prepare(`
UPDATE users
SET last_login_at = CURRENT_TIMESTAMP,
updated_at = CURRENT_TIMESTAMP
WHERE phone = ?
`).bind(phone).run();
userId = existingUser.id;
}
// 获取完整的用户信息
const userInfo = await db.prepare(`
SELECT id, phone, nickname, avatar_url, status
FROM users
WHERE phone = ?
`).bind(phone).first();
// 生成 token
const token = await generateToken(phone, env);
// 删除验证码
await env.bgkv.delete(`sms:${phone}`);
// 返回用户信息和token
return new Response(
JSON.stringify({
success: true,
message: '登录成功',
data: {
token,
phone
user: userInfo
}
}),
{

View File

@@ -1,71 +1,85 @@
import { sendSMS } from '../utils/sms';
interface Env {
bgkv: KVNamespace;
ALIYUN_ACCESS_KEY_ID: string;
ALIYUN_ACCESS_KEY_SECRET: string;
ALIYUN_SMS_SIGN_NAME: string;
ALIYUN_SMS_TEMPLATE_CODE: string;
bgkv: KVNamespace;
}
export const onRequestPost: PagesFunction<Env> = async (context) => {
const { request, env } = context;
try {
const { request, env } = context;
const { phone } = await request.json();
// 获取请求体
const body = await request.json();
const { phone } = body;
// 验证手机号格式
if (!phone || !/^1[3-9]\d{9}$/.test(phone)) {
return new Response(
JSON.stringify({
success: false,
message: '无效的手机号'
}),
success: false,
message: '请输入正确的手机号'
}),
{
status: 400,
headers: {
'Content-Type': 'application/json',
},
headers: { 'Content-Type': 'application/json' },
}
);
}
// 生成6位随机验证码
let verificationCode = Math.floor(100000 + Math.random() * 900000).toString();
// 使用 CF_PAGES_ENVIRONMENT 判断环境
// 值为 'production' 或 'preview'
if (env.CF_PAGES_ENVIRONMENT !== 'production') {
verificationCode = '123456';
// 开发环境使用固定验证码
const verificationCode = env.CF_PAGES_ENVIRONMENT === 'preview'
? '123456'
: Math.random().toString().slice(-6);
if (env.CF_PAGES_ENVIRONMENT !== 'preview') {
try {
await sendSMS(phone, verificationCode, {
accessKeyId: env.ALIYUN_ACCESS_KEY_ID,
accessKeySecret: env.ALIYUN_ACCESS_KEY_SECRET,
signName: env.ALIYUN_SMS_SIGN_NAME,
templateCode: env.ALIYUN_SMS_TEMPLATE_CODE
});
} catch (error) {
console.error('SMS Error:', error);
return new Response(
JSON.stringify({
success: false,
message: error instanceof Error ? error.message : '发送验证码失败,请重试'
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
);
}
}
// 将验证码存储到 KV 中设置5分钟过期
// 存储验证码到 KV设置 5 分钟过期
await env.bgkv.put(`sms:${phone}`, verificationCode, {
expirationTtl: 300 // 5分钟过期
expirationTtl: 5 * 60 // 5分钟
});
console.log(env.CF_PAGES_ENVIRONMENT, await env.bgkv.get(`sms:${phone}`));
return new Response(
JSON.stringify({
success: true,
message: '验证码发送成功',
// 注意:实际生产环境不应该返回验证码
code: verificationCode // 仅用于测试
}),
success: true,
message: '验证码发送成功'
}),
{
status: 200,
headers: {
'Content-Type': 'application/json',
},
headers: { 'Content-Type': 'application/json' },
}
);
} catch (error) {
console.error(error);
console.error('Request Error:', error);
return new Response(
JSON.stringify({
success: false,
message: '服务器错误'
}),
success: false,
message: '请求格式错误'
}),
{
status: 500,
headers: {
'Content-Type': 'application/json',
},
status: 400,
headers: { 'Content-Type': 'application/json' },
}
);
}

125
functions/utils/sms.ts Normal file
View File

@@ -0,0 +1,125 @@
interface AliyunSMSConfig {
accessKeyId: string;
accessKeySecret: string;
signName: string;
templateCode: string;
}
// 辅助函数:生成随机字符串
function generateNonce(length: number): string {
const array = new Uint8Array(length);
crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
// 辅助函数:计算 HMAC-SHA256V3 使用 SHA256
async function calculateHmacSha256(message: string, secret: string): Promise<string> {
const encoder = new TextEncoder();
const keyData = encoder.encode(secret);
const messageData = encoder.encode(message);
const key = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign(
'HMAC',
key,
messageData
);
return btoa(String.fromCharCode(...new Uint8Array(signature)));
}
export async function sendSMS(phone: string, code: string, config: AliyunSMSConfig) {
const API_URL = 'https://dysmsapi.aliyuncs.com/';
const API_VERSION = '2017-05-25';
// 准备请求参数
const params = {
AccessKeyId: config.accessKeyId,
Action: 'SendSms',
Format: 'JSON', // 明确指定返回 JSON 格式
PhoneNumbers: phone,
SignName: config.signName,
TemplateCode: config.templateCode,
TemplateParam: JSON.stringify({ code }),
Version: API_VERSION,
SignatureMethod: 'HMAC-SHA1',
SignatureVersion: '1.0',
SignatureNonce: generateNonce(16),
Timestamp: new Date().toISOString()
};
// 参数排序
const sortedParams = Object.keys(params)
.sort()
.reduce((acc, key) => ({
...acc,
[key]: params[key]
}), {});
// 构建签名字符串
const canonicalizedQueryString = Object.entries(sortedParams)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value as string)}`)
.join('&');
const stringToSign = `GET&${encodeURIComponent('/')}&${encodeURIComponent(canonicalizedQueryString)}`;
// 计算签名
const signature = await calculateHmacSha1(stringToSign, `${config.accessKeySecret}&`);
// 构建最终的 URL
const finalUrl = `${API_URL}?${canonicalizedQueryString}&Signature=${encodeURIComponent(signature)}`;
// 发送请求
const response = await fetch(finalUrl, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
const responseText = await response.text();
let responseData;
try {
responseData = JSON.parse(responseText);
} catch (e) {
console.error('Response:', responseText);
throw new Error('Invalid response format from SMS service');
}
//console.log(responseData, finalUrl)
if (!response.ok || responseData.Code !== 'OK') {
throw new Error(responseData.Message || 'SMS send failed');
}
return responseData;
}
// 辅助函数:计算 HMAC-SHA1
async function calculateHmacSha1(message: string, secret: string): Promise<string> {
const encoder = new TextEncoder();
const keyData = encoder.encode(secret);
const messageData = encoder.encode(message);
const key = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: 'SHA-1' },
false,
['sign']
);
const signature = await crypto.subtle.sign(
'HMAC',
key,
messageData
);
return btoa(String.fromCharCode(...new Uint8Array(signature)));
}