讲师中心 微信公众号
AI工具推荐 视频效率加速

防止重复数据写入Google Sheets:基于URL参数的Web应用优化

风婷同学_6884

风婷同学_6884

发布时间:2025-09-23 11:56:39

|

1044人浏览过

|

来源于php中文网

原创

防止重复数据写入Google Sheets:基于URL参数的Web应用优化

本文旨在解决通过URL参数向Google Sheets提交数据时,因用户重复访问或打开链接而导致数据重复录入的问题。我们将详细介绍如何通过修改Google Apps Script,在服务器端实现数据写入前的重复性校验,确保只有新数据才会被追加到表格中,从而提升数据管理的准确性和效率。

1. 问题背景与挑战

在构建基于google sheets作为数据存储后端的web应用时,常见的一种数据提交方式是通过url参数。例如,用户访问 https://example.com/page?name=john&email=john@gmail.com 时,页面加载后会将 name 和 email 等参数发送到google apps script(gas),进而写入google sheet。

然而,这种机制面临一个挑战:如果用户重复访问该URL(无论是刷新页面、在新标签页打开,还是在不同设备上访问),即使前端JavaScript可能通过 performance.navigation.type 等方式阻止了页面刷新时的重复提交,也无法阻止用户从新入口再次提交相同数据。这会导致Google Sheet中出现大量重复条目,影响数据质量和后续分析。因此,我们需要一种在服务器端(即Google Apps Script)进行重复数据校验的机制。

2. 现有数据提交机制概述

为了更好地理解解决方案,我们首先回顾一下现有前端JavaScript和后端Google Apps Script如何协同工作。

2.1 前端JavaScript(数据捕获与发送)

前端JavaScript负责从当前URL中提取查询参数,并将这些参数通过 fetch API发送到部署为Web应用的Google Apps Script。

if (performance.navigation.type != performance.navigation.TYPE_RELOAD) {
  // 从URL中获取查询参数
  const queryParameters = Object.fromEntries(new URLSearchParams(window.location.search));

  // Google Apps Script Web应用部署的URL
  const scriptURL = 'https://script.google.com/macros/s/script/exec';

  // 准备FormData对象以发送数据
  const data = new FormData();
  Object.entries(queryParameters).forEach(e => data.append(...e));

  // 发送POST请求到Google Apps Script
  fetch(scriptURL, { method: 'POST', body: data })
    .then(response => console.log('Success!', response))
    .catch(error => console.error('Error!', error.message))
}

这段代码确保只有在非页面刷新的情况下才执行数据发送,但如前所述,它无法阻止跨会话或跨设备的重复提交。

2.2 后端Google Apps Script(数据接收与写入)

Google Apps Script作为Web应用的 doPost 函数,接收前端发送的数据,并将其写入指定的Google Sheet。

var sheetName = 'Sheet1'
var scriptProp = PropertiesService.getScriptProperties()

function intialSetup () {
  var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet()
  scriptProp.setProperty('key', activeSpreadsheet.getId())
}

function doPost (e) {
  const { Name, Email, sample } = e.parameter; // 提取URL参数

  // 省略了处理"delete"逻辑的部分,此处主要关注数据添加
  if (sample == "delete") {
    // ... 删除逻辑 ...
  }

  var lock = LockService.getScriptLock()
  lock.tryLock(10000) // 获取脚本锁,防止并发写入冲突

  try {
    var doc = SpreadsheetApp.openById(scriptProp.getProperty('key'))
    var sheet = doc.getSheetByName(sheetName)

    var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]
    var nextRow = sheet.getLastRow() + 1

    var newRow = headers.map(function(header) {
      return header === 'Timestamp' ? new Date() : e.parameter[header]
    })

    sheet.getRange(nextRow, 1, 1, newRow.length).setValues([newRow]) // 将新行数据写入Sheet

    return ContentService
      .createTextOutput(JSON.stringify({ 'result': 'success', 'row': nextRow }))
      .setMimeType(ContentService.MimeType.JSON)
  }
  catch (e) {
    return ContentService
      .createTextOutput(JSON.stringify({ 'result': 'error', 'error': e }))
      .setMimeType(ContentService.MimeType.JSON)
  }
  finally {
    lock.releaseLock() // 释放脚本锁
  }
}

上述 doPost 函数在获取参数后,会直接构造新行并追加到Google Sheet中,并未进行任何重复性检查。

3. 核心解决方案:在Google Apps Script中实现重复数据校验

为了解决重复数据问题,我们需要在 doPost 函数的数据写入逻辑之前,引入一个校验步骤。这个步骤将检查即将写入的数据(例如 Name 和 Email)是否已存在于Google Sheet中。

3.1 修改后的Google Apps Script代码

我们将在 try 块内部,实际写入数据之前,添加重复性检查逻辑。

var sheetName = 'Sheet1'
var scriptProp = PropertiesService.getScriptProperties()

function intialSetup () {
  var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet()
  scriptProp.setProperty('key', activeSpreadheet.getId())
}

function doPost (e) {
  const { Name, Email, sample } = e.parameter; // 提取URL参数

  if (sample == "delete") {
    var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
    var ranges = sheet.getDataRange();
    var values = ranges.getValues().filter(r => ![Name, Email].every(e => r.includes(e)));
    if (ranges.length == 0) {
      return ContentService.createTextOutput(`"${email}" was not found.`);
    }
    ranges.clearContent().offset(0, 0, values.length, values[0].length).setValues(values);
    return ContentService.createTextOutput(`Rows including "${Email}" were deleted.`);
  }

  var lock = LockService.getScriptLock()
  lock.tryLock(10000)

  try {
    var doc = SpreadsheetApp.openById(scriptProp.getProperty('key'))
    var sheet = doc.getSheetByName(sheetName)

    // --- 新增的重复数据校验逻辑开始 ---
    var existingValues = sheet.getDataRange().getValues(); // 获取Sheet中所有数据
    // 过滤出与当前提交的Name和Email都匹配的行
    var matchedRows = existingValues.filter(row => [Name, Email].every(param => row.includes(param)));

    if (matchedRows.length > 0) {
      // 如果找到匹配的行,说明数据已存在,不进行追加
      return ContentService.createTextOutput("Values were not appended. Data already exists.");
    }
    // --- 新增的重复数据校验逻辑结束 ---

    var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]
    var nextRow = sheet.getLastRow() + 1

    var newRow = headers.map(function(header) {
      return header === 'Timestamp' ? new Date() : e.parameter[header]
    })

    sheet.getRange(nextRow, 1, 1, newRow.length).setValues([newRow])

    return ContentService
      .createTextOutput(JSON.stringify({ 'result': 'success', 'row': nextRow }))
      .setMimeType(ContentService.MimeType.JSON)
  }
  catch (e) {
    return ContentService
      .createTextOutput(JSON.stringify({ 'result': 'error', 'error': e }))
      .setMimeType(ContentService.MimeType.JSON)
  }
  finally {
    lock.releaseLock()
  }
}

3.2 代码详解

  1. var existingValues = sheet.getDataRange().getValues();:

    Google Alerts Monitor
    Google Alerts Monitor

    通过免费网页搜索监控关键词。适用场景:(1)追踪品牌或产品提及,(2)监控行业关键词,(3)竞争对手及营销研究。

    下载
    • sheet.getDataRange() 获取Sheet中包含数据的所有单元格范围。
    • .getValues() 将该范围内的数据作为一个二维数组返回。例如,如果Sheet有两列(Name, Email)和两行数据,它可能返回 [['Name', 'Email'], ['John', 'john@gmail.com']]。
  2. var matchedRows = existingValues.filter(row => [Name, Email].every(param => row.includes(param)));:

    • filter() 是JavaScript数组的一个方法,它会遍历 existingValues 数组中的每一行(row),并根据回调函数的返回值(true 或 false)来决定是否保留该行。
    • [Name, Email].every(param => row.includes(param)) 是过滤条件的核心:
      • [Name, Email] 是一个包含当前提交的 Name 和 Email 参数的数组。
      • every() 也是JavaScript数组的方法,它会检查数组中的所有元素是否都满足提供的测试函数。在这里,它检查 Name 和 Email 这两个参数是否都存在于当前遍历的 row(即Sheet中的某一行数据)中。
      • row.includes(param) 检查当前Sheet行(row)是否包含 param(即 Name 或 Email)。
    • 综合起来,matchedRows 将包含所有在 Name 和 Email 两列中都与当前提交值匹配的行。
  3. if (matchedRows.length > 0) { ... }:

    • 如果 matchedRows 数组的长度大于0,说明在Sheet中找到了至少一行与当前提交的 Name 和 Email 完全匹配的数据。
    • 在这种情况下,脚本将返回 ContentService.createTextOutput("Values were not appended. Data already exists.");,表示数据已存在,不执行追加操作。

通过以上修改,Google Apps Script在接收到数据后,会首先执行服务器端的重复性检查。只有当提交的数据在Sheet中不存在时,才会继续执行数据追加操作。

4. 重要注意事项

  1. Web应用版本部署:

    • 关键步骤! 每次修改 Google Apps Script 代码(尤其是 Web 应用的 doPost 函数)后,必须将其部署为新版本,否则您的更改不会生效。
    • 在 Apps Script 编辑器中,点击“部署” -> “新建部署”,选择类型为“Web 应用”,并确保“版本”选择“新建”。
  2. 唯一性判断标准:

    • 在示例中,我们使用 Name 和 Email 两个参数来判断数据的唯一性。在实际应用中,您需要根据业务逻辑选择一个或多个参数作为唯一标识符。例如,如果 Email 本身就是唯一的,则可以只检查 Email。
    • 确保所选的唯一性参数在Sheet中具有实际的唯一性。
  3. 并发处理与锁机制:

    • 原始代码中使用了 LockService.getScriptLock() 和 lock.tryLock(10000) 来处理并发请求。这是一个良好的实践,可以防止多个用户同时提交数据时可能导致的数据冲突或乱序问题。在进行重复性检查时,锁机制依然重要,它确保在检查和写入过程中,Sheet的数据状态是稳定的。
  4. 响应与用户体验:

    • 当检测到重复数据时,脚本返回了 ContentService.createTextOutput("Values were not appended. Data already exists.");。前端JavaScript可以通过检查 fetch 请求的响应来告知用户数据是否成功提交或因重复而被阻止。这有助于提升用户体验。

5. 总结

通过在Google Apps Script的 doPost 函数中引入服务器端重复数据校验机制,我们能够有效解决Web应用中因URL参数导致的重复数据提交问题。这种方法不仅弥补了客户端校验的不足,确保了Google Sheet中数据的准确性和唯一性,也为构建更健壮、更可靠的数据收集系统提供了基础。务必记住,每次对Apps Script Web应用代码进行修改后,都需要部署新版本才能使更改生效。

热门AI工具

更多
墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

相关专题

更多
mysql标识符无效错误怎么解决
mysql标识符无效错误怎么解决

mysql标识符无效错误的解决办法:1、检查标识符是否被其他表或数据库使用;2、检查标识符是否包含特殊字符;3、使用引号包裹标识符;4、使用反引号包裹标识符;5、检查MySQL的配置文件等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

15520

2023.12.04

Python标识符有哪些
Python标识符有哪些

Python标识符有变量标识符、函数标识符、类标识符、模块标识符、下划线开头的标识符、双下划线开头、双下划线结尾的标识符、整型标识符、浮点型标识符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1143

2024.02.23

java标识符合集
java标识符合集

本专题整合了java标识符相关内容,想了解更多详细内容,请阅读下面的文章。

8610

2025.06.11

c++标识符介绍
c++标识符介绍

本专题整合了c++标识符相关内容,阅读专题下面的文章了解更多详细内容。

3402

2025.08.07

length函数用法
length函数用法

length函数用于返回指定字符串的字符数或字节数。可以用于计算字符串的长度,以便在查询和处理字符串数据时进行操作和判断。 需要注意的是length函数计算的是字符串的字符数,而不是字节数。对于多字节字符集,一个字符可能由多个字节组成。因此,length函数在计算字符串长度时会将多字节字符作为一个字符来计算。更多关于length函数的用法,大家可以阅读本专题下面的文章。

1887

2023.09.19

http与https有哪些区别
http与https有哪些区别

http与https的区别:1、协议安全性;2、连接方式;3、证书管理;4、连接状态;5、端口号;6、资源消耗;7、兼容性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

5470

2024.08.16

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

60

2026.10.22

Gradle Wrapper配置教程
Gradle Wrapper配置教程

本专题介绍Gradle Wrapper的基本作用,涵盖文件组成、生成方法、版本配置和任务调用,帮助你理解项目如何使用指定的Gradle版本,减少不同开发环境带来的构建差异。

40

2026.10.22

Golangci-lint检查器使用教程
Golangci-lint检查器使用教程

本专题整理Golangci-lint常用Linters的用途与配置方法,覆盖错误处理、无效代码、安全、复杂度、命名和依赖检查,帮助项目合理选规则。

40

2026.10.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WebStorm 官方调试文档
WebStorm 官方调试文档

共0课时 | 0人学习

React 教程
React 教程

共58课时 | 12.3万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 6.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn