iOS 提交
This commit is contained in:
@@ -1,5 +1,208 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v10.3.2 (2026-06-27)
|
||||
|
||||
### 🔐 隐私权管理增强 - 多方式登录/忘记密码注销/SMTP检测
|
||||
|
||||
**重要更新**:完善隐私权管理页面登录与注销流程,新增SMTP配置检测、4种登录方式、忘记密码注销回执码流程,前端UI全面重构。
|
||||
|
||||
#### 新增功能
|
||||
|
||||
| 功能 | 说明 | 优先级 |
|
||||
|------|------|--------|
|
||||
| 🔍 SMTP配置检测 | 公开API检测SMTP主机/端口/加密/密码是否配置完整+TCP端口可达性 | ⭐⭐⭐ |
|
||||
| 🔑 4种登录方式 | 密码/密保问题/邮箱验证码/回执号登录,iOS分段控制器切换 | ⭐⭐⭐⭐⭐ |
|
||||
| 📧 忘记密码注销 | 凭邮箱或密保答案验证身份→发送回执码→凭回执码提交注销(无需登录) | ⭐⭐⭐⭐⭐ |
|
||||
| 👤 状态查询显示昵称 | accountLookup返回nickname字段(脱敏处理,非账号) | ⭐⭐⭐⭐ |
|
||||
| 🌐 URL语言后缀 | 切换语言时URL同步更新`?lang=zh`/`?lang=en` | ⭐⭐⭐ |
|
||||
| 📋 注销事件查询 | 原"设备Tab"改为"注销事件查询",展示注销申请记录+删除进度 | ⭐⭐⭐ |
|
||||
| ✅ 注销协议勾选框 | 注销确认输入框下方增加协议勾选(可超链接),未勾选禁止提交 | ⭐⭐⭐⭐ |
|
||||
|
||||
#### 新增API接口
|
||||
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| SMTP状态检测 | GET | `/api/user_security/checkSmtpStatus` | 返回配置完整度+端口可达性 |
|
||||
| 密保问题登录 | POST | `/api/user_security/secQuestionLogin` | account+sec_answer |
|
||||
| 邮箱验证码登录 | POST | `/api/user_security/emailCodeLogin` | email+captcha(未注册自动创建) |
|
||||
| 发送注销回执码 | POST | `/api/user_security/sendDeletionCode` | 验证身份后发送6位码到邮箱 |
|
||||
| 凭回执码注销 | POST | `/api/user_security/requestDeletionByReceipt` | 无需登录,凭回执码提交注销 |
|
||||
|
||||
#### 前端变更
|
||||
|
||||
| 区域 | 变更 |
|
||||
|------|------|
|
||||
| Tab结构 | 移除"其他协议"Tab;"设备"Tab改为"📋 注销事件查询" |
|
||||
| 登录表单 | 重构为4种方式(iOS SegmentedControl):密码/密保/邮箱验证码/回执号 |
|
||||
| 忘记密码注销 | 新增可折叠区域:邮箱/密保两种验证→发送回执码→提交注销 |
|
||||
| 注销确认 | 增加协议勾选框(超链接《账号使用协议》和《隐私政策》) |
|
||||
| 状态查询 | 显示"👤 用户昵称: xxx" |
|
||||
| URL语言 | `?lang=zh`/`?lang=en` 后缀同步(history.replaceState) |
|
||||
| 多语言 | zh/en各新增约50个I18N key |
|
||||
| 文件行数 | 1942行 → 2687行 |
|
||||
|
||||
#### 测试结果
|
||||
|
||||
```
|
||||
✅ SMTP配置检测 配置完整+端口可达(host=free.mboxhosting.com:465/SSL)
|
||||
✅ accountLookup昵称 返回nickname字段
|
||||
✅ 密保问题登录 API正常(测试账号未设密保→正确返回错误)
|
||||
✅ 发送注销回执码 回执码已发送到 pt*****@test.local
|
||||
✅ 错误回执码拦截 错误回执码被正确拦截
|
||||
✅ 前端5个URL HTTP 200 (中文/英文/注销Tab/事件Tab/偏好Tab)
|
||||
通过率: 10/10 (100%)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## v10.3.1 (2026-06-27)
|
||||
|
||||
### 🛡️ 隐私权管理增强 - GDPR/PIPL/COPPA合规升级
|
||||
|
||||
**重要更新**:完善隐私权管理功能,新增6大能力(隐私偏好/设备管理/注销进度/未成年保护/邮件通知/数据最小化),修复跨域BUG,达到GDPR/PIPL/COPPA合规要求。
|
||||
|
||||
#### 新增功能
|
||||
|
||||
| 功能 | 说明 | 合规依据 |
|
||||
|------|------|----------|
|
||||
| 🔧 隐私偏好管理 | 4类数据处理同意开关(analytics/marketing/personalization/third_party_share) | GDPR Art.7 |
|
||||
| 📱 登录设备管理 | 设备列表/位置/最后活跃时间,支持远程登出 | 用户体验+安全 |
|
||||
| 📊 数据删除进度可视化 | 注销后展示8类数据删除进度条(账号/Token/设备/收藏/笔记/签到/积分/文章) | 透明度要求 |
|
||||
| 👨👩👧 未成年保护 | 注册时校验出生日期,14岁以下禁止注册,14-17岁开启家长控制(每日120分钟/内容过滤/禁消费) | COPPA/GDPR-K |
|
||||
| 📧 注销邮件通知 | 用户提交注销后异步发送确认邮件到注册邮箱,防冒用 | 安全要求 |
|
||||
| 🗑️ 数据最小化保留 | 注销记录保留期从2年→6个月,username做SHA256哈希存储 | PIPL最小化原则 |
|
||||
|
||||
#### BUG修复
|
||||
|
||||
| BUG | 原因 | 修复 |
|
||||
|-----|------|------|
|
||||
| ❌ 注销提交后后台查不到 | `API_BASE='https://tools.wktyl.com'`从s2ss.com发起跨域请求,`__token__`响应头不可读导致authToken=null | API_BASE改为相对路径`''`,优先从JSON body读取token |
|
||||
| ❌ 管理后台注销列表空 | admin/controller/Userdeletion.php中`$list`查询未带filter | 使用单一`$listQuery`统一过滤 |
|
||||
| ❌ 英文模式下输入框仍显示中文 | `switchLang()`未更新placeholder | 新增`phLookupAccount`/`phLoginAccount`/`phLoginPassword`翻译 |
|
||||
| ❌ SQL迁移ADD COLUMN IF NOT EXISTS失败 | 服务器MySQL版本不支持该语法 | 改用存储过程+INFORMATION_SCHEMA检查 |
|
||||
|
||||
#### 新增API接口
|
||||
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 隐私偏好查询 | GET | `/api/user_security/getConsents` | 返回4类同意状态 |
|
||||
| 隐私偏好更新 | POST | `/api/user_security/updateConsents` | 批量更新同意开关 |
|
||||
| 登录设备列表 | GET | `/api/user_security/listDevices` | 返回设备列表含位置/最后活跃 |
|
||||
| 远程登出设备 | POST | `/api/user_security/revokeDevice` | 通过id/device_id登出指定设备 |
|
||||
| 注销进度查询 | GET | `/api/user_security/deletionProgress` | 返回8类数据删除状态+百分比 |
|
||||
| 家长控制状态 | GET | `/api/user_security/parentalControlStatus` | 返回未成年状态+家长控制配置 |
|
||||
| 更新出生日期 | POST | `/api/user_security/updateBirthday` | 补充/修改出生日期,自动更新未成年状态 |
|
||||
| 清理旧注销记录 | GET | `/api/user_security/cleanupOldDeletionRecords` | 清理6个月以上已完成注销记录 |
|
||||
|
||||
#### 数据库变更
|
||||
|
||||
| 表 | 变更 | 说明 |
|
||||
|----|------|------|
|
||||
| `tool_user_consents` | 新建表 | 用户数据处理同意记录(user_id+consent_type唯一) |
|
||||
| `tool_user_deletion` | 新增 `deletion_progress` text | 注销进度JSON |
|
||||
| `tool_user_deletion` | 新增 `deletion_summary` varchar(500) | 注销数据摘要 |
|
||||
| `tool_user` | 新增 `birthday` date | 出生日期 |
|
||||
| `tool_user` | 新增 `is_minor` tinyint(1) | 是否未成年(0/1) |
|
||||
|
||||
#### 前端变更
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `privacy-rights.html` | 4→6 Tab(新增📱设备/⚙️偏好),注销进度条,浏览器语言自动检测,登录态联动 |
|
||||
| `account-agreement.html` | V6.6→V6.7:保留期2年→6个月+SHA256哈希 |
|
||||
|
||||
#### 测试结果
|
||||
|
||||
```
|
||||
✅ register(带birthday) code=1 注册成功,age=31 is_minor=0
|
||||
✅ parentalControlStatus code=1 返回家长控制配置
|
||||
✅ updateBirthday(未成年) code=1 改为2010年生,is_minor=1
|
||||
✅ getConsents code=1 返回4项同意配置
|
||||
✅ updateConsents code=1 成功更新4项
|
||||
✅ listDevices code=1 返回设备列表
|
||||
✅ deletionProgress code=1 返回进度
|
||||
✅ privacy-rights.html HTTP 200 (devices/preferences tab正常)
|
||||
通过率: 8/8 (100%)
|
||||
```
|
||||
|
||||
#### 部署清单
|
||||
|
||||
- 已上传7个文件到 `123.207.67.197:/www/wwwroot/tools.wktyl.com/`
|
||||
- 备份目录: `backup_privacy_rights_20260627_021954`
|
||||
- SQL迁移已执行(存储过程兼容MySQL 5.7)
|
||||
|
||||
---
|
||||
|
||||
## v10.3.0 (2026-06-27)
|
||||
|
||||
### 🛡️ 隐私权管理中心 + Google Play应用外账户管理
|
||||
|
||||
**重大更新**:新增独立的隐私权管理网页,满足Google Play数据安全合规要求,支持应用外账号状态查询与注销。
|
||||
|
||||
#### 新增功能
|
||||
|
||||
| 功能 | 说明 |
|
||||
|------|------|
|
||||
| 🛡️ 隐私权管理页面 | 4-Tab界面:状态查询/注销账号/隐私权利/其他协议,Apple风格设计,中英双语 |
|
||||
| 🔍 公开账号状态查询 | `accountLookup` API,无需登录即可查询5种状态(正常/封锁/注销中/已注销/无记录) |
|
||||
| 🔐 HMAC-SHA256防枚举 | 查询接口需回执签名,防止账号枚举攻击 |
|
||||
| 📋 账号协议V6.6 | 新增长期未登录账号回收政策(12/18/24个月三级)、服务器数据删除时限、Google Play合规说明 |
|
||||
| 🧪 全流程验证脚本 | `test_privacy_rights_flow.py`,覆盖注册→登录→查询→注销→查询状态→撤销6个接口 |
|
||||
|
||||
#### 新增文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `public/agreements/privacy-rights.html` | 隐私权管理中心网页(1488行,含Web Crypto API回执生成) |
|
||||
| `Scripts/upload_privacy_rights.py` | 服务器文件上传脚本(含备份、SSH端口2026) |
|
||||
| `Scripts/test_privacy_rights_flow.py` | 全流程接口验证脚本(6/6通过) |
|
||||
|
||||
#### 修改文件
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
|------|----------|
|
||||
| `application/api/controller/UserSecurity.php` | 新增 `accountLookup()` 方法(1152行),加入 `$noNeedLogin` 和 `$rateLimits` |
|
||||
| `application/route.php` | 新增 `api/user_security/accountLookup` 路由(第73行) |
|
||||
| `public/agreements/account-agreement.html` | 升级V6.6:新增3.4长期未登录账号回收政策、4.5服务器数据删除时限、4.6应用外账户管理支持 |
|
||||
| `public/agreements/index.html` | 隐私与安全分区新增"隐私权管理"入口(中英文) |
|
||||
| `Scripts/upload_agreements.py` | 修复SSH端口从22改为2026 |
|
||||
|
||||
#### API接口
|
||||
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 账号状态查询 | POST | `/api/user_security/accountLookup` | 公开接口,需回执验证,限流20次/小时/IP |
|
||||
|
||||
#### 状态查询返回值
|
||||
|
||||
| status | status_text | 说明 |
|
||||
|--------|-------------|------|
|
||||
| `normal` | 正常 | 账号正常使用 |
|
||||
| `blocked` | 封锁 | 账号被封禁 |
|
||||
| `deleting` | 注销中 | 注销申请审核中 |
|
||||
| `deleted` | 已注销 | 账号已注销 |
|
||||
| `no_record` | 无记录 | 查无此账号 |
|
||||
|
||||
#### 测试结果
|
||||
|
||||
```
|
||||
✅ register code=1 注册成功
|
||||
✅ login code=1 登录成功
|
||||
✅ accountLookup code=1 状态查询成功
|
||||
✅ requestDeletion code=1 注销申请提交成功
|
||||
✅ deletionStatus code=1 注销状态查询成功
|
||||
✅ cancelDeletion code=1 撤销注销成功
|
||||
通过率: 6/6 (100%)
|
||||
```
|
||||
|
||||
#### 部署URL
|
||||
|
||||
- 隐私权管理页:https://tools.wktyl.com/agreements/privacy-rights.html
|
||||
- 协议列表页:https://tools.wktyl.com/agreements/index.html
|
||||
- 账号协议页:https://tools.wktyl.com/agreements/account-agreement.html
|
||||
|
||||
---
|
||||
|
||||
## v10.2.1 (2026-06-25)
|
||||
|
||||
### 🛡️ 限流排队系统优化
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
-- ============================================================
|
||||
-- 迁移脚本: v10.3.1 隐私权管理增强
|
||||
-- 创建时间: 2026-06-27
|
||||
-- 描述: 新增用户同意管理表、注销进度字段、用户出生日期字段
|
||||
-- 兼容: MySQL 5.7+ (不使用 ADD COLUMN IF NOT EXISTS 语法)
|
||||
-- ============================================================
|
||||
|
||||
-- 1. 用户数据处理同意表 (GDPR/PIPL合规)
|
||||
CREATE TABLE IF NOT EXISTS `tool_user_consents` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL DEFAULT '0' COMMENT '用户ID',
|
||||
`consent_type` varchar(50) NOT NULL DEFAULT '' COMMENT '同意类型: analytics/marketing/personalization/third_party_share',
|
||||
`consent_value` tinyint(1) NOT NULL DEFAULT '1' COMMENT '0=拒绝 1=同意',
|
||||
`consent_source` varchar(20) NOT NULL DEFAULT 'web' COMMENT '来源: web/app/system',
|
||||
`ip` varchar(50) DEFAULT '' COMMENT 'IP地址',
|
||||
`createtime` int(11) DEFAULT NULL COMMENT '创建时间',
|
||||
`updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_user_type` (`user_id`,`consent_type`),
|
||||
KEY `idx_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户数据处理同意记录';
|
||||
|
||||
-- 2. user_deletion表新增注销进度字段 (使用存储过程兼容MySQL 5.7)
|
||||
DROP PROCEDURE IF EXISTS `migrate_v10_3_add_columns`;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE `migrate_v10_3_add_columns`()
|
||||
BEGIN
|
||||
-- tool_user_deletion: deletion_progress
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'tool_user_deletion'
|
||||
AND COLUMN_NAME = 'deletion_progress') THEN
|
||||
ALTER TABLE `tool_user_deletion` ADD COLUMN `deletion_progress` text COMMENT '注销进度JSON' AFTER `admin_remark`;
|
||||
END IF;
|
||||
|
||||
-- tool_user_deletion: deletion_summary
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'tool_user_deletion'
|
||||
AND COLUMN_NAME = 'deletion_summary') THEN
|
||||
ALTER TABLE `tool_user_deletion` ADD COLUMN `deletion_summary` varchar(500) DEFAULT '' COMMENT '注销数据摘要' AFTER `deletion_progress`;
|
||||
END IF;
|
||||
|
||||
-- tool_user: birthday
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'tool_user'
|
||||
AND COLUMN_NAME = 'birthday') THEN
|
||||
ALTER TABLE `tool_user` ADD COLUMN `birthday` date DEFAULT NULL COMMENT '出生日期' AFTER `email`;
|
||||
END IF;
|
||||
|
||||
-- tool_user: is_minor
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'tool_user'
|
||||
AND COLUMN_NAME = 'is_minor') THEN
|
||||
ALTER TABLE `tool_user` ADD COLUMN `is_minor` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否未成年: 0=成年 1=未成年(需家长控制)' AFTER `birthday`;
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
CALL `migrate_v10_3_add_columns`();
|
||||
DROP PROCEDURE IF EXISTS `migrate_v10_3_add_columns`;
|
||||
@@ -34,6 +34,7 @@ class Userdeletion extends Backend
|
||||
$filterJson = $this->request->get('filter', '{}');
|
||||
|
||||
$query = Db::name('user_deletion');
|
||||
$listQuery = Db::name('user_deletion');
|
||||
|
||||
try {
|
||||
$filter = json_decode($filterJson, true);
|
||||
@@ -41,29 +42,18 @@ class Userdeletion extends Backend
|
||||
foreach ($filter as $key => $val) {
|
||||
if ($val !== '' && $val !== null) {
|
||||
$query->where($key, '=', $val);
|
||||
$listQuery->where($key, '=', $val);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {}
|
||||
|
||||
$total = $query->count();
|
||||
$list = Db::name('user_deletion')
|
||||
$list = $listQuery
|
||||
->order($sort ?: 'createtime', $order ?: 'desc')
|
||||
->limit($offset, $limit)
|
||||
->select();
|
||||
|
||||
if (!empty($filter) && is_array($filter)) {
|
||||
$listQuery = Db::name('user_deletion');
|
||||
foreach ($filter as $key => $val) {
|
||||
if ($val !== '' && $val !== null) {
|
||||
$listQuery->where($key, '=', $val);
|
||||
}
|
||||
}
|
||||
$list = $listQuery->order($sort ?: 'createtime', $order ?: 'desc')
|
||||
->limit($offset, $limit)
|
||||
->select();
|
||||
}
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$statusMap = [0 => '待审核', 1 => '已通过', 2 => '已拒绝', 3 => '已自动注销'];
|
||||
$item['status_text'] = $statusMap[$item['status']] ?? '未知';
|
||||
@@ -212,12 +202,17 @@ class Userdeletion extends Backend
|
||||
|
||||
Db::name('user')->where('id', $userId)->delete();
|
||||
|
||||
// 注销完成后,对username做SHA256哈希(PIPL最小化保留原则)
|
||||
// 保留6个月后由cleanupOldDeletionRecords接口自动清理
|
||||
$hashedUsername = hash('sha256', $record['username'] ?? '');
|
||||
|
||||
Db::name('user_deletion')->where('id', $record['id'])->update([
|
||||
'status' => $status,
|
||||
'admin_id' => $adminId,
|
||||
'admin_remark' => $remark,
|
||||
'updatetime' => time(),
|
||||
'deletetime' => time(),
|
||||
'username' => $hashedUsername,
|
||||
]);
|
||||
|
||||
Db::commit();
|
||||
|
||||
@@ -276,6 +276,11 @@ class Feed extends Api
|
||||
}
|
||||
$cached = Cache::get($cacheKey);
|
||||
if ($cached && !$hasSeenIds) {
|
||||
// v2.6: 缓存命中后重新附加当前用户的 is_liked/is_favorited 状态
|
||||
// 缓存中的 per-user 字段可能来自其他用户,不可信,必须按当前用户重查
|
||||
if (isset($cached['list']) && !empty($cached['list'])) {
|
||||
$this->_batchAttachInteractionCounts($cached['list'], $this->_getUserId());
|
||||
}
|
||||
$this->success('成功(cache)', $cached);
|
||||
return;
|
||||
}
|
||||
@@ -421,6 +426,11 @@ class Feed extends Api
|
||||
unset($item);
|
||||
}
|
||||
|
||||
// 批量附加互动计数(like_count/favorite_count/comment_count/share_count)
|
||||
// 修复: 原仅 /api/feed/detail 返回这些字段,列表接口缺失导致客户端按钮显示 0
|
||||
// v2.6: 同时回填当前用户的 is_liked/is_favorited 状态,解决已收藏句子重复出现时状态丢失
|
||||
$this->_batchAttachInteractionCounts($items, $this->_getUserId());
|
||||
|
||||
$result = [
|
||||
'list' => $items,
|
||||
'total' => $total,
|
||||
@@ -432,7 +442,18 @@ class Feed extends Api
|
||||
];
|
||||
|
||||
if (!$hasSeenIds) {
|
||||
Cache::set($cacheKey, $result, 60);
|
||||
// v2.6: 缓存前剥离 per-user 字段(is_liked/is_favorited)
|
||||
// 这些字段是用户私有的,缓存后其他用户命中会串状态
|
||||
// 缓存命中时由 _batchAttachInteractionCounts 重新按当前用户查询
|
||||
$cacheResult = $result;
|
||||
if (isset($cacheResult['list'])) {
|
||||
foreach ($cacheResult['list'] as &$cacheItem) {
|
||||
$cacheItem['is_liked'] = false;
|
||||
$cacheItem['is_favorited'] = false;
|
||||
}
|
||||
unset($cacheItem);
|
||||
}
|
||||
Cache::set($cacheKey, $cacheResult, 60);
|
||||
}
|
||||
|
||||
$this->success('成功', $result);
|
||||
@@ -2682,41 +2703,102 @@ class Feed extends Api
|
||||
/**
|
||||
* @name 清除Feed相关缓存
|
||||
* @desc 在互动操作写入后主动清除相关缓存,确保数据一致性
|
||||
* @lastUpdate v2.5 修复 _lite 后缀缓存未清理 + Cache::rm 不生效的 bug:
|
||||
* 1) 增加 _lite 后缀变体循环
|
||||
* 2) 在 Cache::rm 后调用 _unlinkCacheFile 兜底,直接删除缓存文件
|
||||
* 绕过 opcache 缓存旧代码 / ThinkPHP complex 驱动异常
|
||||
*/
|
||||
private function _clearFeedCache($feedType, $feedId)
|
||||
{
|
||||
try {
|
||||
Cache::rm('feed_stats');
|
||||
Cache::rm('feed_channels');
|
||||
Cache::rm('feed_weight_config');
|
||||
Cache::rm('feed_weight_config_api');
|
||||
Cache::rm("feed_count_{$feedType}");
|
||||
Cache::rm("feed_recommend_guest_20");
|
||||
// 单键缓存
|
||||
$singleKeys = [
|
||||
'feed_stats',
|
||||
'feed_channels',
|
||||
'feed_weight_config',
|
||||
'feed_weight_config_api',
|
||||
"feed_count_{$feedType}",
|
||||
'feed_recommend_guest_20',
|
||||
];
|
||||
foreach ($singleKeys as $sk) {
|
||||
Cache::rm($sk);
|
||||
$this->_unlinkCacheFile($sk);
|
||||
}
|
||||
|
||||
// 列表缓存:覆盖 newest/hottest × 5 页 × 4 种 limit × 普通与 _lite × 平台后缀
|
||||
$sorts = ['newest', 'hottest'];
|
||||
$limits = [10, 20, 30, 50];
|
||||
// 修复 v2.5: 必须覆盖客户端所有可能的 limit 值(含 5),否则 limit=5 的缓存不会被清理
|
||||
$limits = [5, 10, 15, 20, 25, 30, 40, 50];
|
||||
$liteVariants = ['', '_lite']; // lite 模式变体
|
||||
for ($p = 1; $p <= 5; $p++) {
|
||||
foreach ($sorts as $sort) {
|
||||
foreach ($limits as $lim) {
|
||||
Cache::rm("feed_list_{$feedType}_{$sort}_{$p}_{$lim}");
|
||||
Cache::rm("feed_list_all_{$sort}_{$p}_{$lim}");
|
||||
// 带平台后缀
|
||||
foreach (self::$platforms as $pKey => $pName) {
|
||||
Cache::rm("feed_list_{$feedType}_{$sort}_{$p}_{$lim}_plat_{$pKey}");
|
||||
Cache::rm("feed_list_all_{$sort}_{$p}_{$lim}_plat_{$pKey}");
|
||||
foreach ($liteVariants as $liteSuffix) {
|
||||
$k1 = "feed_list_{$feedType}_{$sort}_{$p}_{$lim}{$liteSuffix}";
|
||||
$k2 = "feed_list_all_{$sort}_{$p}_{$lim}{$liteSuffix}";
|
||||
Cache::rm($k1);
|
||||
Cache::rm($k2);
|
||||
$this->_unlinkCacheFile($k1);
|
||||
$this->_unlinkCacheFile($k2);
|
||||
// 带平台后缀
|
||||
foreach (self::$platforms as $pKey => $pName) {
|
||||
$pk1 = "feed_list_{$feedType}_{$sort}_{$p}_{$lim}{$liteSuffix}_plat_{$pKey}";
|
||||
$pk2 = "feed_list_all_{$sort}_{$p}_{$lim}{$liteSuffix}_plat_{$pKey}";
|
||||
Cache::rm($pk1);
|
||||
Cache::rm($pk2);
|
||||
$this->_unlinkCacheFile($pk1);
|
||||
$this->_unlinkCacheFile($pk2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Cache::rm("feed_trending_{$feedType}_20");
|
||||
Cache::rm("feed_trending_all_20");
|
||||
|
||||
// trending 缓存
|
||||
$trendingKeys = [
|
||||
"feed_trending_{$feedType}_20",
|
||||
"feed_trending_all_20",
|
||||
];
|
||||
foreach ($trendingKeys as $tk) {
|
||||
Cache::rm($tk);
|
||||
$this->_unlinkCacheFile($tk);
|
||||
}
|
||||
foreach (self::$platforms as $pKey => $pName) {
|
||||
Cache::rm("feed_trending_{$feedType}_20_{$pKey}");
|
||||
Cache::rm("feed_trending_all_20_{$pKey}");
|
||||
Cache::rm("feed_recommend_guest_20_plat_{$pKey}");
|
||||
$platKeys = [
|
||||
"feed_trending_{$feedType}_20_{$pKey}",
|
||||
"feed_trending_all_20_{$pKey}",
|
||||
"feed_recommend_guest_20_plat_{$pKey}",
|
||||
];
|
||||
foreach ($platKeys as $pk) {
|
||||
Cache::rm($pk);
|
||||
$this->_unlinkCacheFile($pk);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 兜底删除缓存文件
|
||||
* @desc Cache::rm 在某些环境(opcache 缓存旧代码 / complex 驱动异常)下不删除文件,
|
||||
* 本方法直接按 ThinkPHP 5 File 驱动的路径规则计算缓存文件路径并 unlink
|
||||
* @param string $cacheKey 缓存 key(与 Cache::rm 入参一致)
|
||||
* @return bool 文件已删除返回 true,文件不存在或删除失败返回 false
|
||||
*/
|
||||
private function _unlinkCacheFile($cacheKey)
|
||||
{
|
||||
if (!defined('CACHE_PATH') || empty($cacheKey)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$hash = md5($cacheKey);
|
||||
$file = CACHE_PATH . substr($hash, 0, 2) . '/' . substr($hash, 2) . '.php';
|
||||
if (is_file($file) && @unlink($file)) {
|
||||
return true;
|
||||
}
|
||||
} catch (\Exception $e) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 清除权重配置缓存
|
||||
* @desc 在install/sync操作后清除权重相关缓存
|
||||
@@ -2730,7 +2812,8 @@ class Feed extends Api
|
||||
Cache::rm('feed_stats');
|
||||
Cache::rm('feed_channels');
|
||||
$sorts = ['newest', 'hottest'];
|
||||
$limits = [10, 20, 30, 50];
|
||||
// 修复 v2.5: 必须覆盖客户端所有可能的 limit 值(含 5),否则 limit=5 的缓存不会被清理
|
||||
$limits = [5, 10, 15, 20, 25, 30, 40, 50];
|
||||
for ($p = 1; $p <= 5; $p++) {
|
||||
foreach ($sorts as $sort) {
|
||||
foreach ($limits as $lim) {
|
||||
@@ -2946,9 +3029,136 @@ class Feed extends Api
|
||||
}
|
||||
}
|
||||
|
||||
// 批量附加互动计数(like_count/favorite_count/comment_count/share_count)
|
||||
// 修复: favorites/likes/history/readlater 列表也需返回真实计数
|
||||
// v2.6: 同时回填当前用户的 is_liked/is_favorited 状态
|
||||
$this->_batchAttachInteractionCounts($items, $this->_getUserId());
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 批量附加互动计数
|
||||
* @desc 一次性查询 feed_interaction 表,按 (feed_type, feed_id, action) 分组 COUNT,
|
||||
* 回填到 items 的 like_count/favorite_count/comment_count/share_count 字段。
|
||||
* 避免 N+1 查询,列表接口(list/favorites/likes/history/readlater)统一调用。
|
||||
* @param array &$items 待附加计数的 items 数组(引用传递)
|
||||
* @lastUpdate v2.4 新增; 修复客户端点赞按钮显示 0 的问题
|
||||
*/
|
||||
private function _batchAttachInteractionCounts(&$items, $userId = 0)
|
||||
{
|
||||
if (empty($items) || !is_array($items)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 收集所有 (feed_type, feed_id) 对
|
||||
$pairs = [];
|
||||
foreach ($items as $item) {
|
||||
$feedType = $item['feed_type'] ?? '';
|
||||
$feedId = intval($item['id'] ?? 0);
|
||||
if (empty($feedType) || $feedId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$pairs[$feedType][] = $feedId;
|
||||
}
|
||||
if (empty($pairs)) {
|
||||
// 无有效对,仍填充 0 防止客户端解析 null
|
||||
foreach ($items as &$item) {
|
||||
if (!isset($item['like_count'])) $item['like_count'] = 0;
|
||||
if (!isset($item['favorite_count'])) $item['favorite_count'] = 0;
|
||||
if (!isset($item['comment_count'])) $item['comment_count'] = 0;
|
||||
if (!isset($item['share_count'])) $item['share_count'] = 0;
|
||||
// v2.6: 回填当前用户互动状态(未登录默认 false)
|
||||
if (!isset($item['is_liked'])) $item['is_liked'] = false;
|
||||
if (!isset($item['is_favorited'])) $item['is_favorited'] = false;
|
||||
}
|
||||
unset($item);
|
||||
return;
|
||||
}
|
||||
|
||||
// 构造 OR 查询条件: ((feed_type='poetry' AND feed_id IN (...)) OR (feed_type='wisdom' AND feed_id IN (...)) ...)
|
||||
// 仅查询 like/favorite/comment/share 四种 action
|
||||
$countMap = []; // key: "feed_type_feed_id" => ["like"=>n, "favorite"=>n, ...]
|
||||
try {
|
||||
foreach ($pairs as $feedType => $ids) {
|
||||
$ids = array_unique($ids);
|
||||
$rows = Db::name('feed_interaction')
|
||||
->field('feed_id, action, COUNT(*) AS cnt')
|
||||
->where('feed_type', $feedType)
|
||||
->where('feed_id', 'in', $ids)
|
||||
->where('action', 'in', ['like', 'favorite', 'comment', 'share'])
|
||||
->group('feed_id, action')
|
||||
->select();
|
||||
foreach ($rows as $row) {
|
||||
$fid = intval($row['feed_id']);
|
||||
$action = $row['action'];
|
||||
$cnt = intval($row['cnt']);
|
||||
$key = $feedType . '_' . $fid;
|
||||
if (!isset($countMap[$key])) {
|
||||
$countMap[$key] = [
|
||||
'like' => 0,
|
||||
'favorite' => 0,
|
||||
'comment' => 0,
|
||||
'share' => 0,
|
||||
];
|
||||
}
|
||||
$countMap[$key][$action] = $cnt;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 查询失败时降级为 0
|
||||
}
|
||||
|
||||
// v2.6 新增:批量查询当前用户的互动状态,回填 is_liked/is_favorited
|
||||
// 解决:list 接口不返回用户个人状态,导致已收藏句子重复出现时显示"未收藏"
|
||||
$userActionMap = []; // key: "feed_type_feed_id" => ["like"=>bool, "favorite"=>bool]
|
||||
if (!empty($userId)) {
|
||||
try {
|
||||
foreach ($pairs as $feedType => $ids) {
|
||||
$ids = array_unique($ids);
|
||||
$userRows = Db::name('feed_interaction')
|
||||
->field('feed_id, action')
|
||||
->where('user_id', $userId)
|
||||
->where('feed_type', $feedType)
|
||||
->where('feed_id', 'in', $ids)
|
||||
->where('action', 'in', ['like', 'favorite'])
|
||||
->select();
|
||||
foreach ($userRows as $row) {
|
||||
$fid = intval($row['feed_id']);
|
||||
$action = $row['action'];
|
||||
$key = $feedType . '_' . $fid;
|
||||
if (!isset($userActionMap[$key])) {
|
||||
$userActionMap[$key] = [
|
||||
'like' => false,
|
||||
'favorite' => false,
|
||||
];
|
||||
}
|
||||
$userActionMap[$key][$action] = true;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 查询失败时降级为 false
|
||||
}
|
||||
}
|
||||
|
||||
// 回填到 items
|
||||
foreach ($items as &$item) {
|
||||
$feedType = $item['feed_type'] ?? '';
|
||||
$feedId = intval($item['id'] ?? 0);
|
||||
$key = $feedType . '_' . $feedId;
|
||||
$counts = $countMap[$key] ?? null;
|
||||
$item['like_count'] = $counts ? $counts['like'] : 0;
|
||||
$item['favorite_count'] = $counts ? $counts['favorite'] : 0;
|
||||
$item['comment_count'] = $counts ? $counts['comment'] : 0;
|
||||
$item['share_count'] = $counts ? $counts['share'] : 0;
|
||||
// v2.6: 回填当前用户互动状态
|
||||
$userActions = $userActionMap[$key] ?? null;
|
||||
$item['is_liked'] = $userActions ? $userActions['like'] : false;
|
||||
$item['is_favorited'] = $userActions ? $userActions['favorite'] : false;
|
||||
}
|
||||
unset($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 获取权重配置
|
||||
* @desc 从数据库读取管理员设置的推荐权重配置,带缓存
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -70,6 +70,21 @@ Route::rule([
|
||||
'api/user_security/requestDeletion' => 'api/UserSecurity/requestDeletion',
|
||||
'api/user_security/deletionStatus' => 'api/UserSecurity/deletionStatus',
|
||||
'api/user_security/cancelDeletion' => 'api/UserSecurity/cancelDeletion',
|
||||
'api/user_security/accountLookup' => 'api/UserSecurity/accountLookup',
|
||||
'api/user_security/cleanupOldDeletionRecords' => 'api/UserSecurity/cleanupOldDeletionRecords',
|
||||
'api/user_security/getConsents' => 'api/UserSecurity/getConsents',
|
||||
'api/user_security/updateConsents' => 'api/UserSecurity/updateConsents',
|
||||
'api/user_security/listDevices' => 'api/UserSecurity/listDevices',
|
||||
'api/user_security/revokeDevice' => 'api/UserSecurity/revokeDevice',
|
||||
'api/user_security/deletionProgress'=> 'api/UserSecurity/deletionProgress',
|
||||
'api/user_security/parentalControlStatus' => 'api/UserSecurity/parentalControlStatus',
|
||||
'api/user_security/updateBirthday' => 'api/UserSecurity/updateBirthday',
|
||||
// v10.3.2 新增
|
||||
'api/user_security/checkSmtpStatus' => 'api/UserSecurity/checkSmtpStatus',
|
||||
'api/user_security/secQuestionLogin' => 'api/UserSecurity/secQuestionLogin',
|
||||
'api/user_security/emailCodeLogin' => 'api/UserSecurity/emailCodeLogin',
|
||||
'api/user_security/sendDeletionCode' => 'api/UserSecurity/sendDeletionCode',
|
||||
'api/user_security/requestDeletionByReceipt'=> 'api/UserSecurity/requestDeletionByReceipt',
|
||||
]);
|
||||
|
||||
// OAuth社交登录
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
> @File: API_FEED_DOC.md
|
||||
> @Time: 2026-05-13
|
||||
> @Description: 信息流Feed系统API文档,聚合44种内容表为统一信息流
|
||||
> @LastUpdate: v2.3 新增刷新获取新内容接口(/api/feed/refresh_content); 新增用户偏好设置接口(/api/feed/preferences)
|
||||
> @LastUpdate: v2.6 list 接口返回 is_liked/is_favorited 当前用户互动状态;缓存命中后按当前用户重查 per-user 字段,缓存写入前剥离 per-user 字段避免跨用户串状态
|
||||
|
||||
---
|
||||
|
||||
@@ -158,6 +158,12 @@ curl -s "https://tools.wktyl.com/api/feed/channels" | python -m json.tool
|
||||
| sort | string | 当前排序 |
|
||||
| lite | bool | 是否轻量模式 |
|
||||
|
||||
> **v2.4 更新**: list 接口的每个 item 现在都包含 `like_count`、`favorite_count`、`comment_count`、`share_count` 字段(批量查询 feed_interaction 表回填),无需再单独调用 detail 接口获取计数。
|
||||
|
||||
> **v2.5 更新**: 修复 lite 模式(limit=5)点赞/取消点赞后 like_count 不更新的问题。根因是 `_clearFeedCache` 的 `$limits` 数组不含 5,导致 `feed_list_*_newest_1_5_lite` 缓存从未被清理。修复后 `$limits = [5, 10, 15, 20, 25, 30, 40, 50]`,并在 `Cache::rm` 后追加 `_unlinkCacheFile` 兜底直接删除缓存文件。互动操作后 lite 模式 list 接口立即返回最新计数。
|
||||
|
||||
> **v2.6 更新**: list 接口的每个 item 现在包含 `is_liked`、`is_favorited` 字段,表示**当前登录用户**是否已点赞/已收藏。未登录时始终返回 `false`。`_batchAttachInteractionCounts` 新增 `$userId` 参数,批量查询 `feed_interaction` 表中当前用户的 like/favorite 记录。**缓存隔离**:缓存命中后按当前用户重新查询 per-user 字段(缓存中的 is_liked/is_favorited 不可信),缓存写入前剥离为 false 避免跨用户串状态。客户端额外合并本地 DB 状态(OR 策略:服务端 true 或本地 true 均为 true),覆盖未登录场景和离线操作。
|
||||
|
||||
### 3.5 curl测试
|
||||
|
||||
```bash
|
||||
|
||||
@@ -261,9 +261,9 @@
|
||||
<div class="content-card">
|
||||
<div id="content-zh" class="lang-content">
|
||||
<p><strong>闲言APP</strong> 账号使用协议</p>
|
||||
<p>版本号:V6.5</p>
|
||||
<p>更新日期:2026年5月20日</p>
|
||||
<p>生效日期:2026年5月21日</p>
|
||||
<p>版本号:V6.6</p>
|
||||
<p>更新日期:2026年6月17日</p>
|
||||
<p>生效日期:2026年6月17日</p>
|
||||
<p>本协议是您与<strong>弥勒市朋普镇微风暴网络科技工作室</strong>关于<strong>闲言APP</strong>账号注册、使用、安全及注销等事项的约定。请您仔细阅读并充分理解本协议。</p>
|
||||
<h2>一、账号注册</h2>
|
||||
<h3>1.1 注册资格</h3>
|
||||
@@ -317,6 +317,20 @@
|
||||
<li>涉嫌违法违规活动</li>
|
||||
<li>长期未登录(超过<span class="highlight">2年</span>)</li>
|
||||
</ul>
|
||||
<h3>3.4 长期未登录账号回收政策</h3>
|
||||
<p>为合理利用服务器资源并保护账号安全,我们制定了以下长期未登录账号回收规则:</p>
|
||||
<ul>
|
||||
<li><span class="highlight">连续12个月未登录</span>:账号将进入"休眠状态",我们将通过App内消息、邮件或短信方式向您发送提醒通知</li>
|
||||
<li><span class="highlight">连续18个月未登录</span>:账号下的虚拟财产(积分、金币等)将停止产生,但不被清零;账号仍可正常登录恢复使用</li>
|
||||
<li><span class="highlight">连续24个月(2年)未登录</span>:我们将对账号进行<span class="highlight">回收处理</span>,具体处理方式如下:</li>
|
||||
<li>— 账号将被永久冻结,无法登录</li>
|
||||
<li>— 个人资料、收藏、笔记、签到记录等数据将被<span class="highlight">永久删除且不可恢复</span></li>
|
||||
<li>— 账号关联的邮箱、手机号将被解绑,可重新注册新账号</li>
|
||||
<li>— 在回收处理前30天,我们将通过注册邮箱/手机号发送最终通知</li>
|
||||
<li>回收处理后,您将无法找回该账号及其数据</li>
|
||||
<li>为避免账号被回收,请至少每<span class="highlight">12个月</span>登录一次</li>
|
||||
</ul>
|
||||
<p class="note">💡 提示:账号回收处理与主动注销的后果一致,均会导致数据永久删除。建议您定期登录以保留账号和数据。</p>
|
||||
<h2>四、账号注销</h2>
|
||||
<h3>4.1 注销条件</h3>
|
||||
<p>您有权随时申请注销账号,注销前请确保:</p>
|
||||
@@ -351,6 +365,30 @@
|
||||
<li>会员权益、积分、金币等虚拟财产将清零且<span class="highlight">不可恢复</span></li>
|
||||
<li>注销后<span class="highlight">无法恢复</span>,请谨慎操作</li>
|
||||
</ul>
|
||||
<h3>4.5 服务器数据删除时限</h3>
|
||||
<p>关于注销后服务器数据的删除时间,我们承诺以下规则:</p>
|
||||
<ul>
|
||||
<li><span class="highlight">管理员审核通过后立即删除</span>:管理员通过注销申请后,服务器将在<span class="highlight">5分钟内</span>完成以下数据的硬删除:</li>
|
||||
<li>— 用户主表(tool_user)记录</li>
|
||||
<li>— 用户Token、登录设备、在线状态等会话数据</li>
|
||||
<li>— 用户收藏、笔记、点赞等互动数据</li>
|
||||
<li>— 用户签到记录、积分流水、金币流水</li>
|
||||
<li>— 用户文章、评论及关联内容(作者字段匿名化)</li>
|
||||
<li><span class="highlight">审核期超时自动注销</span>:提交申请后<span class="highlight">3天</span>内若管理员未审核,系统将自动执行注销,删除范围同上</li>
|
||||
<li><span class="highlight">注销记录保留</span>:为满足法律法规和审计要求,注销操作记录将保留<span class="highlight">6个月</span>,且用户名在注销完成后立即进行SHA256哈希处理,仅保留不可逆的哈希值用于审计</li>
|
||||
<li>数据库备份遵循<span class="highlight">30天滚动覆盖</span>策略,即最新备份仅保留30天,超期自动清除</li>
|
||||
</ul>
|
||||
<p class="note">💡 重要说明:注销完成后,您将无法通过任何方式(App内、网页、客服查询)查找到该账号的任何信息,状态查询将返回"已注销"或"无记录"。</p>
|
||||
<h3>4.6 应用外账户管理支持(Google Play合规)</h3>
|
||||
<p>为满足Google Play数据安全合规要求,我们提供独立的<span class="highlight">应用外账户管理网页</span>,无需安装App即可进行账号管理操作:</p>
|
||||
<ul>
|
||||
<li><span class="highlight">账号状态查询</span>:访问 <a href="https://tools.wktyl.com/agreements/privacy-rights.html" style="color:var(--primary);">隐私权管理页面</a>,输入账号即可查询状态(正常/封锁/注销中/已注销/无记录)</li>
|
||||
<li><span class="highlight">应用外注销</span>:在隐私权管理页面登录后即可发起注销申请,流程与App内一致</li>
|
||||
<li><span class="highlight">注销状态查询</span>:随时查询注销申请的处理进度</li>
|
||||
<li><span class="highlight">撤销注销</span>:审核期内可随时撤销注销申请</li>
|
||||
<li>该页面同时提供其他软件协议的查看入口,方便您了解权利与义务</li>
|
||||
</ul>
|
||||
<p class="note">🔐 安全提示:应用外账户管理页面采用HMAC-SHA256回执验证机制,防止账号枚举攻击。查询账号状态需提供有效的回执签名。</p>
|
||||
<h2>五、账号冻结与解冻</h2>
|
||||
<h3>5.1 冻结情形</h3>
|
||||
<p>以下情形我们有权冻结您的账号:</p>
|
||||
@@ -387,38 +425,109 @@
|
||||
</div>
|
||||
<div id="content-en" class="lang-content" style="display:none;">
|
||||
<p><strong>Xianyan APP</strong> Account Usage Agreement</p>
|
||||
<p>Version: V6.5</p>
|
||||
<p>Updated: May 20, 2026</p>
|
||||
<p>Effective: May 21, 2026</p>
|
||||
<p>Version: V6.6</p>
|
||||
<p>Updated: June 17, 2026</p>
|
||||
<p>Effective: June 17, 2026</p>
|
||||
<p>This agreement is between you and <strong>Mile City Pengpu Town Weifengbao Network Technology Studio</strong> regarding the registration, use, security, and deletion of your Xianyan APP account. Please read and fully understand this agreement.</p>
|
||||
<h2>I. Account Registration</h2>
|
||||
<h3>1.1 You must be at least 14 years old to register an account</h3>
|
||||
<h3>1.2 You need to provide a valid phone number or email for registration</h3>
|
||||
<h3>1.3 The information you provide must be true, accurate, and complete</h3>
|
||||
<h3>1.4 Each person may only register one account</h3>
|
||||
<h3>1.1 Registration Eligibility</h3>
|
||||
<ul>
|
||||
<li>You must meet the minimum age requirement of your region (14 in China, 16 in EU, 13 in US)</li>
|
||||
<li>Minors under 14 are not allowed to register</li>
|
||||
<li>Each user may only register one account; accounts cannot be bought, transferred, or lent</li>
|
||||
</ul>
|
||||
<h3>1.2 Registration Methods</h3>
|
||||
<ul>
|
||||
<li>Phone number registration via SMS verification</li>
|
||||
<li>Email registration via verification code</li>
|
||||
<li>Third-party login via authorized accounts</li>
|
||||
</ul>
|
||||
<h2>II. Account Security</h2>
|
||||
<h3>2.1 You are responsible for keeping your account password secure</h3>
|
||||
<h3>2.2 Do not use easily guessable passwords</h3>
|
||||
<h3>2.3 If you discover your account has been used without authorization, please contact us immediately</h3>
|
||||
<h3>2.4 We are not responsible for losses caused by your failure to keep your account secure</h3>
|
||||
<h3>2.1 Password Security</h3>
|
||||
<ul>
|
||||
<li>Set a strong password and change it regularly</li>
|
||||
<li>Never share your password with others</li>
|
||||
<li>Notify us immediately if you suspect unauthorized access</li>
|
||||
</ul>
|
||||
<h2>III. Account Usage</h2>
|
||||
<h3>3.1 You may not lend, transfer, or sell your account</h3>
|
||||
<h3>3.2 You may not use your account for illegal activities</h3>
|
||||
<h3>3.3 You may not use technical means to obtain other users' account information</h3>
|
||||
<h3>3.1 Account Restrictions</h3>
|
||||
<p>We may restrict your account in the following cases:</p>
|
||||
<ul>
|
||||
<li>Security risk detected</li>
|
||||
<li>Violation of User Service Agreement</li>
|
||||
<li>Suspected illegal activities</li>
|
||||
<li>Inactive for over 2 years</li>
|
||||
</ul>
|
||||
<h3>3.2 Long-term Inactive Account Recycling Policy</h3>
|
||||
<p>To optimize server resources and protect account security, we apply the following recycling rules:</p>
|
||||
<ul>
|
||||
<li><span class="highlight">12 months inactive</span>: Account enters "dormant state"; we will notify you via app, email, or SMS</li>
|
||||
<li><span class="highlight">18 months inactive</span>: Virtual assets stop accumulating but are not cleared; account can still be reactivated</li>
|
||||
<li><span class="highlight">24 months (2 years) inactive</span>: Account will be <span class="highlight">recycled</span>:</li>
|
||||
<li>— Account permanently frozen, login disabled</li>
|
||||
<li>— Personal data, favorites, notes, check-ins <span class="highlight">permanently deleted</span></li>
|
||||
<li>— Linked email/phone unbound, available for new registrations</li>
|
||||
<li>— Final notification sent 30 days before recycling</li>
|
||||
<li>To avoid recycling, please log in at least once every <span class="highlight">12 months</span></li>
|
||||
</ul>
|
||||
<h2>IV. Account Deletion</h2>
|
||||
<h3>4.1 You can apply for account deletion at: My → Settings → Account Settings → Delete Account</h3>
|
||||
<h3>4.2 Deletion process:</h3>
|
||||
<h3>4.1 Deletion Conditions</h3>
|
||||
<p>You may apply for account deletion at any time. Before deletion, please ensure:</p>
|
||||
<ul>
|
||||
<li>Submit a deletion request and undergo security verification</li>
|
||||
<li>3-day review period (countdown displayed)</li>
|
||||
<li>During the review period, you can cancel the deletion request</li>
|
||||
<li>After the review is complete, the account and related data will be permanently deleted</li>
|
||||
<li>No pending disputes or complaints</li>
|
||||
<li>No incomplete transactions or services</li>
|
||||
<li>You have backed up data you wish to keep</li>
|
||||
</ul>
|
||||
<h3>4.3 After account deletion:</h3>
|
||||
<h3>4.2 Deletion Process</h3>
|
||||
<ul>
|
||||
<li>All personal information will be deleted or anonymized</li>
|
||||
<li>Published content will be handled according to your choice</li>
|
||||
<li>Account cannot be recovered after deletion</li>
|
||||
<li>Apply via "Settings → Account Settings → Delete Account"</li>
|
||||
<li>Step 1: Read deletion warning and confirm understanding</li>
|
||||
<li>Step 2: Security verification - type "DELETE" to confirm; reason is optional</li>
|
||||
<li>Step 3: Enter a <span class="highlight">3-day review period</span></li>
|
||||
<li>Step 4: After admin approval or review timeout, account is <span class="highlight">permanently deleted</span></li>
|
||||
<li>You may cancel the deletion request anytime during the review period</li>
|
||||
</ul>
|
||||
<h3>4.3 Deletion Status Query</h3>
|
||||
<ul>
|
||||
<li>View current status (Pending/Approved/Rejected/Cancelled) on the deletion page</li>
|
||||
<li>Application time, reason, and estimated auto-deletion time are displayed</li>
|
||||
<li>Countdown timer is shown during the review period</li>
|
||||
</ul>
|
||||
<h3>4.4 Deletion Consequences</h3>
|
||||
<ul>
|
||||
<li>Account cannot be logged in or used after deletion</li>
|
||||
<li>The following data will be <span class="highlight">permanently deleted and unrecoverable</span>:</li>
|
||||
<li>All favorites and notes</li>
|
||||
<li>Check-in records and points</li>
|
||||
<li>Personal profile and settings</li>
|
||||
<li>Articles and interaction data</li>
|
||||
<li>Membership benefits, points, and coins cleared</li>
|
||||
<li>Deletion is <span class="highlight">irreversible</span> - proceed with caution</li>
|
||||
</ul>
|
||||
<h3>4.5 Server Data Deletion Timeline</h3>
|
||||
<p>We commit to the following data deletion rules:</p>
|
||||
<ul>
|
||||
<li><span class="highlight">Immediate deletion upon admin approval</span>: Within <span class="highlight">5 minutes</span> of approval, the following data is hard-deleted:</li>
|
||||
<li>— User main table (tool_user) record</li>
|
||||
<li>— User tokens, login devices, online status</li>
|
||||
<li>— User favorites, notes, likes</li>
|
||||
<li>— Check-in records, points/coins transactions</li>
|
||||
<li>— User articles, comments (author field anonymized)</li>
|
||||
<li><span class="highlight">Auto-deletion on timeout</span>: If admin does not review within <span class="highlight">3 days</span>, the system auto-executes deletion</li>
|
||||
<li><span class="highlight">Deletion record retention</span>: Deletion operation records are retained for <span class="highlight">6 months</span>, with username immediately SHA256-hashed upon deletion completion. Only irreversible hash values are retained for audit purposes</li>
|
||||
<li>Database backups follow a <span class="highlight">30-day rolling overwrite</span> policy</li>
|
||||
</ul>
|
||||
<p class="note">💡 Important: After deletion is complete, the account cannot be queried via any channel (app, web, customer service). Status query will return "Deleted" or "No Record".</p>
|
||||
<h3>4.6 Out-of-App Account Management (Google Play Compliance)</h3>
|
||||
<p>To comply with Google Play Data Safety requirements, we provide an independent <span class="highlight">out-of-app account management webpage</span> for managing accounts without installing the app:</p>
|
||||
<ul>
|
||||
<li><span class="highlight">Account Status Query</span>: Visit <a href="https://tools.wktyl.com/agreements/privacy-rights.html" style="color:var(--primary);">Privacy Rights Management Page</a> to query status (Normal/Blocked/Deleting/Deleted/No Record)</li>
|
||||
<li><span class="highlight">Out-of-app deletion</span>: Log in and submit deletion requests via the privacy rights page</li>
|
||||
<li><span class="highlight">Deletion status query</span>: Track deletion progress anytime</li>
|
||||
<li><span class="highlight">Cancel deletion</span>: Cancel deletion requests during the review period</li>
|
||||
<li>The page also provides access to other software agreements</li>
|
||||
</ul>
|
||||
<p class="note">🔐 Security: The out-of-app management page uses HMAC-SHA256 receipt verification to prevent account enumeration attacks.</p>
|
||||
<h2>V. Account Freeze</h2>
|
||||
<h3>5.1 We have the right to freeze accounts in the following circumstances:</h3>
|
||||
<ul>
|
||||
|
||||
@@ -194,6 +194,14 @@
|
||||
</div>
|
||||
<span class="item-arrow">›</span>
|
||||
</a>
|
||||
<a href="privacy-rights.html" class="agreement-item">
|
||||
<span class="item-icon">🛡️</span>
|
||||
<div class="item-text">
|
||||
<div class="item-title">隐私权管理</div>
|
||||
<div class="item-subtitle">账号状态查询、注销申请、隐私权利选择(Google Play合规)</div>
|
||||
</div>
|
||||
<span class="item-arrow">›</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="section-title">📋 服务条款</div>
|
||||
<div class="agreement-list">
|
||||
@@ -293,6 +301,14 @@
|
||||
</div>
|
||||
<span class="item-arrow">›</span>
|
||||
</a>
|
||||
<a href="privacy-rights.html?lang=en" class="agreement-item">
|
||||
<span class="item-icon">🛡️</span>
|
||||
<div class="item-text">
|
||||
<div class="item-title">Privacy Rights Management</div>
|
||||
<div class="item-subtitle">Account status query, deletion request, privacy choices (Google Play compliance)</div>
|
||||
</div>
|
||||
<span class="item-arrow">›</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="section-title">📋 Service Terms</div>
|
||||
<div class="agreement-list">
|
||||
|
||||
2687
docs/toolsapi/public/agreements/privacy-rights.html
Normal file
2687
docs/toolsapi/public/agreements/privacy-rights.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user