From 406d1956b86f164776dea7baaf8316b9fe5b4bf3 Mon Sep 17 00:00:00 2001 From: Dream Hunter Date: Sun, 6 Sep 2026 23:08:41 +0800 Subject: [PATCH] feat: add address activity disable switch (#1138) * feat: add address activity disable switch * style: separate address activity disable guards * docs: clarify address activity switch behavior --- CHANGELOG.md | 1 + CHANGELOG_EN.md | 1 + vitepress-docs/docs/en/guide/worker-vars.md | 1 + vitepress-docs/docs/zh/guide/worker-vars.md | 1 + worker/src/admin_api/cleanup_api.ts | 6 +++++- worker/src/common.ts | 8 ++++++++ worker/src/i18n/en.ts | 1 + worker/src/i18n/type.ts | 1 + worker/src/i18n/zh.ts | 1 + worker/src/types.d.ts | 1 + worker/wrangler.toml.template | 2 ++ 11 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8a5dc4..610e382 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Features +- feat: |Worker| 新增 `DISABLE_ADDRESS_UPDATED_AT`,可关闭单地址及用户批量的主动保活刷新,并禁止内置手动及定时不活跃地址清理,降低 D1 写入量 - feat: |Frontend| 新增 `VITE_DEFAULT_LANG` 构建变量,并支持通过 `index.html` 运行时配置覆盖前端设置 - feat: |兑换码| 新增角色、发信额度及专属邮箱兑换与管理,完善并发保护和表单提示 - feat: |邮件| 新增可选的已读/未读状态,支持点击邮件自动已读和手动切换状态 diff --git a/CHANGELOG_EN.md b/CHANGELOG_EN.md index 4a5007f..513d099 100644 --- a/CHANGELOG_EN.md +++ b/CHANGELOG_EN.md @@ -10,6 +10,7 @@ ### Features +- feat: |Worker| Add `DISABLE_ADDRESS_UPDATED_AT` to disable individual and user-wide address activity keep-alive updates and built-in manual/scheduled inactive-address cleanup, reducing D1 writes - feat: |Frontend| Add the `VITE_DEFAULT_LANG` build variable and support overriding frontend settings through runtime configuration in `index.html` - feat: |Redemption Codes| Add role, sending-credit and custom-mailbox redemption with Admin management, concurrency protection and form validation - feat: |Mail| Add optional read/unread status with click-to-read and manual status switching diff --git a/vitepress-docs/docs/en/guide/worker-vars.md b/vitepress-docs/docs/en/guide/worker-vars.md index 990b87d..de32a89 100644 --- a/vitepress-docs/docs/en/guide/worker-vars.md +++ b/vitepress-docs/docs/en/guide/worker-vars.md @@ -38,6 +38,7 @@ When `ADMIN_API_IP_WHITELIST` is unset or empty, source IPs are not restricted. | `MIN_ADDRESS_LEN` | Number | Minimum length of `email address` name | `1` | | `MAX_ADDRESS_LEN` | Number | Maximum length of `email address` name | `30` | | `DISABLE_CUSTOM_ADDRESS_NAME` | Text/JSON | Disable custom email address names, if set to true, users cannot enter custom names and they will be auto-generated | `true` | +| `DISABLE_ADDRESS_UPDATED_AT` | Text/JSON | Defaults to `false`. Set to `true` to skip individual and bulk activity timestamp updates triggered by user settings, mailbox access, sending mail, and other address activity. Initial timestamps for new addresses and password generation/change/reset behavior, including their `updated_at` updates, remain unchanged. Manual `/admin/cleanup` requests with `cleanType=inactiveAddress` return `403`; scheduled tasks skip that type and continue other cleanup. Cleanup by creation time, other cleanup types, and administrator-defined SQL are unaffected. Set back to `false` to restore normal behavior without backfilling activity from the disabled period | `true` | | `ADDRESS_CHECK_REGEX` | Text | Regular expression for `email address` name, used for validation only | `^(?!.*admin).*` | | `ADDRESS_REGEX` | Text | Regular expression to replace illegal symbols in `email address` name, symbols not in the regex will be replaced. Default is `[^a-z0-9]` if not set. Use with caution as some symbols may prevent email reception | `[^a-z0-9]` | | `DEFAULT_DOMAINS` | JSON | Default domains available to users (not logged in or users without assigned roles) | `["awsl.uk", "dreamhunter2333.xyz"]` | diff --git a/vitepress-docs/docs/zh/guide/worker-vars.md b/vitepress-docs/docs/zh/guide/worker-vars.md index ecdb0ba..f6c5dd0 100644 --- a/vitepress-docs/docs/zh/guide/worker-vars.md +++ b/vitepress-docs/docs/zh/guide/worker-vars.md @@ -38,6 +38,7 @@ | `MIN_ADDRESS_LEN` | 数字 | `邮箱名称` 的最小长度 | `1` | | `MAX_ADDRESS_LEN` | 数字 | `邮箱名称` 的最大长度 | `30` | | `DISABLE_CUSTOM_ADDRESS_NAME` | 文本/JSON | 禁用自定义邮箱地址名称,如果设置为 true,则用户无法输入自定义邮箱名称,将由后台自动生成 | `true` | +| `DISABLE_ADDRESS_UPDATED_AT` | 文本/JSON | 默认 `false`。设为 `true` 时跳过用户设置、邮箱访问、发信等触发的单地址及批量活跃时间更新。创建地址的初始时间、生成/修改/重置密码及其 `updated_at` 更新不变。手动 `/admin/cleanup` 的 `cleanType=inactiveAddress` 返回 `403`,定时任务跳过该项并继续其他清理;按创建时间清理、其他清理类型及管理员自定义 SQL 不受影响。改回 `false` 恢复原有行为,不补齐禁用期间的活跃记录 | `true` | | `ADDRESS_CHECK_REGEX` | 文本 | `邮箱名称` 的正则表达式, 只用于检查 | `^(?!.*admin).*` | | `ADDRESS_REGEX` | 文本 | `邮箱名称` 替换非法符号的正则表达式, 不在其中的符号将被替换,如果不设置,默认为 `[^a-z0-9]`, 需谨慎使用, 有些符号可能导致无法收件 | `[^a-z0-9]` | | `DEFAULT_DOMAINS` | JSON | 默认用户可用的域名(未登录或未分配角色的用户) | `["awsl.uk", "dreamhunter2333.xyz"]` | diff --git a/worker/src/admin_api/cleanup_api.ts b/worker/src/admin_api/cleanup_api.ts index ca19989..1dbbcf7 100644 --- a/worker/src/admin_api/cleanup_api.ts +++ b/worker/src/admin_api/cleanup_api.ts @@ -98,7 +98,11 @@ export default { const msgs = i18n.getMessagesbyContext(c); const { cleanType, cleanDays } = await c.req.json(); try { - await cleanup(c, cleanType, cleanDays); + const success = await cleanup(c, cleanType, cleanDays); + // Report disabled cleanup as forbidden rather than successful. + if (!success) { + return c.text(msgs.InactiveAddressCleanupDisabledMsg, 403); + } } catch (error) { console.error(error); return c.text(`${msgs.OperationFailedMsg}: ${(error as Error).message}`, 500) diff --git a/worker/src/common.ts b/worker/src/common.ts index 0fd4a49..b4aa8cb 100644 --- a/worker/src/common.ts +++ b/worker/src/common.ts @@ -246,6 +246,8 @@ export function updateAddressUpdatedAt( if (!address) { return; } + // Skip activity timestamp writes when activity tracking is disabled. + if (getBooleanValue(c.env.DISABLE_ADDRESS_UPDATED_AT)) return; // update address updated_at asynchronously c.executionCtx.waitUntil((async () => { try { @@ -268,6 +270,8 @@ export function updateUserAddressesUpdatedAt( if (!userId) { return; } + // Apply the same activity tracking switch to bulk updates. + if (getBooleanValue(c.env.DISABLE_ADDRESS_UPDATED_AT)) return; c.executionCtx.waitUntil((async () => { try { await c.env.DB.prepare( @@ -494,6 +498,10 @@ export const cleanup = async ( cleanType: string | undefined | null, cleanDays: number | undefined | null ): Promise => { + // Frozen activity timestamps cannot reliably identify inactive addresses. + if (cleanType === "inactiveAddress" && getBooleanValue(c.env.DISABLE_ADDRESS_UPDATED_AT)) { + return false; + } const msgs = i18n.getMessagesbyContext(c); if (!cleanType || typeof cleanDays !== 'number' || cleanDays < 0 || cleanDays > 1000) { throw new Error(msgs.InvalidCleanupConfigMsg) diff --git a/worker/src/i18n/en.ts b/worker/src/i18n/en.ts index adb3f27..08395b4 100644 --- a/worker/src/i18n/en.ts +++ b/worker/src/i18n/en.ts @@ -88,6 +88,7 @@ const messages: LocaleMessages = { EnableSendMailForDomainMsg: "Please enable SEND_MAIL for this domain first", InvalidCleanupConfigMsg: "Invalid cleanType or cleanDays", InvalidCleanTypeMsg: "Invalid cleanType", + InactiveAddressCleanupDisabledMsg: "Address activity updates are disabled; cleanup by inactivity is not allowed", EnableKVForMailVerifyMsg: "Please enable KV first if you want to enable mail verify", VerifyMailDomainInvalidMsg: "VerifyMailSender domain must be in", InvalidMaxAddressCountMsg: "Invalid maxAddressCount", diff --git a/worker/src/i18n/type.ts b/worker/src/i18n/type.ts index bfd3a42..aafb1a7 100644 --- a/worker/src/i18n/type.ts +++ b/worker/src/i18n/type.ts @@ -86,6 +86,7 @@ export type LocaleMessages = { EnableSendMailForDomainMsg: string InvalidCleanupConfigMsg: string InvalidCleanTypeMsg: string + InactiveAddressCleanupDisabledMsg: string EnableKVForMailVerifyMsg: string VerifyMailDomainInvalidMsg: string InvalidMaxAddressCountMsg: string diff --git a/worker/src/i18n/zh.ts b/worker/src/i18n/zh.ts index 9946d41..b55582c 100644 --- a/worker/src/i18n/zh.ts +++ b/worker/src/i18n/zh.ts @@ -88,6 +88,7 @@ const messages: LocaleMessages = { EnableSendMailForDomainMsg: "请先为此域名启用 SEND_MAIL", InvalidCleanupConfigMsg: "无效的 cleanType 或 cleanDays", InvalidCleanTypeMsg: "无效的 cleanType", + InactiveAddressCleanupDisabledMsg: "已禁用地址活跃时间更新,不能按不活跃时间清理地址", EnableKVForMailVerifyMsg: "如果要启用邮件验证,请先启用 KV", VerifyMailDomainInvalidMsg: "验证邮件发送者域名必须在", InvalidMaxAddressCountMsg: "无效的 maxAddressCount", diff --git a/worker/src/types.d.ts b/worker/src/types.d.ts index b11c520..57f6bea 100644 --- a/worker/src/types.d.ts +++ b/worker/src/types.d.ts @@ -42,6 +42,7 @@ type Bindings = { RANDOM_SUBDOMAIN_DOMAINS: string | string[] | undefined RANDOM_SUBDOMAIN_LENGTH: string | number | undefined DISABLE_CUSTOM_ADDRESS_NAME: string | boolean | undefined + DISABLE_ADDRESS_UPDATED_AT: string | boolean | undefined CREATE_ADDRESS_DEFAULT_DOMAIN_FIRST: string | boolean | undefined ADMIN_USER_ROLE: string | undefined USER_DEFAULT_ROLE: string | UserRole | undefined diff --git a/worker/wrangler.toml.template b/worker/wrangler.toml.template index 183e671..cf6027d 100644 --- a/worker/wrangler.toml.template +++ b/worker/wrangler.toml.template @@ -41,6 +41,8 @@ PREFIX = "tmp" # MAX_ADDRESS_LEN = 30 # Disable custom email address name, if set true, users cannot input custom email name, will auto generate # DISABLE_CUSTOM_ADDRESS_NAME = true +# Disable address activity keep-alive updates and built-in manual/scheduled cleanup by inactivity. Defaults to false. +# DISABLE_ADDRESS_UPDATED_AT = true # IF YOU WANT TO MAKE YOUR SITE PRIVATE, UNCOMMENT THE FOLLOWING LINES # PASSWORDS = ["123", "456"] # For admin panel