# 一、eggjs问答系统(控制器 /app/controller/api/chat/askanswer.js 完整代码)

'use strict';

const Controller = require('egg').Controller;
const fs = require('fs');
const path = require('path');

class AskanswerController extends Controller {
  // 获取文件路径
  getFilePath(type, createUserId) {
    const baseDir = this.config.baseDir;
    const dirPath = path.join(baseDir, 'data', 'askAnswer');
    
    // 确保目录存在
    if (!fs.existsSync(dirPath)) {
      fs.mkdirSync(dirPath, { recursive: true });
    }
    
    return path.join(dirPath, `chatuser_${type}_${createUserId}.json`);
  }
  
  // 读取文件数据
  readFileData(filePath) {
    if (!fs.existsSync(filePath)) {
      return [];
    }
    
    try {
      const data = fs.readFileSync(filePath, 'utf8');
      return JSON.parse(data) || [];
    } catch (error) {
      this.ctx.logger.error('读取问答文件失败:', error);
      return [];
    }
  }
  
  // 写入文件数据
  writeFileData(filePath, data) {
    try {
      fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
      return true;
    } catch (error) {
      this.ctx.logger.error('写入问答文件失败:', error);
      return false;
    }
  }
  
  // 搜索问答 - 新增方法
  async search() {
    const { ctx } = this;
    const { type, create_userId, ask } = ctx.request.body;
    
    if (!type || !create_userId || !ask) {
      ctx.body = {
        code: 400,
        msg: '参数不完整'
      };
      return;
    }
    
    try {
      const filePath = this.getFilePath(type, create_userId);
      const questions = this.readFileData(filePath);
      
      if (questions.length === 0) {
        ctx.body = {
          code: 404,
          msg: '暂无问答数据'
        };
        return;
      }
      
      // 模糊匹配算法
      const searchKeywords = ask.toLowerCase().split(/\s+/).filter(k => k.length > 0);
      let bestMatch = null;
      let highestScore = 0;
      
      // 计算匹配度评分
      questions.forEach(question => {
        let score = 0;
        const questionText = (question.ask + ' ' + question.answer).toLowerCase();
        
        // 1. 完全匹配 - 最高优先级
        if (question.ask.toLowerCase().includes(ask.toLowerCase()) || 
            question.answer.toLowerCase().includes(ask.toLowerCase())) {
          score += 100;
        }
        
        // 2. 关键词匹配
        searchKeywords.forEach(keyword => {
          // 问题中包含关键词
          if (question.ask.toLowerCase().includes(keyword)) {
            score += 10;
          }
          
          // 回答中包含关键词
          if (question.answer.toLowerCase().includes(keyword)) {
            score += 5;
          }
          
          // 计算关键词出现次数
          const askCount = (question.ask.toLowerCase().match(new RegExp(keyword, 'g')) || []).length;
          const answerCount = (question.answer.toLowerCase().match(new RegExp(keyword, 'g')) || []).length;
          score += (askCount * 2) + answerCount;
        });
        
        // 3. 长度相似度 - 问题长度越接近搜索词,得分越高
        const lengthDiff = Math.abs(question.ask.length - ask.length);
        score += Math.max(0, 10 - lengthDiff / 5);
        
        // 4. 时效性 - 较新的内容得分更高
        const daysSinceUpdate = (Date.now() - question.update_time) / (1000 * 60 * 60 * 24);
        score += Math.max(0, 5 - daysSinceUpdate / 10);
        
        // 5. 热门度 - 点赞多的内容得分更高
        score += Math.min(question.likeCount || 0, 10);
        
        // 更新最佳匹配
        if (score > highestScore) {
          highestScore = score;
          bestMatch = question;
        }
      });
      
      // 设置匹配阈值,避免低质量匹配
      const threshold = searchKeywords.length * 5;
      
      if (bestMatch && highestScore >= threshold) {
        ctx.body = {
          code: 200,
          data: bestMatch,
          score: highestScore, // 返回匹配度评分,便于前端调试
          msg: '搜索成功'
        };
      } else {
        // 如果没有找到合适匹配,尝试使用更宽松的匹配方式
        const fallbackMatch = this.fallbackSearch(questions, ask);
        if (fallbackMatch) {
          ctx.body = {
            code: 200,
            data: fallbackMatch,
            score: 5, // 较低的匹配度
            msg: '找到相关回答'
          };
        } else {
          ctx.body = {
            code: 404,
            msg: '未找到相关问答'
          };
        }
      }
    } catch (error) {
      ctx.logger.error('搜索问答失败:', error);
      ctx.body = {
        code: 500,
        msg: '搜索失败'
      };
    }
  }
  
  // 备用搜索方法 - 使用更宽松的匹配规则
  fallbackSearch(questions, ask) {
    const searchText = ask.toLowerCase();
    
    // 尝试部分匹配
    for (const question of questions) {
      const questionText = (question.ask + ' ' + question.answer).toLowerCase();
      
      // 检查是否包含搜索词的主要部分
      const words = searchText.split(/\s+/).filter(w => w.length > 2);
      let matchCount = 0;
      
      for (const word of words) {
        if (questionText.includes(word)) {
          matchCount++;
        }
      }
      
      // 如果超过一半的关键词匹配,认为是相关结果
      if (matchCount >= Math.ceil(words.length / 2)) {
        return question;
      }
    }
    
    // 尝试同义词匹配(简单实现)
    const synonymMap = {
      '怎么': '如何',
      '哪里': '何处',
      '什么': '啥',
      '为什么': '为何',
      '怎么办': '如何处理'
    };
    
    let synonymText = searchText;
    Object.keys(synonymMap).forEach(key => {
      synonymText = synonymText.replace(new RegExp(key, 'g'), synonymMap[key]);
    });
    
    if (synonymText !== searchText) {
      for (const question of questions) {
        const questionText = (question.ask + ' ' + question.answer).toLowerCase();
        if (questionText.includes(synonymText)) {
          return question;
        }
      }
    }
    
    return null;
  }
  
  // 创建问答
  async create() {
    const { ctx } = this;
    const { ask, answer, type, create_userId } = ctx.request.body;
    
    if (!ask || !answer || !type || !create_userId) {
      ctx.body = {
        code: 400,
        msg: '参数不完整'
      };
      return;
    }
    
    try {
      const filePath = this.getFilePath(type, create_userId);
      const questions = this.readFileData(filePath);
      
      // 检查是否已存在相同问题
      const existingIndex = questions.findIndex(q => q.ask === ask);
      const now = Date.now();
      
      if (existingIndex !== -1) {
        // 更新已存在的问题
        questions[existingIndex] = {
          ...questions[existingIndex],
          answer,
          update_time: now
        };
      } else {
        // 创建新问题
        const newQuestion = {
          id: now, // 使用时间戳作为ID
          ask,
          answer,
          type,
          create_userId,
          create_time: now,
          update_time: now,
          likeCount: 0,
          liked: false
        };
        
        questions.unshift(newQuestion);
      }
      
      // 写入文件
      const success = this.writeFileData(filePath, questions);
      
      if (success) {
        ctx.body = {
          code: 200,
          msg: existingIndex !== -1 ? '问答已更新' : '问答创建成功'
        };
      } else {
        ctx.body = {
          code: 500,
          msg: '保存失败'
        };
      }
    } catch (error) {
      ctx.logger.error('创建问答失败:', error);
      ctx.body = {
        code: 500,
        msg: '服务器错误'
      };
    }
  }
  
  // 更新问答
  async update() {
    const { ctx } = this;
    const { id, ask, answer, type, create_userId } = ctx.request.body;
    
    if (!id || !ask || !answer || !type || !create_userId) {
      ctx.body = {
        code: 400,
        msg: '参数不完整'
      };
      return;
    }
    
    try {
      const filePath = this.getFilePath(type, create_userId);
      const questions = this.readFileData(filePath);
      
      const questionIndex = questions.findIndex(q => q.id === id);
      
      if (questionIndex === -1) {
        ctx.body = {
          code: 404,
          msg: '问答不存在'
        };
        return;
      }
      
      // 更新问答
      questions[questionIndex] = {
        ...questions[questionIndex],
        ask,
        answer,
        update_time: Date.now()
      };
      
      // 写入文件
      const success = this.writeFileData(filePath, questions);
      
      if (success) {
        ctx.body = {
          code: 200,
          msg: '更新成功'
        };
      } else {
        ctx.body = {
          code: 500,
          msg: '更新失败'
        };
      }
    } catch (error) {
      ctx.logger.error('更新问答失败:', error);
      ctx.body = {
        code: 500,
        msg: '服务器错误'
      };
    }
  }
  
  // 删除问答
  async delete() {
    const { ctx } = this;
    const { id, type, create_userId } = ctx.request.body;
    
    if (!id) {
      ctx.body = {
        code: 400,
        msg: '参数不完整'
      };
      return;
    }
    
    try {
      // 如果没有提供type和create_userId,需要遍历所有可能的文件
      if (!type || !create_userId) {
        const baseDir = this.config.baseDir;
        const dirPath = path.join(baseDir, 'data', 'askAnswer');
        
        if (!fs.existsSync(dirPath)) {
          ctx.body = {
            code: 404,
            msg: '问答不存在'
          };
          return;
        }
        
        const files = fs.readdirSync(dirPath);
        let deleted = false;
        
        for (const file of files) {
          if (file.endsWith('.json')) {
            const filePath = path.join(dirPath, file);
            const questions = this.readFileData(filePath);
            const filteredQuestions = questions.filter(q => q.id !== id);
            
            if (filteredQuestions.length !== questions.length) {
              this.writeFileData(filePath, filteredQuestions);
              deleted = true;
              break;
            }
          }
        }
        
        if (deleted) {
          ctx.body = {
            code: 200,
            msg: '删除成功'
          };
        } else {
          ctx.body = {
            code: 404,
            msg: '问答不存在'
          };
        }
      } else {
        // 直接定位到特定文件
        const filePath = this.getFilePath(type, create_userId);
        
        if (!fs.existsSync(filePath)) {
          ctx.body = {
            code: 404,
            msg: '问答不存在'
          };
          return;
        }
        
        const questions = this.readFileData(filePath);
        const filteredQuestions = questions.filter(q => q.id !== id);
        
        if (filteredQuestions.length === questions.length) {
          ctx.body = {
            code: 404,
            msg: '问答不存在'
          };
          return;
        }
        
        const success = this.writeFileData(filePath, filteredQuestions);
        
        if (success) {
          ctx.body = {
            code: 200,
            msg: '删除成功'
          };
        } else {
          ctx.body = {
            code: 500,
            msg: '删除失败'
          };
        }
      }
    } catch (error) {
      ctx.logger.error('删除问答失败:', error);
      ctx.body = {
        code: 500,
        msg: '服务器错误'
      };
    }
  }
  
  // 获取问答列表
  async list() {
    const { ctx } = this;
    const { type, userId } = ctx.request.body;
    
    if (!type || !userId) {
      ctx.body = {
        code: 400,
        msg: '参数不完整'
      };
      return;
    }
    
    try {
      const filePath = this.getFilePath(type, userId);
      const questions = this.readFileData(filePath);
      
      ctx.body = {
        code: 200,
        data: questions,
        msg: '获取成功'
      };
    } catch (error) {
      ctx.logger.error('获取问答列表失败:', error);
      ctx.body = {
        code: 500,
        msg: '服务器错误'
      };
    }
  }
  
  // 获取问答详情
  async detail() {
    const { ctx } = this;
    const { id, type, create_userId } = ctx.request.body;
    
    if (!id) {
      ctx.body = {
        code: 400,
        msg: '参数不完整'
      };
      return;
    }
    
    try {
      let question = null;
      
      // 如果没有提供type和create_userId,需要遍历所有可能的文件
      if (!type || !create_userId) {
        const baseDir = this.config.baseDir;
        const dirPath = path.join(baseDir, 'data', 'askAnswer');
        
        if (fs.existsSync(dirPath)) {
          const files = fs.readdirSync(dirPath);
          
          for (const file of files) {
            if (file.endsWith('.json')) {
              const filePath = path.join(dirPath, file);
              const questions = this.readFileData(filePath);
              question = questions.find(q => q.id === id);
              
              if (question) break;
            }
          }
        }
      } else {
        // 直接定位到特定文件
        const filePath = this.getFilePath(type, create_userId);
        
        if (fs.existsSync(filePath)) {
          const questions = this.readFileData(filePath);
          question = questions.find(q => q.id === id);
        }
      }
      
      if (question) {
        ctx.body = {
          code: 200,
          data: question,
          msg: '获取成功'
        };
      } else {
        ctx.body = {
          code: 404,
          msg: '问答不存在'
        };
      }
    } catch (error) {
      ctx.logger.error('获取问答详情失败:', error);
      ctx.body = {
        code: 500,
        msg: '服务器错误'
      };
    }
  }
}

module.exports = AskanswerController;
更新时间: 2025年9月27日星期六晚上10点22分