Использование сценариев приложений Google для обработки онлайн -формыJavascript

Форум по Javascript
Ответить
Anonymous
 Использование сценариев приложений Google для обработки онлайн -формы

Сообщение Anonymous »

Я не мог понять, почему эти сценарии не работают. По сути, фронтальная форма выглядит хорошо, но параметры от script.google.com 405 Статус: Отсутствует CRO.Response Headers:
HTTP/2 405
content-type: text/html; charset=UTF-8
content-encoding: gzip
date: Sat, 09 Aug 2025 14:00:56 GMT
expires: Sat, 09 Aug 2025 14:00:56 GMT
cache-control: private, max-age=0
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
content-security-policy: frame-ancestors 'self'
x-xss-protection: 1; mode=block
content-length: 149
server: GSE
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
X-Firefox-Spdy: h2
< /code>
Передняя часть моей веб -страницы Github, которая находится < /p>


Contact Me
Please fill in the below form, and I will get back to you.



Name


Email Address


Messages


data-theme="light" data-size="normal">


Submission







const form = document.getElementById('contactForm');
const submitBtn = document.getElementById('submitBtn');
const statusMessage = document.getElementById('statusMessage');

form.addEventListener('submit', async function(e) {
e.preventDefault();
submitBtn.classList.add('loading');
statusMessage.textContent = ''; // 清空消息

try {
const cfToken = document.querySelector('[name="cf-turnstile-response"]')?.value;
if (!cfToken) throw new Error("请完成验证后再提交表单");

const data = {
name: document.getElementById('name').value,
email: document.getElementById('email').value,
message: document.getElementById('message').value,
'cf-turnstile-response': cfToken
};

const res = await fetch('https://script.google.com/macros/s/.../exec', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});

const result = await res.json();

if (result.success) {
showStatusMessage("表单提交成功!感谢您的联系,我们会尽快回复您。", "success");
form.reset();
if (window.turnstile) turnstile.reset();
} else {
showStatusMessage(result.message || "Turnstile验证失败,请重新提交。", "error");
}
} catch (err) {
showStatusMessage(err.message || "提交过程中出现错误,请稍后再试。", "error");
} finally {
submitBtn.classList.remove('loading');
}
});


< /code>
Скрипты приложений Google: < /p>
// --- CONFIGURATION (Use script properties) ---
const PROPERTIES = PropertiesService.getScriptProperties();
const TURNSTILE_SECRET = PROPERTIES.getProperty('TURNSTILE_SECRET') || '';
const RECIPIENT_EMAIL = PROPERTIES.getProperty('RECIPIENT_EMAIL') || '';
const SPREADSHEET_ID = PROPERTIES.getProperty('SPREADSHEET_ID') || SpreadsheetApp.getActiveSpreadsheet().getId();

// 安全设置 - 允许的源域名 (根据实际需要修改)
const ALLOWED_ORIGINS = [
'https://xxx.github.io'
// 添加更多源域名
];

const ALLOWED_METHODS = 'POST, OPTIONS';
const ALLOWED_HEADERS = 'Content-Type, Authorization';
const MAX_AGE = 86400; // 24小时

// --- 初始化逻辑 ---
initializeSetup();

/**
* 初始化必要的环境设置
*/
function initializeSetup() {
// 确保表头存在
try {
const sheet = getSheet('表单提交');
if (sheet.getLastRow() === 0) {
const headers = ['时间戳', 'ISO日期', '姓名', '邮箱', '消息内容'];
sheet.appendRow(headers);
// 加粗表头
const headerRange = sheet.getRange(1, 1, 1, headers.length);
headerRange.setFontWeight('bold');
Logger.log('已创建表头');
}
} catch (e) {
Logger.log('初始化错误: ' + e.message);
}
}

// --- CORS 响应处理 ---
function doOptions(e) {
const origin = getRequestOrigin(e);

return createCorsResponse('', ContentService.MimeType.TEXT, {
statusCode: 204,
headers: getCorsHeaders(origin)
});
}

function doPost(e) {
const origin = getRequestOrigin(e);
const corsHeaders = getCorsHeaders(origin);

try {
// 1. 请求验证
const requestData = validateRequest(e);
const data = JSON.parse(requestData);

// 2. 输入验证
validateInput(data);

// 3. Turnstile 验证
verifyTurnstile(data['cf-turnstile-response']);

// 4. 写入数据
writeToSheet(data);

// 5. 发送邮件
if (RECIPIENT_EMAIL) {
sendEmailNotification(data.name, data.email, data.message);
}

return createCorsResponse({
success: true,
message: "表单提交成功!"
}, ContentService.MimeType.JSON, {
headers: corsHeaders
});

} catch (error) {
Logger.log('错误: ' + error.stack);
return createCorsResponse({
success: false,
message: error.message || "服务器错误"
}, ContentService.MimeType.JSON, {
statusCode: 400,
headers: corsHeaders
});
}
}

// --- 辅助函数 ---

/**
* 安全创建 CORS 兼容的响应
*/
function createCorsResponse(content, contentType, options = {}) {
const output = ContentService.createTextOutput(
typeof content === 'string' ? content : JSON.stringify(content)
);

output.setMimeType(contentType);

// 设置响应头
const headers = Object.assign({}, options.headers || {}, {
'Content-Type': contentType === ContentService.MimeType.JSON
? 'application/json'
: contentType
});

output.setHeaders(headers);

// 设置状态码(仅支持有限的状态码)
if (options.statusCode) {
if ([200, 201, 204, 400, 401, 403, 404, 500].includes(options.statusCode)) {
output.setStatusCode(options.statusCode);
}
}

return output;
}

/**
* 获取 CORS 头部
*/
function getCorsHeaders(origin) {
return {
'Access-Control-Allow-Origin': ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0],
'Access-Control-Allow-Methods': ALLOWED_METHODS,
'Access-Control-Allow-Headers': ALLOWED_HEADERS,
'Access-Control-Max-Age': MAX_AGE
};
}

/**
* 获取请求来源
*/
function getRequestOrigin(e) {
try {
return e && e.parameter && e.parameter.__origin ? e.parameter.__origin :
(typeof e === 'object' && e['__origin']) ||
(e && e.headers && e.headers.Origin) ||
'';
} catch (err) {
return '';
}
}

/**
* 验证请求有效性
*/
function validateRequest(e) {
if (!e || !e.postData || !e.postData.contents) {
throw new Error("无效请求: 无法获取请求内容");
}

// 防止超大型请求(限制1MB)
if (e.postData.contents.length > 1024 * 1024) {
throw new Error("请求过大");
}

return e.postData.contents;
}

/**
* 验证输入字段
*/
function validateInput(data) {
const { name = '', email = '', message = '' } = data;

const isNameValid = name.trim().length >= 2;
const isEmailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
const isMessageValid = message.trim().length > 5;

if (!isNameValid) throw new Error("姓名至少需要2个字符");
if (!isEmailValid) throw new Error("邮箱格式无效");
if (!isMessageValid) throw new Error("留言至少需要6个字符");
}

/**
* 验证 Cloudflare Turnstile token
*/
function verifyTurnstile(token) {
if (!token) throw new Error("缺少安全令牌");

const url = 'https://challenges.cloudflare.com/turns ... siteverify';
const options = {
method: 'post',
contentType: 'application/x-www-form-urlencoded',
payload: {
secret: TURNSTILE_SECRET,
response: token,
remoteip: Session.getActiveUser().getClientIp()
},
muteHttpExceptions: true
};

const response = UrlFetchApp.fetch(url, options);

if (response.getResponseCode() !== 200) {
throw new Error(`Turnstile API错误: HTTP ${response.getResponseCode()}`);
}

const result = JSON.parse(response.getContentText());

if (!result.success) {
throw new Error(`安全验证失败: ${result['error-codes']?.join(', ') || '未知错误'}`);
}
}

/**
* 写入 Google Sheet
*/
function writeToSheet(data) {
try {
const sheet = getSheet('表单提交');
const timestamp = new Date();

sheet.appendRow([
Utilities.formatDate(timestamp, Session.getScriptTimeZone(), "yyyy-MM-dd HH:mm:ss"),
timestamp.toISOString(),
data.name,
data.email,
data.message
]);

// 自动调整列宽
sheet.autoResizeColumns(1, 5);

} catch (e) {
Logger.log(`表格写入错误: ${e.message}`);
throw new Error("无法保存数据到表格");
}
}

/**
* 获取工作表(确保存在)
*/
function getSheet(sheetName) {
try {
const spreadsheet = SpreadsheetApp.openById(SPREADSHEET_ID);
let sheet = spreadsheet.getSheetByName(sheetName);

if (!sheet) {
// 动态创建工作表(如果不存在)
sheet = spreadsheet.insertSheet(sheetName);
initializeSetup(); // 重新初始化表头
Logger.log(`已创建新工作表: ${sheetName}`);
}

return sheet;
} catch (e) {
throw new Error(`无法访问表格: ${e.message}`);
}
}

/**
* 发送邮件通知
*/
function sendEmailNotification(name, email, message) {
try {
const subject = `新表单提交 - ${name}`;
const htmlBody = `

您有新表单提交



姓名
${escapeHtml(name)}


邮箱
${escapeHtml(email)}


时间

${Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy-MM-dd HH:mm")}



留言内容:




${escapeHtml(message)}





这封邮件由自动系统发送,请勿回复。


`;

GmailApp.sendEmail(RECIPIENT_EMAIL, subject, htmlBody.replace(/]+>/g, ' '), {
htmlBody: htmlBody
});

} catch (e) {
Logger.log('邮件发送错误: ' + e.message);
// 邮箱失败不应中断主要流程
}
}

/**
* HTML 内容安全转义
*/
function escapeHtml(unsafe) {
if (typeof unsafe !== 'string') return unsafe;
return unsafe
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}


Подробнее здесь: https://stackoverflow.com/questions/797 ... nline-form
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Javascript»