当前位置: 首页 > news >正文

nodejs:express + js-mdict 作为后端,vue 3 + vite 作为前端,在线查询英汉词典

向 doubao.com/chat/ 提问:
node.js + js-mdict 作为后端,vue 3 + vite 作为前端,编写在线查询英汉词典

后端部分(express + js-mdict

1. 项目结构

首先,创建一个项目目录,结构如下:cd \js
mkdir mydict-app

mydict-app/
├── public/
│   ├── index.html
│   ├── styles.css
│   └── script.js
├── server/
│   └── app.js
├── package.json
└── README.md

英汉词典文件(*.mdx , *.mdd)  已经放在 /js/testdict/ 

在 public 中添加一些英汉字典的样式:oalecd8e.css , oalecd8e.js , uk_pron.png, us_pron.png,
copy jquery-3.2.1.min.js pulibc\jquery.js 

2. 安装依赖

在项目根目录下运行以下命令,初始化项目并安装所需的依赖:
cd mydict-app
npm init -y
npm install express  cors
added 69 packages in 6s

npm install js-mdict 
added 3 packages in 2s

3. 编写后端代码

创建 app.js 文件,代码如下:

const express = require('express');
const cors = require('cors');
const Mdict = require('js-mdict');
const path = require('path');

const app = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// 提供静态文件
app.use(express.static(path.join(__dirname, '../public')));

// 加载MDict词典文件
//const mdict = new Mdict('path/to/your/dictionary.mdx');
const mdx = new Mdict.MDX('/js/testdict/oale8.mdx');
const mdd = new Mdict.MDD('/js/testdict/oale8.mdd');

const isWord = (txt) => {
    // 只允许字母、/、空格、-
    return /^[a-zA-Z \/\-]+$/.test(txt);
};

// 处理查询请求,响应:单词的定义(html)
app.get('/query', (req, res) => {
    let word = req.query.word;
    if (!word) {
        return res.status(400).json({ error: 'No word input'});
    }
    if (word) {
        let data = mdx.lookup(word);
        console.log("cha: "+ word);
        if(data.definition){
            res.send(data.definition);
        } else {
            res.status(400).send('this word not found');
        }
    } else {
        res.status(400).send('error: No word input');
    }
});

// 处理查询请求,响应:单词的定义(json)
app.get('/search', async (req, res) => {
    const word = req.query.word;
    if (!word) {
        return res.status(400).json({ error: 'No word input'});
    }
    console.log("cha: "+ word);
    try {
        let data = await mdx.lookup(word);
        let result;
        if (data.definition){ result = data.definition;}
        else { result ='';}
        res.json({ result });
    } catch (error) {
        console.error('Error searching in MDict:', error);
        res.status(500).json({ error: 'Internal server error' });
    }
});

// 处理前缀查询请求
app.get('/prefix', (req, res) => {
    let word = req.query.word;
    // 检查word是否合法
    if (word.length <2) {
        return res.status(400).json({ error: 'input too short'});
    } else if (word.length>50 || !isWord(word)) {
        return res.status(400).json({ error: 'Invalid input.'});
    }
    if (word) {
        let alist = mdx.prefix(word);
        console.log("pre: "+ word);
        if(alist.length >0){
            let wordls = [];
            alist.forEach(function(value){
                wordls.push(value.keyText);
            });  
            res.json({wordls});
        } else {
            res.status(500).json({ info: 'this word not found'});
        }
    } else {
        res.status(500).json({ error: 'No word input'});
    }
});

// 处理模糊查询请求
app.get('/fuzzy', (req, res) => {
    let word = req.query.word;
    // 检查word是否合法
    if (word.length>50 || !isWord(word)) {
        return res.status(400).json({ error: 'Invalid input.'});
    }
    if (word) {
        let alist = mdx.fuzzy_search(word,3,1);
        console.log("fuzzy: "+ word);
        if(alist.length >0){
            let wordls = [];
            alist.forEach(function(value){
                wordls.push(value.keyText);
            });  
            res.json({wordls});
        } else {
            res.status(500).json({ info: 'this word not found'});
        }
    } else {
        res.status(500).json({ error: 'No word input'});
    }
});

var MIME = {
    'css': 'text/css',
    'bmp': 'image',
    'img': 'image',
    'gif': 'image',
    'jpg': 'image/jpeg',
    'png': 'image/png',
    'spx': 'audio/x-speex',
    'wav': 'audio/wav',
    'mp3': 'audio/mp3',
    'js' : 'text/javascript'
};
// 指定目录
const dir1 = "/";

// 实现文件下载,*/是路径
app.get('/data/*/:fileName', (req, res, next) => {
    let path1 = req.params[0]; // 捕获 * 匹配的部分
    let fileName = req.params.fileName; // 捕获文件名
    // 检查路径中是否包含非法字符(如 ..)
    if (path1.includes('..') || fileName.includes('..')) {
        return res.status(400).send('Invalid path: Path traversal is not allowed.');
    }
    let extname = path.extname(fileName);
    let ext = extname.substring(1).toLowerCase();
    if (['mp3','spx','wav'].includes(ext)){
      let filePath = path.join(dir1,path1, fileName);
      //console.log(filePath);
      let data = mdd.locate(filePath);
      if (data){
        console.log('key: '+ data.keyText);
        //console.log(Buffer.isBuffer(data.definition));
        
        if (data.definition){
          let binaryData = Buffer.from(data.definition, 'base64');
        //res.setHeader('Content-Type', 'application/octet-stream');
          res.set({
           'Content-Type': MIME[ext] || 'audio',
           'Content-Disposition': 'attachment;',
           'Content-Length': Buffer.byteLength(binaryData)
          });
        //console.log(MIME[ext]);
          res.end(binaryData);
        } else {
          res.status(400).send('error: data.definition is null');
        }
      } else {
        res.status(400).send('error: data is null');
      }
    } else {
        res.status(400).send('filename.ext is not .mp3');
    }
});

// 实现文件下载,*/是路径
app.get('/*/:fileName', (req, res, next) => {
    let path1 = req.params[0]; // 捕获 * 匹配的部分
    let fileName = req.params.fileName; // 捕获文件名
    // 检查路径中是否包含非法字符(如 ..)
    if (fileName.includes('..')) {
        return res.status(400).send('Invalid path: Path traversal is not allowed.');
    }
    //console.log(fileName);
    let extname = path.extname(fileName);
    let ext = extname.substring(1).toLowerCase();
    if (['bmp','gif','jpg','png'].includes(ext)){
      let filePath = path.join(dir1,path1, fileName);
      //console.log(filePath);
      let data = mdd.locate(filePath);
      if (data){
        console.log('key: '+ data.keyText);
        //console.log(Buffer.isBuffer(data.definition));
        
        if (data.definition){
          let binaryData = Buffer.from(data.definition, 'base64');
        //res.setHeader('Content-Type', 'application/octet-stream');
          res.set({
           'Content-Type': MIME[ext] || 'image',
           'Content-Disposition': 'attachment;',
           'Content-Length': Buffer.byteLength(binaryData)
          });
        //console.log(MIME[ext]);
          res.end(binaryData);
        } else {
          res.status(400).send('error: data.definition is null');
        }
      } else {
        res.status(400).send('error: data is null');
      }
    } else {
        console.log(fileName);
        res.status(400).send('filename.ext is not image');
    }
});

const port = process.env.PORT || 8006;
app.listen(port, () => {
    console.log(`Server is running on port:${port}`);
});

4. 运行后端服务

cd mydict-app
node server/app.js
Server is running on port:8006


前端部分(Vue 3 + Vite)

1. 创建前端项目

node -v
v18.20.6
npm -v
10.8.2

cd \js
cnpm create vite@latest mydict-web --template vue
 选 Vue 
 选 JavaScript

项目结构‌:Vite 会自动创建一个基本的项目结构,包括 src目录下的组件、路由和状态管理等文件。主要文件和目录如下:

  • App.vue:根组件
  • main.js:应用程序入口
  • router:Vue Router配置
  • store:状态管理

在 public 中添加一些英汉字典的样式:oalecd8e.css , oalecd8e.js , uk_pron.png, us_pron.png,
copy jquery-3.2.1.min.js pulibc\jquery.js 

2. 安装依赖

cd mydict-web
cnpm install axios

cnpm install vue-router -S

 package.json 如下

{
  "name": "mydict-web",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.7.9",
    "vue": "^3.5.13",
    "vue-router": "^4.5.0"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.2.1",
    "vite": "^6.1.0"
  }
}

3. 编写前端代码

修改 src/App.vue 文件:

<template>
  <div id="app">
    <input v-model="sWord" placeholder="请输入英文单词" @keyup.enter="search">
    &nbsp; <button @click="search">查询</button>
    &nbsp; <button @click="prefix">前缀查询</button>
    &nbsp; <button @click="fuzzy">模糊查询</button>
    <div v-if="result">
      <h3>查询结果</h3>
      <div id="result" name="result" v-html="result"></div>
    </div>
    <div v-if="error">{{ error }}</div>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import axios from 'axios';

const sWord = ref('');
const result = ref('');
const error = ref('');

// 查询
const search = async () => {
  try {
    const response = await axios.get('http://localhost:8006/search', {
      params: {
        word: sWord.value
      }
    });
    result.value = response.data.result;
    error.value = '';
  } catch (err) {
    result.value = '';
    error.value = err.response?.data?.error || '请求出错,请稍后重试';
  }
};

// 前缀查询
const prefix = async () => {
  try {
    const response = await axios.get('http://localhost:8006/prefix', {
      params: {
        word: sWord.value
      }
    });
    let items = [];
    let wordls = response.data.wordls;
    wordls.forEach((item, i) => {
      if (i<=20){
        items[i] = `<a href="http://localhost:8006/query?word=${item}" target="result">${item}</a>`;
      }
    });
    if (items.length >0){ result.value = items.join(', ');}
    else {result.value = '';}
    error.value = '';
  } catch (err) {
    result.value = '';
    error.value = err.response?.data?.error || '请求出错,请稍后重试';
  }
};

// 模糊查询
const fuzzy = async () => {
  try {
    const response = await axios.get('http://localhost:8006/fuzzy', {
      params: {
        word: sWord.value
      }
    });
    let items = [];
    let wordls = response.data.wordls;
    wordls.forEach((item, i) => {
      if (i<=20){
        items[i] = `<a href="http://localhost:8006/query?word=${item}" target="result">${item}</a>`;
      }
    });
    if (items.length >0){ result.value = items.join(', ');}
    else {result.value = '';}
    error.value = '';
  } catch (err) {
    result.value = '';
    error.value = err.response?.data?.error || '请求出错,请稍后重试';
  }
};

</script>

<style scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: left;
  color: #2c3e50;
  margin-top: 10px;
}
</style>

4. 运行前端项目

cd mydict-web
npm run dev

> mydict-web@0.1.0 dev
> vite

  VITE v6.1.1  ready in 1083 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
h

  Shortcuts
  press r + enter to restart the server
  press u + enter to show server url
  press o + enter to open in browser
  press c + enter to clear console
  press q + enter to quit
o

注意事项

  • 跨域问题:在开发环境中使用 cors 中间件解决跨域问题,在生产环境中可以通过配置反向代理等方式处理。
  • MDX 文件路径:确保 app.js 中的 mdict Path 指向正确的 .mdx 词典文件。
  • 安全性:在生产环境中,需要考虑对后端接口进行安全防护,如限制请求频率、验证请求来源等。

通过以上步骤,你就可以实现一个简单的在线英汉词典查询系统。

相关文章:

  • 【由技及道】SpringBoot启动即退出的量子纠缠诊断实录【人工智障AI2077的开发问题日志】
  • JavaScript函数-函数案例
  • VScode中Markdown PDF无法正确输出包含数学公式的pdf解决方案
  • C# Combox 绑定数据
  • 详解 Spring 配置数据源的两种方式
  • 【Linux】进程控制
  • 代码审计入门学习
  • CCA社群共識聯盟正式上線
  • N皇后问题(位运算版本)
  • Tips :仿真竞争条件 指的是什么?
  • FFmpeg+vvenc实现H.266的视频编解码教程
  • SAP-ABAP:使用ST05(SQL Trace)追踪结构字段来源的步骤
  • JavaScript系列(88)--Babel 编译原理详解
  • 5秒修改文件默认打开方式-windows版
  • 蓝桥杯 Java B 组之回溯剪枝优化(N皇后、数独)
  • Milvus x DeepSeek 搭建低成本高精度 RAG 实战
  • 【部署优化篇十三】深度解析《DeepSeek API网关:Kong+Nginx配置指南》——从原理到实战的超详细手册
  • androidstudio 运行项目加载很慢,优化方法
  • Golang适配达梦数据库连接指定模式
  • 第十章:服务器消费者管理模块
  • 秦洪看盘|指标股发力,A股渐有突破态势
  • 4月企业新发放贷款利率处于历史低位
  • 多个侵华日军细菌战部队留守名簿文件首次公布
  • 美国务卿鲁比奥将前往土耳其参加俄乌会谈
  • “75万买299元路由器”事件进展:重庆市纪委等三部门联合介入调查
  • 共情场域与可持续发展——关于博物馆、美术馆运营的新思考