【Trae+AI】和Trae学习搭建App_2.1:第3章·手搓后端基础框架Express

📚前言

跟着trae规划的学习开发安卓手机app的教程,一步一步学习实践,总规划见下面文档

【Trae+AI】和Trae学习搭建App_00:总项目规划_trae 封装apk-CSDN博客

下面开始规划的第三章,开始之前还有点事要说。

🌍战略调整

Trae的AI太强大了,给它个主题,它就哐哐建完了,对想一步一步学习的我们来说,速度有点太快了,其中的原理机制,都搞不明白,所以放下生化武器,回归原始手搓,文本编辑器+CMD+AI教程指导【豆包】

✍️AI提示语:

我要学习安卓手机app的开发,我已经完成第一章规划,第二章:开发环境配置(包括Android Studio、node.js、MongoDB、Git),请为我编写第三章的详细教程,内容包括 第3章:后端基础框架 Express框架搭建 数据库模型设计(用户、任务、分类) 中间件配置(日志、跨域、认证) 可以分多次输出,一定要有详细的操作过程,和对应原理,指导初学的我逐步掌握。


第二部分:后端API开发

第三章:后端基础框架Express

本章将围绕安卓APP后端开发的核心需求,搭建基于Express的后端框架,完成MongoDB数据库模型设计,并配置核心中间件(日志、跨域、认证)。所有操作均面向初学者,每一步都会讲解原理 +实操,确保你理解"做什么"和"为什么这么做"。

3.1 Express框架核心原理(先懂原理,再动手)

3.1.1 什么是Express?

Express是基于Node.js的轻量级Web开发框架,它不是替代Node.js,而是对Node.js的http模块进行了封装和扩展,解决了原生Node.js开发Web服务时"代码冗余、路由管理复杂、中间件扩展麻烦"等问题。

3.1.2 Express核心特点(为什么选它做安卓APP后端?)

  1. 极简核心:仅提供基础的Web服务能力(路由、中间件),无冗余功能,学习成本低;
  2. 中间件架构:所有核心功能(日志、认证、跨域)都通过中间件实现,扩展性极强;
  3. 生态完善:与MongoDB(非关系型数据库)、Mongoose(数据库ODM)完美适配,适合移动端后端开发;
  4. 易上手:语法贴近原生Node.js,初学者能快速落地。

3.1.3 Express核心概念(提前铺垫)

  • 应用实例 :通过express()创建的核心对象,负责管理路由、中间件、监听端口;
  • 路由:匹配客户端请求(如GET/POST),并返回对应数据(安卓APP的接口本质就是路由);
  • 中间件:请求处理的"管道",每个中间件可修改请求/响应,或传递给下一个中间件(如日志中间件先记录请求,再交给业务逻辑)。

3.2 Express框架搭建(手把手实操)

前置条件

你已完成Node.js安装(第二章配置),先验证环境:

  1. 打开终端(Windows:CMD/PowerShell,Mac/Linux:终端);

  2. 输入以下命令,能显示版本号即正常:

    复制代码
    node -v  # 如v18.17.0
    npm -v   # 如9.6.7

步骤1:创建项目目录

后端项目需要独立的目录结构,避免文件混乱,操作如下:

  1. 新建一个文件夹(建议命名:android-app-backend),作为后端根目录;

  2. 终端进入该目录(以Windows为例,假设文件夹在E:\99mydata\06vsplace\android-app-backend):

    复制代码
    cd /d E:\99mydata\06vsplace\android-app-backend

步骤2:初始化NPM项目

NPM(Node包管理器)是管理项目依赖(如Express)的工具,初始化后会生成package.json(项目配置文件):

  1. 终端输入以下命令(-y表示跳过手动配置,直接生成默认package.json):

    复制代码
    npm init -y

实际执行界面参考:

📌命令解析:

  • npm init初始化 Node.js 项目 ,生成 package.json 文件(Node.js 项目的 "身份证");package.json 是项目的核心,作用如下:
    • 记录项目的基本信息(名称、版本、作者);
    • 管理项目依赖(安装的 Express、Mongoose 等都会列在dependencies里);
    • 定义脚本命令(比如我们后来加的"dev": "nodemon app.js");
    • 标识项目入口(main字段,Node.js 运行时会优先找这个文件)。
  • -y--yes 的简写,代表 "所有配置项都使用默认值,无需手动交互确认"。默认规则如下:
    • 项目名称(name):默认是当前文件夹名称,但会自动转为小写 ,且去掉空格 / 特殊字符(比如文件夹名是Android App Backend,会变成android-app-backend);
    • 入口文件(main):默认是index.js(我们后续改成了app.js,不影响,只是约定);
    • 许可证(license):默认ISC(可后续手动修改为 MIT 等);
    • 所有空值项(描述、作者、关键词):后续可手动编辑package.json补充。

步骤3:安装Express

  1. 终端输入安装命令(--save表示将Express写入dependencies(生产依赖),项目上线也需要):

    复制代码
    npm install express --save
  2. 安装完成后,目录会新增:

    • node_modules:存放所有依赖包(无需手动修改);
    • package-lock.json:锁定依赖包版本,保证不同环境安装的依赖一致。

实际执行界面参考:

📌命令解析:

  • npm install:NPM(Node 包管理器)的核心安装命令,用于下载第三方包到当前项目;

  • express:要安装的包名称(即我们需要的 Express 框架);

  • --save:标识将该包写入package.jsondependencies字段(生产依赖),确保项目在生产环境(上线)时也能安装该依赖。

  • 后面会用到了另一个参数:--save-dev
    *

    标识 对应字段 适用场景
    --save(-S) dependencies 项目运行时必需的依赖
    --save-dev(-D) devDependencies 仅开发阶段需要的工具

步骤4:创建基础Express服务

核心是编写app.js(入口文件),实现最基础的Web服务:

  1. android-app-backend目录下新建app.js文件;

  2. 复制以下代码到app.js每一行都有注释解释原理

    复制代码
    // 1. 引入Express框架(安装后才能require)
    const express = require('express');
    
    // 2. 创建Express应用实例(核心对象,管理路由/中间件)
    const app = express();
    
    // 3. 定义端口号(后端服务监听的端口,安卓APP后续会访问这个端口)
    const PORT = 3000;
    
    // 4. 配置内置中间件:解析JSON格式的请求体(安卓APP传参通常用JSON)
    // 原理:客户端(安卓)POST请求的JSON数据,需通过此中间件解析后才能在路由中获取
    app.use(express.json());
    
    // 5. 定义测试路由(安卓APP的接口本质就是路由)
    // 格式:app.请求方法(路由路径, 处理函数)
    // GET请求:客户端获取数据(如安卓APP获取用户列表)
    app.get('/api/hello', (req, res) => {
      // req:请求对象(包含客户端传递的参数、请求头等)
      // res:响应对象(用于向客户端返回数据)
      res.status(200).json({
        code: 200,
        message: 'Express服务启动成功!',
        data: null
      });
    });
    
    // 6. 启动服务,监听指定端口
    app.listen(PORT, () => {
      console.log(`Express服务已启动,访问地址:http://localhost:${PORT}`);
    });

📌代码解释

  • require('express')模块加载,执行具体内容如下

    • 先检查项目的node_modules文件夹是否有express目录
    • 进入express目录,读取package.json文件,找到"main": "index.js"(Express 的入口文件)
    • 加载express/index.js,执行该文件中的代码,最终返回 Express 模块导出的核心函数
    • 将这个函数赋值给const express,完成模块加载
  • express()创建一个Express 应用实例(App Instance) ,本质是一个HTTP 请求处理函数 + 功能管理容器

    • 作为请求处理函数 :Node.js 的http模块创建服务时,需要一个 "请求处理函数",而app对象本身就是这个函数。比如底层等价于:

      复制代码
      const http = require('http');
      const express = require('express');
      const app = express();
      // http.createServer的参数就是请求处理函数,app对象可直接传入
      http.createServer(app).listen(3000);

      我们常用的app.listen(3000),其实是 Express 对http.createServer(app).listen()的简化封装。

    • 作为功能管理容器app对象挂载了大量方法(use/get/post/listen等),用于管理后端服务的所有逻辑,比如:

      • app.use():注册中间件(日志、跨域、解析 JSON);
      • app.get('/api/hello'):定义 GET 路由;
      • app.listen(3000):启动服务并监听端口。
  • app.use(express.json());给 Express 应用实例注册 "JSON 请求体解析中间件" ,作用是自动解析客户端(如安卓 APP)发送的JSON格式请求体数据,让你能在路由处理函数中通过req.body直接获取解析后的 JSON 数据,无需手动处理原始请求流。

    • app.use()是 Express 注册中间件 的核心方法
      • 作用:将指定的中间件 "挂载" 到应用实例上,所有请求(无论 GET/POST/PUT,无论哪个路由)都会先经过这个中间件处理;
    • express.json()是 Express 内置的中间件生成函数 ,调用后会返回一个 "JSON 解析中间件函数",核心功能:
      • 检测请求头中的Content-Type是否为application/json(客户端需设置该请求头);
      • 如果是,就把请求体的二进制数据解析成 JSON 对象;
      • 把解析后的 JSON 对象挂载到req.body属性上;
      • 如果不是(比如请求体是表单格式),则不处理,req.body仍为undefined。
  • app.get('/api/hello', (req, res) => { ... })给 Express 应用注册一个「GET 请求路由」 ,作用是:当客户端(如安卓 APP)向后端发送 GET 方式的 http://localhost:3000/api/hello 请求时,Express 会触发后面的回调函数,处理该请求并返回响应。

    • 语法:app.get(路由路径, 回调函数)
      • 请求方法是GET(客户端通过 GET 方式发起请求,而非 POST/PUT 等);
      • 请求路径匹配/api/hello(比如http://localhost:3000/api/hello,路径部分完全匹配)。
    (req, res):回调函数的参数
    • 回调函数的两个核心参数是 Express 自动传入的

      参数 全称 核心作用
      req Request 代表客户端的请求信息: ✅ 获取请求参数(req.query/req.params) ✅ 获取请求头(req.headers) ✅ 获取客户端 IP(req.ip)等
      res Response 代表服务端的响应能力: ✅ 返回文本 / JSON / 文件(res.send()/res.json()) ✅ 设置响应状态码(res.status(200)) ✅ 设置响应头(res.setHeader())等
  • app.listen(PORT, () => { ... }):启动 Express HTTP 服务,让其绑定到指定端口(PORT)并监听客户端请求,服务启动成功后执行回调函数(如打印提示)。

步骤5:运行并测试Express服务

  1. 终端输入启动命令(执行app.js):

    复制代码
    node app.js
  2. 终端显示以下内容,说明服务启动成功:

    复制代码
    Express服务启动成功,访问地址:http://localhost:3000
  3. 测试接口:

    • 打开浏览器,访问http://localhost:3000/api/hello

    • 页面显示以下JSON数据,说明路由正常:

      复制代码
      {"code":200,"message":"Express服务启动成功!","data":null}

实际运行界面参考:

💡乱码问题提示:

如果运行命令,出现乱码,是cmd字符集和程序文件字符集不一致的原因,可以修改程序文件的字符集:

  1. 用记事本打开app.js → 点击「文件」→「另存为」;
  2. 在「编码」选项中选择「UTF-8」,覆盖保存文件;
  3. 重新运行node app.js,配合方法 1 的编码切换,乱码会消失。

📌查看修改CMD的字符集

  • chcp 命令,返回 "活动代码页" 对应的数字,这个数字代表当前使用的字符集
    • 936GBK(中文 Windows 系统默认编码);
    • 65001UTF-8(通用编码,支持中文 / 英文 / 特殊符号);
    • 437 → 美国英语(英文系统默认)。
  • chcp 65001 命令,设置cmd的字符集

步骤6:优化开发体验(安装nodemon)

问题:每次修改app.js后,都需要手动执行node app.js重启服务,效率低。

解决:安装nodemon(开发依赖),它能监听文件变化,自动重启服务。

  1. 安装nodemon(--save-dev表示仅开发环境使用,上线不需要):

    复制代码
    npm install nodemon --save-dev
    1. 执行界面:
  2. 修改package.jsonscripts字段(添加启动脚本):
    scripts

    复制代码
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
    }

    修改后:

    复制代码
    "scripts": {
      "start": "node app.js",
      "dev": "nodemon app.js"
    }
    1. ⚠️提示:
      1. package.json严格的 JSON 文件 ,不支持 JavaScript 的注释(///* */)、多余逗号等语法;
      2. scripts字段的作用是定义快捷命令,"dev": "nodemon app.js"表示用npm run dev执行nodemon app.js
  3. 重启服务(用dev脚本):

    复制代码
    npm run dev
    1. 执行界面:
  4. 验证:修改app.js/api/hellomessage内容,保存后终端会自动重启服务,刷新浏览器即可看到修改后的结果。

    1. 修改app.js保存后,程序自动重启:

3.3 数据库模型设计(MongoDB + Mongoose)

3.3.1 核心原理铺垫

  1. 为什么用MongoDB?
    安卓APP的后端数据(用户、任务、分类)多为"非结构化/半结构化"(如用户的头像、任务的备注),MongoDB(非关系型数据库)比MySQL(关系型)更灵活,且与Node.js生态适配更好。
  2. 为什么用Mongoose?
    MongoDB原生操作语法不够直观,Mongoose是MongoDB的ODM(对象文档映射)工具,能:
    • 定义数据模型(约束字段类型、必填项);
    • 简化CRUD(增删改查)操作;
    • 提供数据验证、中间件等功能。

3.3.2 安装Mongoose并连接MongoDB

步骤1:安装Mongoose

终端输入命令(写入生产依赖):

复制代码
npm install mongoose --save

执行界面参考:

📌命令执行位置

  • 在项目目录android-app-backend下执行mongoose会被安装到项目根目录的node_modules文件夹 中,同时写入当前项目的package.jsondependencies字段。→ 这是正确的操作,依赖属于当前项目,仅在该项目中可用。

  • 在 CMD 默认目录(如C:\Users\你的用户名)下执行mongoose会被安装到当前默认目录的node_modules文件夹 中(如果默认目录没有package.json,则会全局安装,具体看 npm 配置),且不会关联到android-app-backend项目。→ 这是错误的操作 ,依赖不属于目标项目,后续在android-app-backend中引入mongoose会报错(找不到模块)。

  • 如果已经在默认目录误装了,只需在项目目录重新执行一次命令即可(不影响),如果删除默认目录的包用npm uninstall mongoose。

步骤2:连接MongoDB
  1. 确保你已安装并启动MongoDB(第二章配置):

  2. app.js中添加MongoDB连接代码(放在const app = express();之后):

    复制代码
    // 引入Mongoose
    const mongoose = require('mongoose');
    
    // 连接MongoDB(本地数据库,数据库名:android_app_db)
    // 原理:mongoose.connect()建立与MongoDB的连接,返回Promise
    mongoose.connect('mongodb://localhost:27017/android_app_db')
      .then(() => console.log('MongoDB连接成功!'))
      .catch(err => console.error('MongoDB连接失败:', err));
  3. 重启服务(npm run dev),终端显示MongoDB连接成功!即正常。

执行界面参考:

📌代码解释:

  • mongoose.connect():发起数据库连接

    • 这个字符串是 MongoDB 的连接地址(URI),拆解如下:

      部分 含义
      mongodb:// 协议头,标识这是 MongoDB 的连接协议(类似 HTTP 协议的http://
      localhost 数据库服务器地址,localhost代表本地电脑(也可写本机 IP:127.0.0.1
      27017 MongoDB 的默认端口号(安装后默认监听该端口,除非手动修改)
      android_app_db 要连接 / 创建的数据库名称:✅ 如果该数据库不存在,MongoDB 会自动创建;✅ 如果已存在,直接连接该数据库。
  • mongoose.connect()返回一个Promise 对象(异步操作特征)

  • .then(() => console.log('MongoDB连接成功!')):连接成功的回调

    • Promise 的.then()用于处理 "异步操作成功" 的场景
    • 当 Mongoose 与 MongoDB 服务器建立连接、且数据库android_app_db可访问时,执行该回调函数
  • .catch(err => console.error('MongoDB连接失败:', err)):连接失败的回调

    • Promise 的.catch()用于处理 "异步操作失败" 的场景

📜补充:JavaScript 箭头函数的语法规则

javascript 复制代码
// 有参数的情况
(参数1, 参数2) => { 函数体 }

// 只有1个参数时,括号可省略
参数 => { 函数体 }

// 无参数时,必须用()占位
() => { 函数体 }

// 函数体只有一行代码时,{}也可省略
() => 单行代码

3.3.3 设计数据模型(用户、任务、分类)

核心思路

先梳理安卓APP的核心数据关系:

  • 一个用户可以有多个任务;
  • 一个任务属于一个分类;
  • 分类属于某个用户(避免不同用户的分类混淆)。
步骤1:创建模型目录

为了代码结构清晰,新建models文件夹(在android-app-backend根目录),用于存放所有数据模型文件。

步骤2:用户模型(User.js)

models下新建User.js,定义用户的核心字段(用户名、密码、邮箱、创建时间等):

复制代码
const mongoose = require('mongoose');

// 1. 定义Schema(模式):约束数据结构,相当于"表结构"
// 原理:Schema规定了每个字段的类型、是否必填、默认值等,保证数据一致性
const UserSchema = new mongoose.Schema({
  username: {
    type: String,        // 字段类型:字符串
    required: true,      // 必填项
    unique: true,        // 唯一值(不能重复)
    trim: true           // 自动去除首尾空格
  },
  password: {
    type: String,
    required: true,
    minlength: 6         // 最小长度:6位
  },
  email: {
    type: String,
    required: true,
    unique: true,
    match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, '请输入合法的邮箱'] // 邮箱格式验证
  },
  avatar: {
    type: String,
    default: ''          // 默认值:空字符串(无头像)
  }
}, {
  timestamps: true       // 自动添加createdAt(创建时间)、updatedAt(更新时间)字段
});

// 2. 创建Model(模型):基于Schema创建操作数据库的对象
// 原理:Model对应MongoDB的集合(collection),名称为User(自动转为小写复数users)
const User = mongoose.model('User', UserSchema);

// 3. 导出模型,供其他文件使用
module.exports = User;

📌语法解释:

  • const UserSchema = new mongoose.Schema(...)通过 Mongoose 定义「用户数据的结构规则(Schema)」 ,相当于给 MongoDB 的users集合(表)制定 "数据模板",约束存入的用户数据必须符合这个规则

    • Schema 的核心选项作用:

      配置项 作用 示例
      type 指定字段类型(String/Number/Date 等) type: String
      required 是否必填(true/false) required: true
      unique 是否唯一(避免重复) unique: true
      default 默认值 avatar: { default: '' }
      minlength 字符串最小长度 password: { minlength: 6 }
      match 正则验证(如邮箱格式) match: [/^\w+@\w+\.\w+/, '邮箱格式错误']
  • const User = mongoose.model('User', UserSchema);基于已定义的用户数据规则(Schema),创建可操作 MongoDB 数据库的「模型(Model)」对象 。后续所有对 "用户数据" 的增删改查(CRUD)操作,都要通过这个模型完成。

    • 'User' 会映射到 MongoDB 的users集合

      • Mongoose 有一个默认规则 :将模型名('User')转为小写 + 复数形式,作为对应的 MongoDB 集合名
    • Model 是 Schema 的 "实例化操作对象",封装了所有对 MongoDB 集合的 CRUD 方法

      常用 Model 方法 作用 示例
      create() 新增数据(插入用户) User.create({ username: 'test', password: '123456' })
      find() 查询多条数据(查所有用户) User.find()
      findOne() 查询单条数据(按用户名查) User.findOne({ username: 'test' })
      updateOne() 更新单条数据(改密码) User.updateOne({ username: 'test' }, { password: '654321' })
      deleteOne() 删除单条数据(删用户) User.deleteOne({ username: 'test' })
  • module.exports = User;User模型对象暴露(导出)出去 ,让项目中其他文件可以通过require()引入并使用这个User模型,实现代码复用和模块化管理。

    • module:当前模块的内置对象,它代表 "当前文件模块本身"

    • module.exports:模块的 "导出接口",默认值是{}(空对象)

    = User:将User模型赋值给导出接口
    • Node.js 在加载模块时,会为每个.js文件创建一个专属的module对象,比如

      • 加载models/User.js时,生成module_User对象,module_User.exports 只属于这个文件;
      • 加载models/Task.js时,生成module_Task对象,module_Task.exports 只属于这个文件;
    • 同一个js文件不同赋值的情况:

      写法 效果 适用场景
      module.exports = User 覆盖默认值,导出单个值(User 模型) 模块只需要导出一个核心对象(如模型)
      module.exports.User = User,也可简写为exports.User = User; 不覆盖默认值,往空对象里加属性 模块需要导出多个内容(如同时导出 User 和 UserSchema)
    app.js中引入并使用
    复制代码
        // 引入User模型(核心:能拿到User.js中导出的User对象)
        const User = require('./models/User');
    
        // 直接使用User模型操作数据库(新增用户)
        User.create({
          username: 'testuser',
          password: '123456',
          email: 'test@example.com'
        }).then(user => console.log('用户创建成功:', user));
步骤3:分类模型(Category.js)

models下新建Category.js,分类关联用户(通过userId):

复制代码
const mongoose = require('mongoose');

const CategorySchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    trim: true
  },
  userId: {
    type: mongoose.Schema.Types.ObjectId,  // 类型:MongoDB的ObjectId(关联User的_id)
    ref: 'User',                           // 关联的模型:User(用于联表查询)
    required: true                         // 分类必须属于某个用户
  },
  color: {
    type: String,
    default: '#333333'  // 分类颜色(默认黑色)
  }
}, {
  timestamps: true
});

const Category = mongoose.model('Category', CategorySchema);
module.exports = Category;
步骤4:任务模型(Task.js)

models下新建Task.js,任务关联用户和分类:

复制代码
const mongoose = require('mongoose');

const TaskSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
    trim: true
  },
  content: {
    type: String,
    default: ''
  },
  userId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  },
  categoryId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Category',
    required: true
  },
  status: {
    type: Number,
    enum: [0, 1, 2],     // 枚举值:0-未完成,1-已完成,2-已取消
    default: 0
  },
  deadline: {
    type: Date,          // 截止时间(日期类型)
    default: null
  }
}, {
  timestamps: true
});

const Task = mongoose.model('Task', TaskSchema);
module.exports = Task;
步骤5:验证模型(可选)

app.js中引入模型(测试连接),添加以下代码:

复制代码
// 引入模型(放在MongoDB连接代码之后)
const User = require('./models/User');
const Category = require('./models/Category');
const Task = require('./models/Task');

// 测试创建一个用户(仅测试,后续可删除)
// 原理:通过Model.create()插入数据
User.create({
  username: 'testuser',
  password: '123456',
  email: 'test@example.com'
}).then(user => {
  console.log('测试用户创建成功:', user);
  // 基于该用户创建分类
  return Category.create({
    name: '工作任务',
    userId: user._id,
    color: '#4285F4'
  });
}).then(category => {
  console.log('测试分类创建成功:', category);
  // 基于用户和分类创建任务
  return Task.create({
    title: '完成安卓APP后端接口',
    content: '编写用户、任务、分类的CRUD接口',
    userId: category.userId,
    categoryId: category._id,
    deadline: new Date('2025-12-01')
  });
}).then(task => {
  console.log('测试任务创建成功:', task);
}).catch(err => {
  console.error('数据创建失败:', err);
});

重启服务后,终端会打印创建的用户、分类、任务信息,说明模型设计和数据库连接正常(测试完成后可删除这段代码)。

命令窗口界面参考:

通过「MongoDB Compass」,查看mongodb中的数据:


3.4 中间件配置(日志、跨域、认证)

3.4.1 中间件核心原理

  • 中间件是一个函数 ,格式:(req, res, next) => {}
  • req:请求对象(可获取请求参数、请求头);
  • res:响应对象(可设置响应头、返回数据);
  • next:函数,调用后将请求传递给下一个中间件(如果不调用,请求会"卡住");
  • 中间件通过app.use()注册,可全局生效(所有路由)或局部生效(指定路由)。

3.4.2 日志中间件(morgan)

原理

morgan 是 Node.js/Express 生态中最常用的 HTTP 请求日志中间件 ,核心作用是自动化记录每一个客户端对后端的 HTTP 请求细节(如请求方法、路径、状态码、响应时间、客户端 IP 等),并可将日志输出到控制台、文件等位置,是后端调试、排查问题、监控请求的核心工具。

实操步骤
  1. 安装morgan:

    复制代码
    npm install morgan --save

    执行界面参考:

    💡提示:

    安装插件后,服务器自动重启,执行app.js,其中会再执行各数据记录创建,引起重复key的错误,如下图,将之前验证模型的代码注释掉就可以了

  2. app.js中配置(放在app.use(express.json());之前):

    复制代码
    // 引入morgan
    const morgan = require('morgan');
    
    // 配置日志格式:combined(标准格式,包含请求头、IP、响应时间等)
    // 原理:app.use(morgan('combined')) 表示所有请求都经过日志中间件
    app.use(morgan('combined'));

    📌代码解释:

    • morgan('combined')创建一个基于「combined 格式」的 HTTP 请求日志中间件
      • 语法:morgan(日志格式[, 配置项]),调用后返回一个 Express 中间件函数,可通过app.use()注册。

        格式名 日志内容(示例) 特点 适用场景
        combined 如上示例(包含 IP、时间、方法、URL、状态码、UA 等) 最详细 生产环境 / 排查问题
        common ::1 - - [28/Nov/2025:10:00:00 +0800] "GET /api/hello HTTP/1.1" 200 13 精简版 combined 生产环境
        dev GET /api/hello 200 2.321 ms - 13 极简(方法 + 路径 + 状态码 + 响应时间) 开发环境
        short ::1 GET /api/hello HTTP/1.1 200 13 - 2.3ms 介于 dev 和 common 之间 开发 / 测试环境
      • 配置项示例(生产环境,输出到文件):

        复制代码
        const express = require('express');
        const morgan = require('morgan');
        const fs = require('fs');
        const path = require('path');
        const app = express();
        
        // 创建日志文件写入流
        const accessLogStream = fs.createWriteStream(
          path.join(__dirname, 'access.log'), // 日志文件路径
          { flags: 'a' } // 追加模式(避免覆盖旧日志)
        );
        
        // 注册中间件,将日志写入文件
        app.use(morgan('combined', { stream: accessLogStream }));
        
        app.get('/api/hello', (req, res) => {
          res.json({ message: 'Hello' });
        });
        
        app.listen(3000);
  3. 测试:重启服务后,访问http://localhost:3000/api/hello,终端会打印日志信息(示例):

    复制代码
    ::1 - - [26/Nov/2025:10:00:00 +0800] "GET /api/hello HTTP/1.1" 200 57 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"

    实际运行界面参考:


    日志解析:

    字段段 含义 具体解读
    ::1 客户端 IP 地址 ::1 是 IPv6 的本地回环地址 (等价于 IPv4 的127.0.0.1),说明请求来自本机(你在本地测试)。
    - 远程登录用户(Remote User) '-' 表示无该信息(morgan 无法获取客户端的系统登录用户,开发 / 测试环境默认显示-)。
    - 认证用户(Authenticated User) '-' 表示该请求无需身份认证(如公开的/api/hello接口),需登录的接口会显示用户名。
    [28/Nov/2025:02:47:02 +0000] 请求时间(UTC 时区) 格式:[日/月/年:时:分:秒 时区]+0000是 UTC 时区(东八区需加 8 小时,实际时间是 2025-11-28 10:47:02)。
    "GET /api/hello HTTP/1.1" 请求行(Request Line) 拆解:✅ GET:请求方法(你定义的app.get('/api/hello'));✅ /api/hello:请求路径;✅ HTTP/1.1:HTTP 协议版本。
    304 响应状态码(HTTP Status Code) 304 = Not Modified(未修改),表示客户端本地有该请求的缓存,服务端无需返回数据,直接告知 "用缓存"(比 200 更高效)。【响应状态码(200 = 成功,404 = 不存在,500 = 服务器错误)】
    - 响应体字节数(Response Size) '-' 表示无响应体(304 状态码不会返回数据,所以字节数为空;200 状态码会显示具体数字,如13代表 13 个字节)。
    "-" 来源页面(Referer) '-' 表示请求不是从其他页面跳转而来(比如直接在浏览器地址栏输入 URL、安卓 APP 直接调用接口)。
    "Mozilla/5.0 ... Edg/142.0.0.0" 客户端标识(User-Agent,UA) 说明请求来自Windows 10 系统的 Edge 浏览器(142 版本) ;如果是安卓 APP 调用,这里会显示 APP 的 UA(如Android/14; Mobile; rv:100.0)。

3.4.3 跨域中间件(cors)

原理

CORS(Cross-Origin Resource Sharing,跨域资源共享)是解决浏览器 "同源策略" 限制 的 HTTP 协议规范,而**cors中间件** (Express 生态的cors包)是对该规范的落地实现,核心作用是:

  • 让你的 Express 后端服务允许不同域名 / 端口 / 协议的客户端(如前端网页、安卓 APP)跨域请求接口,避免浏览器因同源策略拦截响应。

📌**"同源策略"说明:**

浏览器为了安全,默认限制 "非同源" 的 AJAX 请求(即 "跨域请求"),只有满足「协议、域名、端口全部相同」才是 "同源":

请求场景 是否同源 浏览器是否拦截
前端http://localhost:8080 → 后端http://localhost:3000 否(端口不同) 拦截(跨域)
前端https://a.com → 后端https://b.com 否(域名不同) 拦截(跨域)
前端http://localhost:3000 → 后端http://localhost:3000 不拦截
实操步骤
  1. 安装cors:

    复制代码
    npm install cors --save

    执行界面参考:

  2. app.js中配置(放在日志中间件之后):

    复制代码
    // 引入cors
    const cors = require('cors');
    
    // 全局配置跨域(允许所有域名访问,开发环境可用;生产环境需指定具体域名)
    app.use(cors({
      origin: '*',        // 允许所有域名(生产环境改为安卓APP的域名/IP)
      methods: ['GET', 'POST', 'PUT', 'DELETE'], // 允许的请求方法
      allowedHeaders: ['Content-Type', 'Authorization'] // 允许的请求头
    }));

    📌cors中间件会根据配置项,自动给后端响应添加对应的 HTTP 头,告诉浏览器 / 安卓 APP "该跨域请求被允许",cors配置项说明:

    配置项 取值 / 示例 核心含义 实用说明
    origin * / ['https://app.com', 'http://192.168.1.100'] 允许跨域请求的「来源(域名 / IP: 端口)」 *:开发环境用,允许所有域名(最宽松,方便测试); ❌ 生产环境禁用*,需改为安卓 APP 的真实域名 / IP(如http://192.168.1.200:8080),避免任意网站跨域调用你的接口; ✅ 也可传数组,指定多个可信来源(如同时允许前端网页和安卓 APP)。
    methods ['GET', 'POST', 'PUT', 'DELETE'] 允许跨域请求的「HTTP 方法」 ✅ 明确限定仅开放业务需要的方法(比如只开放 GET/POST,关闭 PUT/DELETE),减少攻击面; ✅ 若不配置,默认允许GET,HEAD,PUT,PATCH,POST,DELETE(全部常用方法); ✅ 对应你后端的app.get()/app.post()等路由方法,确保前端 / 安卓 APP 的请求方法在列表内。
    allowedHeaders ['Content-Type', 'Authorization'] 允许跨域请求携带的「请求头」 Content-Type:必须配置!安卓 APP 传 JSON 数据时会带Content-Type: application/json,没这个配置会跨域失败; ✅ Authorization:如果接口需要 token 认证(如Bearer token),必须配置这个请求头,否则安卓 APP 携带 token 时会被拦截; ✅ 若不配置,默认只允许Content-Type, Accept, Accept-Language等基础头,自定义头(如token)会被拦截。

    其他常用配置项:

    配置项 核心解决问题 生产环境建议
    credentials 能否携带登录态 /token 需认证时设为true,同时指定origin
    maxAge "预检请求(OPTIONS)" 的缓存时长(秒),减少重复预检请求,提升性能。 设为 3600(1 小时)或 86400(1 天);默认值0(每次跨域请求都发预检)

3.4.4 认证中间件(JWT)

原理

JWT(JSON Web Token)是一种轻量级的、基于 JSON 的令牌规范,核心作用是「在客户端和服务端之间安全传递身份 / 权限信息」,无需服务端存储会话(Session),是前后端分离 / 跨域场景下(如安卓 APP + 后端)最常用的身份认证方案。

安卓APP的用户登录后,后端需要生成一个"令牌(JWT)"返回给APP,后续APP每次请求都携带该令牌,后端通过认证中间件验证令牌的合法性,确认用户身份。

JWT(JSON Web Token)由三部分组成:

  • Header(头部):指定加密算法;
  • Payload(载荷):存储用户ID等非敏感信息;
  • Signature(签名):用密钥加密Header+Payload,防止篡改。
实操步骤
步骤1:安装JWT相关依赖
复制代码
npm install jsonwebtoken bcryptjs --save
  • jsonwebtokenbcryptjs两个独立的包名
    • jsonwebtoken:生成和验证JWT令牌;
    • bcryptjs:加密用户密码(避免明文存储)。

执行界面参考:

步骤2:封装密码加密/解密工具(可选,优化代码)

在根目录新建utils文件夹,创建authUtils.js

复制代码
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

// 1. 密码加密(注册/修改密码时使用)
// 原理:bcrypt.hash()生成哈希值,saltRounds=10表示加密强度
exports.encryptPassword = async (password) => {
  const salt = await bcrypt.genSalt(10);
  return await bcrypt.hash(password, salt);
};

// 2. 密码验证(登录时使用)
exports.comparePassword = async (password, hashedPassword) => {
  return await bcrypt.compare(password, hashedPassword);
};

// 3. 生成JWT令牌
// 原理:jwt.sign()生成令牌,expiresIn指定过期时间(24小时)
exports.generateToken = (userId) => {
  // 密钥(生产环境需放在环境变量中,避免硬编码)
  const SECRET_KEY = 'android_app_secret_key_2025';
  return jwt.sign({ userId }, SECRET_KEY, { expiresIn: '24h' });
};

// 4. 验证JWT令牌(认证中间件核心逻辑)
exports.verifyToken = (token) => {
  const SECRET_KEY = 'android_app_secret_key_2025';
  try {
    // 验证令牌,返回解码后的载荷(包含userId)
    return jwt.verify(token, SECRET_KEY);
  } catch (err) {
    // 令牌过期/篡改,返回null
    return null;
  }
};

📌代码解释:

  • async/await语法

    • async:声明这是一个异步函数 ,函数内部可使用await等待异步操作,且函数执行后会返回一个 Promise 对象;

    • await:等待右侧的异步操作完成

    • 调用异步函数:(在 async 函数中用 await):

      复制代码
        const bcrypt = require('bcryptjs');
      
        // 定义加密函数
        const encryptPassword = async (password) => {
          const salt = await bcrypt.genSalt(10);
          return await bcrypt.hash(password, salt);
        };
      
        // 调用加密函数(必须在async函数中用await)
        const run = async () => {
          const rawPwd = '123456'; // 明文密码
          const hashPwd = await encryptPassword(rawPwd); // 等待加密完成
          console.log(hashPwd); // 输出加密后的字符串:$2a$10$xxxxxx...
        };
      
        run();
  • bcrypt代码
    *

    代码行 核心作用 关键细节
    async (password) => {} 定义异步箭头函数,参数password是用户输入的明文密码 (如123456 异步函数调用时必须用await,或.then()接收返回值(加密后的密码)。
    const salt = await bcrypt.genSalt(10); 生成「盐值(salt)」------bcrypt 加密的核心,相当于 "随机加密因子"。 10rounds(轮数):值越高,加密越安全,但耗时越长(推荐 8-12); ✅ 盐值是随机生成的,即使相同密码,每次生成的盐值不同,最终加密结果也不同(防止彩虹表破解)。
    return await bcrypt.hash(password, salt); 用盐值对明文密码进行哈希加密,返回加密后的字符串。 ✅ 加密过程是不可逆的:无法从加密后的字符串还原出明文密码; ✅ 最终返回的是类似$2a$10$xxxxxx...的字符串(包含盐值 + 加密后的密码,无需单独存储盐值)。
    bcrypt.compare 异步密码校验方法 「对比用户输入的明文密码」和「数据库中存储的 bcrypt 加密密码」,返回布尔值(true/false
  • jwt.sign({ userId }, SECRET_KEY, { expiresIn: '24h' }):生成一个 JWT 令牌 :将用户标识(userId)嵌入令牌,用服务端私钥(SECRET_KEY)签名,并设置令牌 24 小时后过期,是用户登录成功后颁发身份凭证的关键代码

    • SECRET_KEY → 签名私钥

      ✅ 用这个私钥对「Header + Payload」进行加密,生成 JWT 的 Signature(签名);

    • ✅ 客户端携带令牌请求时,服务端需用相同的私钥验证签名,确认令牌未被篡改;

  • jwt.verify(token, SECRET_KEY):校验客户端携带的 JWT 令牌是否合法有效

    • token → 客户端携带的 JWT 令牌
    • SECRET_KEY→ 服务端的私钥(必须和生成令牌时的 jwt.sign 私钥完全一致)
  • exports 是 Node.js 每个模块内置的空对象(属于 CommonJS 规范),作用是「对外暴露模块内的变量 / 函数 / 对象」

步骤3:编写认证中间件

在根目录新建middleware文件夹,创建authMiddleware.js

复制代码
const { verifyToken } = require('../utils/authUtils');

// 认证中间件:验证请求头中的JWT令牌
exports.auth = (req, res, next) => {
  // 1. 获取请求头中的令牌(安卓APP需在请求头中携带:Authorization: Bearer <token>)
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({
      code: 401,
      message: '未登录,请先登录',
      data: null
    });
  }

  // 2. 提取令牌(去掉Bearer前缀)
  const token = authHeader.split(' ')[1];

  // 3. 验证令牌
  const decoded = verifyToken(token);
  if (!decoded) {
    return res.status(401).json({
      code: 401,
      message: '令牌过期或无效,请重新登录',
      data: null
    });
  }

  // 4. 将用户ID存入req对象,供后续路由使用
  req.userId = decoded.userId;

  // 5. 传递给下一个中间件/路由
  next();
};

📌代码解释:

  • req.headers.authorization,获取 HTTP 请求头里 Authorization 字段,专门用于携带身份认证凭证

    • HTTP 请求头的实际形态

      复制代码
        GET /api/user/info HTTP/1.1
        Host: your-server.com
        Content-Type: application/json
        Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....
    • req 的属性,对应 HTTP 请求头的所有键值对(如 Content-TypeUser-AgentAuthorization 等)

步骤4:注册认证中间件并测试
  1. app.js中引入并使用认证中间件:

    javascript 复制代码
    // 引入认证中间件
    const { auth } = require('./middleware/authMiddleware');
    const { encryptPassword, generateToken } = require('./utils/authUtils');
    const User = require('./models/User');
    
    // 示例1:登录接口(无需认证,生成令牌)
    app.post('/api/login', async (req, res) => {
      try {
        const { username, password } = req.body;
        // 1. 查询用户
        const user = await User.findOne({ username });
        if (!user) {
          return res.status(400).json({ code: 400, message: '用户不存在', data: null });
        }
        // 2. 验证密码==》此处逻辑有错误❌=》后面有更正代码
        const isMatch = await bcrypt.compare(password, user.password);
        if (!isMatch) {
          return res.status(400).json({ code: 400, message: '密码错误', data: null });
        }
        // 3. 生成令牌
        const token = generateToken(user._id);
        res.status(200).json({
          code: 200,
          message: '登录成功',
          data: { token, username: user.username }
        });
      } catch (err) {
        res.status(500).json({ code: 500, message: '服务器错误', data: null });
      }
    });
    
    // 示例2:需要认证的接口(获取用户的任务列表)
    // 原理:auth中间件先验证令牌,通过后才执行路由逻辑
    app.get('/api/tasks', auth, async (req, res) => {
      try {
        // req.userId由auth中间件存入,查询该用户的所有任务
        const tasks = await Task.find({ userId: req.userId });
        res.status(200).json({
          code: 200,
          message: '获取任务列表成功',
          data: tasks
        });
      } catch (err) {
        res.status(500).json({ code: 500, message: '服务器错误', data: null });
      }
    });

    📌代码解释:

    • app.get('/api/tasks', auth, async (req, res)
      • authauthMiddleware 的简写(通常为了代码简洁重命名),本质是一个函数,请求到达接口后会先执行该中间件
      • auth 中间件验证通过(调用 next()),才会进入后面的业务逻辑回调
      • 若验证失败(如 JWT 令牌无效),中间件会直接返回 res.status(401) 错误,终止请求,不会执行后续业务逻辑。
    • 逻辑图如下:
  2. 测试流程:

    • 先通过Postman/APIPost发送POST请求到http://localhost:3000/api/login,参数:

      复制代码
      { "username": "testuser", "password": "123456" }

      **🧩可选工具:**curl测试工具,也可以使用hoppscotch,参考文档:

      【工具】curl测试工具Hoppscotch 测试工具使用教程(零基础版)_hoppscotch本地部署-CSDN博客

      测试参考界面:

      代码中有错误,因为之前的用户表存的密码是明文:调整代码,增加注册功能,注册时,将密码加密后,保存进数据库:

      javascript 复制代码
      // 1. 引入Express框架(安装后才能require)
      const express = require('express');
      
      // 2. 创建Express应用实例(核心对象,管理路由/中间件)
      const app = express();
      // 引入Mongoose
      const mongoose = require('mongoose');
      
      // 连接MongoDB(本地数据库,数据库名:android_app_db)
      // 原理:mongoose.connect()建立与MongoDB的连接,返回Promise
      mongoose.connect('mongodb://localhost:27017/android_app_db')
        .then(() => console.log('MongoDB连接成功!'))
        .catch(err => console.error('MongoDB连接失败:', err));
      
      // 引入模型(放在MongoDB连接代码之后)
      const User = require('./models/User');
      const Category = require('./models/Category');
      const Task = require('./models/Task');
      /*
      // 测试创建一个用户(仅测试,后续可删除)
      // 原理:通过Model.create()插入数据
      User.create({
        username: 'testuser',
        password: '123456',
        email: 'test@example.com'
      }).then(user => {
        console.log('测试用户创建成功:', user);
        // 基于该用户创建分类
        return Category.create({
          name: '工作任务',
          userId: user._id,
          color: '#4285F4'
        });
      }).then(category => {
        console.log('测试分类创建成功:', category);
        // 基于用户和分类创建任务
        return Task.create({
          title: '完成安卓APP后端接口',
          content: '编写用户、任务、分类的CRUD接口',
          userId: category.userId,
          categoryId: category._id,
          deadline: new Date('2025-12-01')
        });
      }).then(task => {
        console.log('测试任务创建成功:', task);
      }).catch(err => {
        console.error('数据创建失败:', err);
      });
      */
      // 3. 定义端口号(后端服务监听的端口,安卓APP后续会访问这个端口)
      const PORT = 3000;
      
      // 引入morgan
      const morgan = require('morgan');
      
      // 配置日志格式:combined(标准格式,包含请求头、IP、响应时间等)
      // 原理:app.use(morgan('combined')) 表示所有请求都经过日志中间件
      app.use(morgan('combined'));
      
      // 引入cors
      const cors = require('cors');
      
      // 全局配置跨域(允许所有域名访问,开发环境可用;生产环境需指定具体域名)
      app.use(cors({
        origin: '*',        // 允许所有域名(生产环境改为安卓APP的域名/IP)
        methods: ['GET', 'POST', 'PUT', 'DELETE'], // 允许的请求方法
        allowedHeaders: ['Content-Type', 'Authorization'] // 允许的请求头
      }));
      
      // 引入认证中间件
      const { auth } = require('./middleware/authMiddleware');
      const { encryptPassword,comparePassword, generateToken,verifyToken } = require('./utils/authUtils');
      //const User = require('./models/User');
      //const bcrypt = require('bcryptjs');
      
      // 4. 配置内置中间件:解析JSON格式的请求体(安卓APP传参通常用JSON)
      // 原理:客户端(安卓)POST请求的JSON数据,需通过此中间件解析后才能在路由中获取
      app.use(express.json());
      
      // 5. 定义测试路由(安卓APP的接口本质就是路由)
      // 格式:app.请求方法(路由路径, 处理函数)
      // GET请求:客户端获取数据(如安卓APP获取用户列表)
      app.get('/api/hello', (req, res) => {
        // req:请求对象(包含客户端传递的参数、请求头等)
        // res:响应对象(用于向客户端返回数据)
        res.status(200).json({
          code: 200,
          message: 'Express服务启动成功!==》该了一下,会重启吗?',
          data: null
        });
      });
      
      // 1. 注册接口:加密明文密码后存储
      app.post('/api/register', async (req, res) => {
        try {
          const { username, password ,email} = req.body;
          // 校验参数
          if (!username || !password) {
            return res.status(400).json({ message: '用户名/密码不能为空' });
          }
       /*   // 检查用户是否已存在
          const existUser = User.find(u => u.username === username);
          if (existUser) {
            return res.status(400).json({ message: '用户已存在' });
          }
      */
          // 核心:加密明文密码
          const hashedPassword = await encryptPassword(password);
      	User.create({
      	  username: username,
      	  password: hashedPassword,
      	  email: email
      	}).then(user => {
      	  console.log('测试用户创建成功:', user);
      	  // 基于该用户创建分类
      	  return Category.create({
      		name: '工作任务',
      		userId: user._id,
      		color: '#4285F4'
      	  });
      	}).then(category => {
      	  console.log('测试分类创建成功:', category);
      	  // 基于用户和分类创建任务
      	  return Task.create({
      		title: '完成安卓APP后端接口',
      		content: '编写用户、任务、分类的CRUD接口',
      		userId: category.userId,
      		categoryId: category._id,
      		deadline: new Date('2025-12-01')
      	  });
      	}).then(task => {
      	  console.log('测试任务创建成功:', task);
      	}).catch(err => {
      	  console.error('数据创建失败:', err);
      	});
      
      
        } catch (err) {
          res.status(500).json({ message: `注册失败:${err.stack}` });
        }
      });
      
      
      // 示例1:登录接口(无需认证,生成令牌)
      app.post('/api/login', async (req, res) => {
        try {
          const { username, password } = req.body;
      	console.log(`input=> username:${username};password:${password}`);
          // 1. 查询用户
          const user = await User.findOne({ username });
      	console.log(`user:${user};user.password:${user.password}`);
          if (!user) {
            return res.status(400).json({ code: 400, message: '用户不存在', data: null });
          }
          // 2. 验证密码
          const isMatch = await comparePassword(password, user.password);
          if (!isMatch) {
            return res.status(400).json({ code: 400, message: '密码错误', data: null });
          }
          // 3. 生成令牌
          const token = generateToken(user._id);
          res.status(200).json({
            code: 200,
            message: '登录成功',
            data: { token, username: user.username }
          });
        } catch (err) {
          res.status(500).json({ code: 500, message: '服务器错误:' + err.stack, data: null });
        }
      });
      
      // 示例2:需要认证的接口(获取用户的任务列表)
      // 原理:auth中间件先验证令牌,通过后才执行路由逻辑
      app.get('/api/tasks', auth, async (req, res) => {
        try {
          // req.userId由auth中间件存入,查询该用户的所有任务
          const tasks = await Task.find({ userId: req.userId });
          res.status(200).json({
            code: 200,
            message: '获取任务列表成功',
            data: tasks
          });
        } catch (err) {
          res.status(500).json({ code: 500, message: '服务器错误', data: null });
        }
      });
      
      // 6. 启动服务,监听指定端口
      app.listen(PORT, () => {
        console.log(`Express服务已启动,访问地址:http://localhost:${PORT}`);
      });

      注册界面如下:

      服务器输出:

    • 后端返回令牌(token);

      • 执行界面参考: 然后用user3登录:

    • 发送GET请求到http://localhost:3000/api/tasks,请求头添加:

      复制代码
      Authorization: Bearer <返回的token>

      执行界面参考:

      测试提取用户的任务列表:

      用上面返回的认证信息,放在"Bearer "(注意Bearer后面要有空格)后面

      并设置查询的userid,如下图

    • 能获取任务列表,说明认证中间件生效;

    • 若不携带令牌/令牌无效,会返回401错误,符合预期。

      • 如果认证信息错误,会有类似下面提示
      • 这说明中间件authMiddleware生效:

3.5 本章总结

本章完成了安卓APP后端的核心基础搭建:

  1. 基于Express搭建了Web服务,理解了路由、中间件的核心原理;
  2. 设计了用户、任务、分类的MongoDB数据模型,实现了数据关联;
  3. 配置了日志(morgan)、跨域(cors)、认证(JWT)三大核心中间件,解决了后端开发的基础问题。

🐳心得:

纯手搓,有点累啊,有点累😮‍💨,特别是调错🐞,但心里明明白白☀️,光明磊落☀︎,阳光万丈☼,哇哈哈🤣~

✅AI教程,推荐豆包,比DeepSeek更适合初学者。

相关推荐
L.fountain5 小时前
图像自回归生成(Auto-regressive image generation)实战学习(二)
学习·数据挖掘·回归
梁辰兴5 小时前
OpenAI更新ChatGPT Images:生成速度最高提升4倍,原生多模态模型
人工智能·科技·ai·chatgpt·大模型·openai·图像生成
古城小栈5 小时前
边缘大模型本地部署与推理实战:以GPT-OSS-20B为例
人工智能·gpt·语言模型·边缘计算
感谢地心引力5 小时前
【AI】免费的代价?Google AI Studio 使用指南与 Cherry Studio + MCP 实战教程
人工智能·ai·google·chatgpt·gemini·mcp·cherry studio
云上漫步者5 小时前
深度实战:Rust交叉编译适配OpenHarmony PC——sys_locale完整适配案例
开发语言·后端·rust
Tezign_space5 小时前
SEO优化与AI内容运营的技术融合:架构、算法与实施路径
人工智能·架构·内容运营·私域运营·ai内容生成·seo流量增长·内容运营效率
元气满满-樱5 小时前
LNMP架构学习
android·学习·架构
小苑同学5 小时前
PaperReding:《LLaMA: Open and Efficient Foundation Language Models》
人工智能·语言模型·llama
geneculture5 小时前
融智学体系图谱(精确对应版)
大数据·人工智能·学习·融智学的重要应用·信智序位