积分规则管理系统修复
概述
本文档记录了积分规则管理系统的重大修复,解决了域管理员无法创建积分规则和前端显示问题。
修复时间
修复日期: 2025年9月3日
问题描述
主要问题
域管理员无法创建积分规则
- 无论输入任何规则名称,都提示"规则名称已存在"
- 导致域管理员无法创建新的积分规则
前端页面显示问题
- 积分规则管理弹窗显示"演示积分规则数据"
- 无法显示真实的积分规则数据
项目完成积分计算问题
- 部分项目只有评级积分,没有项目完成积分
- 项目完成积分配置正确但未正确计算
根本原因分析
1. 数据库约束问题
问题: 数据库中存在全局唯一约束 points_rules_name_unique,与数据隔离机制冲突。
影响:
- 不同管理域无法创建相同名称的积分规则
- 违反了域管理员数据隔离的设计原则
2. 前端认证问题
问题: admin-category-rules.js 中的API调用缺少 Authorization 头部。
影响:
- 前端无法正确认证,导致API返回空数据
- 积分规则创建、编辑、删除功能失效
3. 积分计算逻辑问题
问题: checkProjectCompletionCondition 方法中错误地从 ratingPoints[rating] 获取完成积分。
影响:
- 项目完成积分计算错误
- 用户无法获得正确的项目完成奖励
修复方案
1. 数据库约束修复
创建新的数据库迁移
javascript
// database/migrations/20250903091238_fix_points_rules_unique_constraint.js
exports.up = async function(knex) {
console.log('🔄 开始修复积分规则唯一约束...');
// 删除旧的全局唯一约束
await knex.schema.alterTable('points_rules', function(table) {
table.dropUnique('name', 'points_rules_name_unique');
});
// 添加新的复合唯一约束
await knex.schema.alterTable('points_rules', function(table) {
table.unique(['name', 'admin_domain'], 'points_rules_name_domain_unique');
});
console.log('✅ 积分规则唯一约束修复完成');
};更新错误处理逻辑
javascript
// services/points-exchange.js
if (error.code === '23505' && error.constraint === 'points_rules_name_domain_unique') {
return { success: false, error: '规则名称已存在' };
}2. 前端认证修复
修复 loadData 方法
javascript
// assets/js/admin-category-rules.js
async loadData() {
try {
// 获取认证token
const token = localStorage.getItem('token');
const headers = {
'Content-Type': 'application/json'
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
// 加载积分规则数据
const rulesResponse = await fetch(window.isDemo ? '/demo/api/points-exchange/admin/points-rules' : getApiUrl('/api/points-exchange/admin/points-rules'), {
cache: 'no-cache',
credentials: 'include',
headers: headers
});
// ... 其他代码
} catch (error) {
logger.error('加载数据失败:', error);
this.showNotification('加载数据失败', 'error');
}
}修复 saveRule 方法
javascript
// assets/js/admin-category-rules.js
async saveRule() {
// ... 其他代码
// 获取认证token
const token = localStorage.getItem('token');
const headers = {
'Content-Type': 'application/json'
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(url, {
method,
headers: headers,
credentials: 'include',
body: JSON.stringify(ruleData)
});
// ... 其他代码
}修复 deleteRule 方法
javascript
// assets/js/admin-category-rules.js
async deleteRule(ruleId) {
// ... 其他代码
// 获取认证token
const token = localStorage.getItem('token');
const headers = {
'Content-Type': 'application/json'
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(window.isDemo ? `/demo/api/points-exchange/admin/points-rules/${ruleId}` : getApiUrl(`/api/points-exchange/admin/points-rules/${ruleId}`), {
method: 'DELETE',
headers: headers,
credentials: 'include'
});
// ... 其他代码
}3. 积分计算逻辑修复
修复项目完成积分计算
javascript
// services/points-exchange.js
async checkProjectCompletionCondition(userId, projectId, rating) {
try {
// 获取项目信息
const project = await db('study_projects')
.where('id', projectId)
.first();
if (!project) return { success: false, error: '项目不存在' };
// 获取评级标准
const ratingStandards = project.rating_standards;
if (!ratingStandards) return { success: false, error: '项目未配置评级标准' };
// 修复:从 rating_standards.completion_points 获取完成积分
const completionPoints = ratingStandards.completion_points || 0;
if (completionPoints > 0) {
// 检查是否已经获得过项目完成积分
const existingRecord = await db('points_records')
.where({
user_id: userId,
project_id: projectId,
reason: 'project_completion'
})
.first();
if (existingRecord) {
return { success: false, error: '已获得过项目完成积分' };
}
return { success: true, points: completionPoints, rating };
}
return { success: false, error: '项目未配置完成积分' };
} catch (error) {
logger.error('检查项目完成条件失败:', error);
return { success: false, error: '检查失败' };
}
}4. 添加管理端API路由
添加缺失的管理端路由
javascript
// routes/points-exchange.js
// 获取所有积分规则(管理端)
router.get('/admin/points-rules', authenticateToken, requireAdmin, async (req, res) => {
if (isDemoApi(req)) {
return res.json({
success: true,
data: [
{ id: 1, name: '每日打卡', description: '每日学习打卡奖励', points: 10, is_active: true, created_at: new Date(), updated_at: new Date() },
{ id: 2, name: '完成项目', description: '完成学习项目奖励', points: 50, is_active: true, created_at: new Date(), updated_at: new Date() }
]
});
}
const rules = await pointsExchangeService.getPointsRules(req.user.id);
res.json({ success: true, data: rules });
});
// 新建积分规则(管理端)
router.post('/admin/points-rules', authenticateToken, requireAdmin, async (req, res) => {
try {
const result = await pointsExchangeService.createPointsRule(req.body, req.user.id);
if (!result.success) return res.status(400).json({ success: false, error: result.error });
res.json({ success: true, data: result });
} catch (error) {
console.error('创建积分规则失败:', error);
// 检查是否是数据库唯一约束错误
if (error.code === '23505' && error.constraint === 'points_rules_name_domain_unique') {
return res.status(400).json({ success: false, error: '规则名称已存在' });
}
res.status(500).json({ success: false, error: '创建积分规则失败' });
}
});
// 更新积分规则(管理端)
router.put('/admin/points-rules/:id', authenticateToken, requireAdmin, async (req, res) => {
try {
const result = await pointsExchangeService.updatePointsRule(req.params.id, req.body);
if (!result.success) return res.status(400).json({ success: false, error: result.error });
res.json({ success: true });
} catch (error) {
console.error('更新积分规则失败:', error);
// 检查是否是数据库唯一约束错误
if (error.code === '23505' && error.constraint === 'points_rules_name_domain_unique') {
return res.status(400).json({ success: false, error: '规则名称已存在' });
}
res.status(500).json({ success: false, error: '更新积分规则失败' });
}
});
// 删除积分规则(管理端)
router.delete('/admin/points-rules/:id', authenticateToken, requireAdmin, async (req, res) => {
const result = await pointsExchangeService.deletePointsRule(req.params.id);
if (!result.success) return res.status(400).json({ success: false, error: result.error });
res.json({ success: true });
});修复验证
1. 数据库约束验证
bash
# 运行数据库迁移
npm run db:migrate
# 验证约束是否正确创建
psql -d study_tracker_dev -c "\d points_rules"2. 前端功能验证
- 登录域管理员账户 (test6/123456)
- 打开积分规则管理弹窗
- 验证规则列表显示 - 应该显示4条积分规则
- 测试创建新规则 - 应该能成功创建
- 测试编辑规则 - 应该能正常编辑
- 测试删除规则 - 应该能正常删除
3. 积分计算验证
- 创建测试项目 - 配置项目完成积分
- 提交学习记录 - 选择评级
- 验证积分记录 - 应该同时获得评级积分和完成积分
技术细节
数据隔离机制
系统使用 admin_domain 字段实现数据隔离:
sql
-- 新的复合唯一约束
ALTER TABLE points_rules
ADD CONSTRAINT points_rules_name_domain_unique
UNIQUE (name, admin_domain);认证机制
前端使用 JWT Token 进行认证:
javascript
// 获取token
const token = localStorage.getItem('token');
// 添加到请求头
headers['Authorization'] = `Bearer ${token}`;错误处理
统一的错误处理机制:
javascript
// 数据库约束错误
if (error.code === '23505' && error.constraint === 'points_rules_name_domain_unique') {
return { success: false, error: '规则名称已存在' };
}影响范围
正面影响
- 域管理员功能完善 - 可以正常管理积分规则
- 数据隔离加强 - 确保域管理员只能管理自己域的数据
- 积分计算准确 - 项目完成积分计算正确
- 用户体验提升 - 前端界面正常显示和操作
兼容性
- 向后兼容 - 不影响现有数据和功能
- 数据库兼容 - 迁移脚本安全可靠
- API兼容 - 新增路由不影响现有API
后续优化建议
- 添加规则模板 - 为域管理员提供常用规则模板
- 批量操作 - 支持批量创建、编辑、删除规则
- 规则导入导出 - 支持规则的导入导出功能
- 规则验证 - 添加更严格的规则配置验证
- 操作日志 - 记录积分规则的操作日志
相关文件
修改的文件
database/migrations/20250903091238_fix_points_rules_unique_constraint.js(新增)services/points-exchange.js(修改)routes/points-exchange.js(修改)assets/js/admin-category-rules.js(修改)
相关文档
docs/features/dynamic-points-rules.mddocs/features/corrected-points-rules.mddocs/features/consistent-points-rules.md
总结
本次修复解决了积分规则管理系统的核心问题,确保了:
- 数据隔离 - 域管理员只能管理自己域的数据
- 功能完整 - 积分规则的CRUD操作完全正常
- 计算准确 - 项目完成积分计算正确
- 用户体验 - 前端界面正常显示和操作
修复后的系统更加稳定、安全和易用,为域管理员提供了完整的积分规则管理功能。