fix: v3.0.1 - 修复5个代码问题
修复内容: 1. Collection 模型唯一约束逻辑错误 - 修改约束从 (user_id, site_id, folder_id) 到 (user_id, site_id) - 防止用户多次收藏同一网站 2. 用户注册重复提交数据库 - 优化为只提交一次数据库操作 - 提升注册性能 3. JavaScript 未使用的变量 - 删除 updateCollectButton() 中未使用的 icon 变量 4. 文件夹计数逻辑缺失 - 为每个文件夹添加收藏数量计算 - 修复收藏列表页面显示 5. JavaScript 错误处理不完善 - 所有 fetch 调用添加 HTTP 状态码检查 - 改进网络错误提示 新增文件: - fix_collection_constraint.py - 数据库约束修复脚本 - BUGFIX_v3.0.1.md - 详细修复记录 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
171
BUGFIX_v3.0.1.md
Normal file
171
BUGFIX_v3.0.1.md
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
# v3.0 代码修复记录
|
||||||
|
|
||||||
|
**修复日期:** 2025-02-06
|
||||||
|
**修复版本:** v3.0.1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修复的问题
|
||||||
|
|
||||||
|
### 1. Collection 模型唯一约束逻辑错误 ✅
|
||||||
|
|
||||||
|
**问题描述:**
|
||||||
|
- 原约束:`(user_id, site_id, folder_id)`
|
||||||
|
- 由于 `folder_id` 可为 NULL,导致用户可以多次收藏同一网站到"未分类"
|
||||||
|
|
||||||
|
**修复方案:**
|
||||||
|
- 新约束:`(user_id, site_id)`
|
||||||
|
- 确保每个用户只能收藏一次同一个网站
|
||||||
|
|
||||||
|
**修改文件:**
|
||||||
|
- `models.py` (第 262-265 行)
|
||||||
|
|
||||||
|
**数据库迁移:**
|
||||||
|
- 新增 `fix_collection_constraint.py` 脚本用于修复现有数据库
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 用户注册重复提交数据库 ✅
|
||||||
|
|
||||||
|
**问题描述:**
|
||||||
|
- 注册时先提交用户数据,登录后再次提交 `last_login`
|
||||||
|
- 造成不必要的数据库操作
|
||||||
|
|
||||||
|
**修复方案:**
|
||||||
|
- 在第一次提交前设置 `last_login`
|
||||||
|
- 只提交一次数据库
|
||||||
|
|
||||||
|
**修改文件:**
|
||||||
|
- `app.py` (第 643-651 行)
|
||||||
|
|
||||||
|
**修复前:**
|
||||||
|
```python
|
||||||
|
user = User(username=username)
|
||||||
|
user.set_password(password)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit() # 第一次
|
||||||
|
|
||||||
|
login_user(user)
|
||||||
|
user.last_login = datetime.now()
|
||||||
|
db.session.commit() # 第二次
|
||||||
|
```
|
||||||
|
|
||||||
|
**修复后:**
|
||||||
|
```python
|
||||||
|
user = User(username=username)
|
||||||
|
user.set_password(password)
|
||||||
|
user.last_login = datetime.now()
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit() # 只提交一次
|
||||||
|
|
||||||
|
login_user(user)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. JavaScript 未使用的变量 ✅
|
||||||
|
|
||||||
|
**问题描述:**
|
||||||
|
- `updateCollectButton()` 函数中获取了 `icon` 元素但未使用
|
||||||
|
|
||||||
|
**修复方案:**
|
||||||
|
- 删除未使用的变量声明
|
||||||
|
|
||||||
|
**修改文件:**
|
||||||
|
- `templates/detail_new.html` (第 1599-1611 行)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 文件夹计数逻辑缺失 ✅
|
||||||
|
|
||||||
|
**问题描述:**
|
||||||
|
- 模板中使用 `folder.count` 但后端未计算
|
||||||
|
- 导致文件夹标签显示的数量不正确
|
||||||
|
|
||||||
|
**修复方案:**
|
||||||
|
- 在 `user_collections()` 路由中为每个文件夹计算收藏数量
|
||||||
|
|
||||||
|
**修改文件:**
|
||||||
|
- `app.py` (第 1141-1144 行)
|
||||||
|
|
||||||
|
**新增代码:**
|
||||||
|
```python
|
||||||
|
# 为每个文件夹添加收藏计数
|
||||||
|
for folder in folders:
|
||||||
|
folder.count = Collection.query.filter_by(
|
||||||
|
user_id=current_user.id,
|
||||||
|
folder_id=folder.id
|
||||||
|
).count()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. JavaScript 错误处理不完善 ✅
|
||||||
|
|
||||||
|
**问题描述:**
|
||||||
|
- Fetch API 未检查 HTTP 状态码
|
||||||
|
- 如果服务器返回 500 错误,`.json()` 可能失败但不会被捕获
|
||||||
|
|
||||||
|
**修复方案:**
|
||||||
|
- 在所有 fetch 调用中添加状态码检查
|
||||||
|
|
||||||
|
**修改文件:**
|
||||||
|
- `templates/detail_new.html` (第 1579-1662 行)
|
||||||
|
|
||||||
|
**修复模式:**
|
||||||
|
```javascript
|
||||||
|
fetch('/api/...')
|
||||||
|
.then(r => {
|
||||||
|
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
|
.then(data => { ... })
|
||||||
|
.catch(err => { ... });
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修改的文件清单
|
||||||
|
|
||||||
|
1. **models.py** - 修复 Collection 唯一约束
|
||||||
|
2. **app.py** - 修复注册重复提交 + 添加文件夹计数
|
||||||
|
3. **templates/detail_new.html** - 删除未使用变量 + 改进错误处理
|
||||||
|
4. **fix_collection_constraint.py** (新增) - 数据库约束修复脚本
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 部署说明
|
||||||
|
|
||||||
|
### 对于新部署(未运行过 create_user_tables.py)
|
||||||
|
|
||||||
|
直接运行:
|
||||||
|
```bash
|
||||||
|
python create_user_tables.py
|
||||||
|
```
|
||||||
|
|
||||||
|
新的约束会自动生效。
|
||||||
|
|
||||||
|
### 对于已部署的环境(已有数据)
|
||||||
|
|
||||||
|
需要运行修复脚本:
|
||||||
|
```bash
|
||||||
|
python fix_collection_constraint.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意:** 如果数据库中已存在重复收藏(同一用户多次收藏同一网站),修复脚本会失败。需要先清理重复数据。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 测试建议
|
||||||
|
|
||||||
|
修复后请测试:
|
||||||
|
|
||||||
|
1. **收藏功能** - 尝试多次收藏同一网站,应该提示"已收藏"
|
||||||
|
2. **文件夹计数** - 访问收藏列表,文件夹标签应显示正确的数量
|
||||||
|
3. **错误处理** - 断网情况下点击收藏,应显示"网络请求失败"
|
||||||
|
4. **注册流程** - 注册新用户,检查数据库只有一条记录
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**修复版本:** v3.0.1
|
||||||
|
**修复人员:** Claude Sonnet 4.5
|
||||||
10
app.py
10
app.py
@@ -642,13 +642,12 @@ def create_app(config_name='default'):
|
|||||||
try:
|
try:
|
||||||
user = User(username=username)
|
user = User(username=username)
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
|
user.last_login = datetime.now()
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# 自动登录
|
# 自动登录
|
||||||
login_user(user)
|
login_user(user)
|
||||||
user.last_login = datetime.now()
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
flash('注册成功!', 'success')
|
flash('注册成功!', 'success')
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
@@ -1144,6 +1143,13 @@ def create_app(config_name='default'):
|
|||||||
Folder.sort_order.desc(), Folder.created_at
|
Folder.sort_order.desc(), Folder.created_at
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
|
# 为每个文件夹添加收藏计数
|
||||||
|
for folder in folders:
|
||||||
|
folder.count = Collection.query.filter_by(
|
||||||
|
user_id=current_user.id,
|
||||||
|
folder_id=folder.id
|
||||||
|
).count()
|
||||||
|
|
||||||
# 获取收藏(分页)
|
# 获取收藏(分页)
|
||||||
page = request.args.get('page', 1, type=int)
|
page = request.args.get('page', 1, type=int)
|
||||||
folder_id = request.args.get('folder_id')
|
folder_id = request.args.get('folder_id')
|
||||||
|
|||||||
48
fix_collection_constraint.py
Normal file
48
fix_collection_constraint.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
"""
|
||||||
|
数据库修复脚本:修复 Collection 表的唯一约束
|
||||||
|
运行方式:python fix_collection_constraint.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from app import create_app
|
||||||
|
from models import db
|
||||||
|
|
||||||
|
def fix_collection_constraint():
|
||||||
|
"""修复 Collection 表的唯一约束"""
|
||||||
|
app = create_app(os.getenv('FLASK_ENV', 'development'))
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
print("开始修复 Collection 表的唯一约束...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 删除旧的唯一约束
|
||||||
|
print("1. 删除旧的唯一约束 unique_user_site_folder...")
|
||||||
|
db.engine.execute(
|
||||||
|
"ALTER TABLE collections DROP INDEX unique_user_site_folder"
|
||||||
|
)
|
||||||
|
print(" ✓ 旧约束已删除")
|
||||||
|
|
||||||
|
# 添加新的唯一约束
|
||||||
|
print("2. 添加新的唯一约束 unique_user_site...")
|
||||||
|
db.engine.execute(
|
||||||
|
"ALTER TABLE collections ADD CONSTRAINT unique_user_site UNIQUE (user_id, site_id)"
|
||||||
|
)
|
||||||
|
print(" ✓ 新约束已添加")
|
||||||
|
|
||||||
|
print("\n✅ 修复完成!")
|
||||||
|
print("\n说明:")
|
||||||
|
print("- 旧约束:(user_id, site_id, folder_id) - 允许重复收藏到未分类")
|
||||||
|
print("- 新约束:(user_id, site_id) - 每个用户只能收藏一次同一个网站")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ 修复失败: {str(e)}")
|
||||||
|
print("\n可能的原因:")
|
||||||
|
print("1. 约束名称不匹配(MySQL/SQLite 差异)")
|
||||||
|
print("2. 数据库中已存在重复数据")
|
||||||
|
print("3. 数据库权限不足")
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
fix_collection_constraint()
|
||||||
@@ -260,7 +260,7 @@ class Collection(db.Model):
|
|||||||
site = db.relationship('Site', backref='collections')
|
site = db.relationship('Site', backref='collections')
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
db.UniqueConstraint('user_id', 'site_id', 'folder_id', name='unique_user_site_folder'),
|
db.UniqueConstraint('user_id', 'site_id', name='unique_user_site'),
|
||||||
db.Index('idx_user_folder', 'user_id', 'folder_id'),
|
db.Index('idx_user_folder', 'user_id', 'folder_id'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1578,7 +1578,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
function checkCollectionStatus() {
|
function checkCollectionStatus() {
|
||||||
fetch('/api/auth/status')
|
fetch('/api/auth/status')
|
||||||
.then(r => r.json())
|
.then(r => {
|
||||||
|
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (!data.logged_in || data.user_type !== 'user') {
|
if (!data.logged_in || data.user_type !== 'user') {
|
||||||
return; // 未登录或非普通用户,不检查收藏状态
|
return; // 未登录或非普通用户,不检查收藏状态
|
||||||
@@ -1586,7 +1589,10 @@ function checkCollectionStatus() {
|
|||||||
|
|
||||||
// 已登录,检查收藏状态
|
// 已登录,检查收藏状态
|
||||||
fetch(`/api/collections/status/${siteCode}`)
|
fetch(`/api/collections/status/${siteCode}`)
|
||||||
.then(r => r.json())
|
.then(r => {
|
||||||
|
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
isCollected = data.is_collected;
|
isCollected = data.is_collected;
|
||||||
updateCollectButton();
|
updateCollectButton();
|
||||||
@@ -1598,7 +1604,6 @@ function checkCollectionStatus() {
|
|||||||
|
|
||||||
function updateCollectButton() {
|
function updateCollectButton() {
|
||||||
const btn = document.getElementById('collectBtn');
|
const btn = document.getElementById('collectBtn');
|
||||||
const icon = document.getElementById('collectIcon');
|
|
||||||
const text = document.getElementById('collectText');
|
const text = document.getElementById('collectText');
|
||||||
|
|
||||||
if (isCollected) {
|
if (isCollected) {
|
||||||
@@ -1613,7 +1618,10 @@ function updateCollectButton() {
|
|||||||
function toggleCollect() {
|
function toggleCollect() {
|
||||||
// 先检查登录状态
|
// 先检查登录状态
|
||||||
fetch('/api/auth/status')
|
fetch('/api/auth/status')
|
||||||
.then(r => r.json())
|
.then(r => {
|
||||||
|
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (!data.logged_in) {
|
if (!data.logged_in) {
|
||||||
// 未登录,跳转到登录页
|
// 未登录,跳转到登录页
|
||||||
@@ -1636,7 +1644,10 @@ function toggleCollect() {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({ site_code: siteCode })
|
body: JSON.stringify({ site_code: siteCode })
|
||||||
})
|
})
|
||||||
.then(r => r.json())
|
.then(r => {
|
||||||
|
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
isCollected = data.action === 'added';
|
isCollected = data.action === 'added';
|
||||||
|
|||||||
Reference in New Issue
Block a user