Skip to content

积分规则管理系统修复

概述

本文档记录了积分规则管理系统的重大修复,解决了域管理员无法创建积分规则和前端显示问题。

修复时间

修复日期: 2025年9月3日

问题描述

主要问题

  1. 域管理员无法创建积分规则

    • 无论输入任何规则名称,都提示"规则名称已存在"
    • 导致域管理员无法创建新的积分规则
  2. 前端页面显示问题

    • 积分规则管理弹窗显示"演示积分规则数据"
    • 无法显示真实的积分规则数据
  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. 前端功能验证

  1. 登录域管理员账户 (test6/123456)
  2. 打开积分规则管理弹窗
  3. 验证规则列表显示 - 应该显示4条积分规则
  4. 测试创建新规则 - 应该能成功创建
  5. 测试编辑规则 - 应该能正常编辑
  6. 测试删除规则 - 应该能正常删除

3. 积分计算验证

  1. 创建测试项目 - 配置项目完成积分
  2. 提交学习记录 - 选择评级
  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: '规则名称已存在' };
}

影响范围

正面影响

  1. 域管理员功能完善 - 可以正常管理积分规则
  2. 数据隔离加强 - 确保域管理员只能管理自己域的数据
  3. 积分计算准确 - 项目完成积分计算正确
  4. 用户体验提升 - 前端界面正常显示和操作

兼容性

  • 向后兼容 - 不影响现有数据和功能
  • 数据库兼容 - 迁移脚本安全可靠
  • API兼容 - 新增路由不影响现有API

后续优化建议

  1. 添加规则模板 - 为域管理员提供常用规则模板
  2. 批量操作 - 支持批量创建、编辑、删除规则
  3. 规则导入导出 - 支持规则的导入导出功能
  4. 规则验证 - 添加更严格的规则配置验证
  5. 操作日志 - 记录积分规则的操作日志

相关文件

修改的文件

  • 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.md
  • docs/features/corrected-points-rules.md
  • docs/features/consistent-points-rules.md

总结

本次修复解决了积分规则管理系统的核心问题,确保了:

  1. 数据隔离 - 域管理员只能管理自己域的数据
  2. 功能完整 - 积分规则的CRUD操作完全正常
  3. 计算准确 - 项目完成积分计算正确
  4. 用户体验 - 前端界面正常显示和操作

修复后的系统更加稳定、安全和易用,为域管理员提供了完整的积分规则管理功能。

Released under the MIT License.