NodeJS全栈开发一个功能完善的Express项目(附完整源码)

您所在的位置:网站首页 nodejs博客项目 NodeJS全栈开发一个功能完善的Express项目(附完整源码)

NodeJS全栈开发一个功能完善的Express项目(附完整源码)

2024-01-10 16:10| 来源: 网络整理| 查看: 265

一. 前言

Node.js对前端来说无疑具有里程碑意义,与其越来越流行的今天,掌握 Node.js 技术已经不仅仅是加分项,而是前端攻城师们必须要掌握的一项技能。而 Express 基于 Node.js 平台,快速、开放、极简的 Web 开发框架,成为 Node.js 最流行的框架,所以使用 Express 进行 web 服务端的开发是个不错且可信赖的选择。但是 Express 初始化后,并不马上就是一个开箱即用,各种功能完善的 web 服务端项目,例如:MySQL 连接、jwt-token 认证、md5 加密、中间件路由模块、异常错误处理、跨域配置、自动重启等一系列常见的功能,需要开发者自己手动配置安装插件和工具来完善功能,如果你对 web 服务端开发或者 Express 框架不熟悉,那将是一项耗费巨大资源的工作。

本文作者根据项目实战经验已将底层服务架构搭建完成,并且本项目已在 github 开源,供大家学习参考使用(如有不足,还请批评指正),希望能减轻大家的工作量,更高效的完成工作,有更多时间提升自己的能力。

后端 API 接口源码地址 👉:https://github.com/jackchen0120/todo-nodejs-api

前端界面源码地址 👉:https://github.com/jackchen0120/todo-vue-admin

如果觉得本文还不错,记得点个 👍 或者给个 ❤️,你们的赞和 star 是作者编写更多更精彩文章的动力!

分享之前我们先来认识一下Node.js、Express都是什么东东。

Node.js

简单的说 Node.js 就是运行在服务端的 JavaScript。

Node.js 是一个基于 Chrome JavaScript 运行时建立的一个平台。

Node.js 是一个事件驱动 I/O 服务端 JavaScript 环境,基于 Google 的 V8 引擎,V8 引擎执行 Javascript 的速度非常快,性能非常好。

Express

Express 是一个简洁而灵活的 Node.js Web 应用框架,提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。使用 Express 可以快速地搭建一个完整功能的网站。

Express 框架核心特性:

可以设置中间件来响应 HTTP 请求。定义了路由表用于执行不同的 HTTP 请求动作。可以通过向模板传递参数来动态渲染 HTML 页面。 二. 前后端分离

前端项目采用的技术栈是基于Vue + iView,用 vue-cli 构建前端界面,后端项目采用的技术栈是基于Node.js + Express + MySQL,用 Express 搭建的后端服务器。

Web3研习社 👉:[https://54web3.cc/)

部分效果截图

三. 前端部分 3.1 基础环境

开发前准备工作,相关运行环境配置如下:

工具名称版本号node.js10.16.2npm6.9.0

运行项目

1> 下载安装依赖

git clone https://github.com/jackchen0120/todo-vue-admin.git cd todo-vue-admin npm install 或 yarn

2> 开发模式

npm run serve

运行之后,访问地址:http://localhost:8082

3> 生产环境打包

npm run build

3.2 目录结构 │ package.json // npm包管理所需模块及配置信息 │ vue.config.js // webpack配置 ├─public │ favicon.ico // 图标 │ index.html // 入口html文件 └─src │ App.vue // 根组件 │ main.js // 程序入口文件 ├─assets // 存放公共图片文件夹 ├─components │ Footer.vue // 页面底部公用组件 │ Header.vue // 页面头部公用组件 ├─router │ index.js // 单页面路由注册组件 ├─store │ │ index.js // 状态管理仓库入口文件 │ └─modules │ userInfo.js // 用户模块状态管理文件 ├─styles │ base.scss // 基础样式文件 ├─utils │ api.js // 统一封装API接口调用方法 │ network.js // axios封装与拦截器配置 │ url.js // 自动部署服务器环境 │ valid.js // 统一封装公用模块方法 └─views Home.vue // 首页界面 Login.vue // 登录界面 3.3 技术栈 vue2.6vue-routervuexaxioswebpackES6/7flexiViewUI 3.4 功能模块 登录(登出)注册记住密码忘记密码(修改密码)todoList 增删改查点亮红星标记查询条件筛选 3.5 代码实现 3.5.1 全局安装 vue-cli4

npm install -g @vue/cli #安装指定版本 npm install -g @vue/[email protected] #OR yarn global add @vue/cli

3.5.2 vue-cli4 创建项目及运行

vue create todo-vue-admin cd todo-vue-admin npm run serve

3.5.3 开发配置

在项目根目录新增vue.config.js文件,配置信息如图所示:

3.5.4 其他事项

按照上面的步骤完成脚手架搭建后,把需要的 axios、vue-router、view-design、sass-loader、node-sass 等依赖库安装配置好,准备开始上膛。

3.5.5 实现前端登录注册功能

首先在 views 文件夹下面新建login.vue组件,编写一个静态的登录注册页面。登录成功后将登录返回的 token 保存到浏览器端并跳转到主页。views 文件夹下面新建home.vue组件,显示登录成功后的页面,并获取用户基本信息,主页右上角显示用户头像、修改密码、退出登录等功能。代码如下:

TODOList区块链管理平台 登录 · 注册 立即登录 记住我 忘记密码? 立即注册 .login-container { background-image: url('../assets/bg.png'); background-position: center; background-size: cover; position: relative; width: 100%; height: 100%; .pageHeader { padding-top: 30px; padding-left: 40px; img { vertical-align: middle; display: inline-block; margin-right: 15px; } span { font-size: 18px; display: inline-block; vertical-align: -4px; color: rgba(255, 255, 255, 1); } } .login-box { position: absolute; left: 64vw; top: 50%; -webkit-transform: translateY(-50%); transform: translateY(-50%); box-sizing: border-box; text-align: center; box-shadow: 0 1px 11px rgba(0, 0, 0, 0.3); border-radius: 2px; width: 420px; background: #fff; padding: 45px 35px; .option { text-align: left; margin-top: 18px; .checked { padding-left: 5px; } .forget-pwd, .goback { float: right; font-size: 14px; font-weight: 400; color: #4981f2; line-height: 20px; cursor: pointer; } .protocol { color: #4981f2; cursor: pointer; } } .login-text { width: 100%; text-align: center; padding: 0 0 30px; font-size: 24px; letter-spacing: 1px; a { padding: 10px; color: #969696; &.active { font-weight: 600; color: rgba(73, 129, 242, 1); border-bottom: 2px solid rgba(73, 129, 242, 1); } &:hover { border-bottom: 2px solid rgba(73, 129, 242, 1); } } b { padding: 10px; } } .title { font-weight: 600; padding: 0 0 30px; font-size: 24px; letter-spacing: 1px; color: rgba(73, 129, 242, 1); } .input-box { .input { &:nth-child(1) { /*margin-top: 10px;*/ } &:nth-child(2), &:nth-child(3) { margin-top: 20px; } } } .loginBtn { width: 100%; height: 45px; margin-top: 40px; font-size: 15px; } .input { padding: 10px 0px; font-size: 15px; width: 350px; color: #2c2e33; outline: none; // 去除选中状态边框 border: 1px solid #fff; border-bottom-color: #e7e7e7; background-color: rgba(0, 0, 0, 0); // 透明背景 } input:focus { border-bottom-color: #0f52e0; outline: none; } .button { line-height: 45px; cursor: pointer; margin-top: 50px; border: none; outline: none; height: 45px; width: 350px; background: rgba(216, 216, 216, 1); border-radius: 2px; color: white; font-size: 15px; } } // 滚动条样式 ::-webkit-scrollbar { width: 10px; } ::-webkit-scrollbar-track { -webkit-box-shadow: inset006pxrgba(0, 0, 0, 0.3); border-radius: 8px; } ::-webkit-scrollbar-thumb { border-radius: 10px; background: rgba(0, 0, 0, 0.2); -webkit-box-shadow: inset006pxrgba(0, 0, 0, 0.5); } ::-webkit-scrollbar-thumb:window-inactive { background: rgba(0, 0, 0, 0.4); } //设置更改input 默认颜色 ::-webkit-input-placeholder { /* WebKit browsers */ color: #bebebe; font-size: 16px; } ::-moz-placeholder { /* Mozilla Firefox 19+ */ color: #bebebe; font-size: 16px; } :-ms-input-placeholder { /* Internet Explorer 10+ */ color: #bebebe; font-size: 16px; } input:-webkit-autofill { box-shadow: 0 0 0px 1000px rgba(255, 255, 255, 1) inset; -webkit-box-shadow: 0 0 0px 1000px rgba(255, 255, 255, 1) inset; -webkit-text-fill-color: #2c2e33; } .ivu-checkbox-wrapper { margin-right: 0; } }

请求登录成功后,根据需求将用户信息保存到浏览器端,通过vuex-persistedstate插件使用浏览器的本地存储(localstorage)对状态(state)进行持久化。

npm install -S vuex-persistedstate

配置信息在 store 文件夹下面新建 index.js 文件,代码如下:

import Vue from 'vue' import Vuex from 'vuex' import userInfo from './modules/userInfo' // 用户模块信息 import createPersistedState from 'vuex-persistedstate' Vue.use(Vuex) export default new Vuex.Store({ modules: { // 采用模块化状态管理 userInfo }, getters: { isLogined: state => { return state.userInfo.isLogined } }, plugins: [createPersistedState({ // 插件配置信息 key: 'store', // key对象存储的key值可以自定义 storage: window.localStorage, // storage对象存储的value值,采用HTML5中的新特性localStorage属性实现 })] })

在 modules 文件夹下面新建userInfo.js文件,用作用户状态管理成员配置,将 token 保存到 vuex 中,代码如下:

const userInfo = { namespaced: true, state: { data: {}, isLogined: false }, getters: { userInfo: state => { return state.data } }, mutations: { // 设置用户信息 setUserInfo(state, userInfo) { state.data = userInfo state.isLogined = true }, // 清除用户信息 clearUserInfo(state,info) { state.data = info state.isLogined = false }, // 修改用户信息 modifyUserInfo(state, newInfo) { state.data = Object.assign(state.data, newInfo) } }, actions: { // 保存用户信息 saveInfo({ commit }, result) { commit('setUserInfo', result) }, // 退出登录 logout({commit}) { commit('clearUserInfo', {}) location.href = '/login' } } } export default userInfo

在 router 文件夹下面新建index.js文件,用来添加路由信息,代码如下:

import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) const routes = [ { path: '/login', name: 'Login', component: () => import('@/views/Login.vue'), meta: { title: '登录界面' } }, { path: '/', name: 'Home', component: () => import('@/views/Home.vue'), meta: { title: '首页', requireAuth: true } }, { path: '**', redirect: '/' } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default router

编写完登录注册界面,登录成功后跳转到主页。

// 立即登录 login() { if (this.isDisabled || this.isLoading) { return false; } if (!this.$Valid.validUserName(this.formLogin.userName)) { this.$Message.error('请输入正确的邮箱/手机号'); return false; } if (!this.$Valid.validPass(this.formLogin.userPwd)) { this.$Message.error('密码应为8到20位字母或数字!'); return false; } // 判断复选框是否被勾选,勾选则调用配置cookie方法 if (this.checked) { // 传入账号名,密码,和保存天数3个参数 this.setCookie(this.formLogin.userName, this.formLogin.userPwd, 7); } else { // 清空Cookie this.clearCookie(); } this.isLoading = true; let form = { username: this.formLogin.userName, password: this.formLogin.userPwd }; login(form) .then(res => { console.log('登录===', res); this.isLoading = false; if (res.code == 0) { this.clearInput(); this.$Message.success('登录成功'); this.$store.dispatch('userInfo/saveInfo', res.data); this.$router.push('/home'); } else { this.$Message.error(res.msg); } }) .catch(() => { this.isLoading = false; }); }

编写主页,头部和底部组件单独引入作为可复用,存放在/src/components 文件夹下面,首页效果如图所示:

// 点击头像下拉菜单选择 changeMenu(name) { if (name == 'a') { this.modal = true; this.$refs['formItem'].resetFields(); } else if (name == 'b') { this.$store.dispatch('userInfo/logout') } }

使用 axios 编写 http 请求和响应拦截器。在 utils 文件夹下新建network.js文件,代码如下:

import Vue from 'vue' import axios from 'axios' import { apiUrl } from './url' import store from '../store' // 创建实例 const service = axios.create({ baseURL: apiUrl, timeout: 55000 }) // 请求拦截器 service.interceptors.request.use(config => { if (store.state.userInfo.data.token) { config.headers['authorization'] = store.state.userInfo.data.token; } return config; }, error => { Promise.reject(error); }) // 响应拦截器 service.interceptors.response.use( response => { console.log(response.data) // 抛出401错误,因为token失效,重新刷新页面,清空缓存,跳转到登录界面 if (response.data.code == 401) { store.dispatch('userInfo/logout') .then(() => { location.reload(); }); } return response.data; }, error => { Vue.prototype.$Message.error({ content: '网络异常,请稍后再试', duration: 5 }) return Promise.reject(error) } ) export default service;

在 utils 文件夹下新建api.js实现前端 API 接口统一调用,代码如下:

import network from './network'; // 登录 export function login(data) { return network({ url: `/login`, method: "post", data }); } // 注册 export function register(data) { return network({ url: `/register`, method: "post", data }) } // 密码重置 export function resetPwd(data) { return network({ url: `/resetPwd`, method: "post", data }) } // 任务列表 export function queryTaskList(params) { return network({ url: `/queryTaskList`, method: "get", params }) } // 添加任务 export function addTask(data) { return network({ url: `/addTask`, method: "post", data }) } // 编辑任务 export function editTask(data) { return network({ url: `/editTask`, method: "put", data }) } // 操作任务状态 export function updateTaskStatus(data) { return network({ url: `/updateTaskStatus`, method: "put", data }) } // 点亮红星标记 export function updateMark(data) { return network({ url: `/updateMark`, method: "put", data }) } // 删除任务 export function deleteTask(data) { return network({ url: `/deleteTask`, method: "delete", data }) }

到这里,前端的登录注册功能就基本实现了。接下来要实现后端的接口部分了。👏

四. MySQL 安装配置

请移步到我的另一篇博客有详细介绍。

数据库设计部分

使用MySQL,创建数据库my_test ,创建sys_user用户表。

-- 创建数据库 CREATE DATABASE `my_test` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; -- 创建用户表 CREATE TABLE `sys_user` ( `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '唯一标识', `username` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '登录帐号,邮箱或手机号', `password` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '登录密码', `nickname` VARCHAR(50) NULL DEFAULT '' COMMENT '昵称', `avator` VARCHAR(50) NULL DEFAULT '' COMMENT '用户头像', `sex` VARCHAR(20) NULL DEFAULT '' COMMENT '性别:u:未知, m:男, w:女', `gmt_create` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `gmt_modify` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `username_UNIQUE` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='用户表'; 五. 后端部分 5.1 基础环境

开发前准备工作,相关运行环境配置如下:

工具名称版本号express4.17.1mysql5.7

运行项目

1> 下载安装依赖

git clone https://github.com/jackchen0120/todo-nodejs-api.git cd todo-nodejs-api npm install 或 yarn

2> 开发模式

npm start

运行之后,访问地址:http://localhost:8088

3> 生产环境(后台启动服务)

pm2 start ecosystem.config.js

5.2 目录结构 │ app.js // 入口文件 │ ecosystem.config.js // pm2默认配置文件 │ package.json // npm包管理所需模块及配置信息 ├─db │ dbConfig.js // mysql数据库基础配置 ├─routes │ index.js // 初始化路由信息,自定义全局异常处理 │ tasks.js // 任务路由模块 │ users.js // 用户路由模块 ├─services │ taskService.js // 业务逻辑处理 - 任务相关接口 │ userService.js // 业务逻辑处理 - 用户相关接口 └─utils constant.js // 自定义常量 index.js // 封装连接mysql模块 md5.js // 后端封装md5方法 user-jwt.js // jwt-token验证和解析函数 5.3 技术栈 Node.js v10express v4mysql v5.7express-jwtnodemoncryptocorsboompm2 5.4 功能模块 登录(登出)注册记住密码修改密码todoList 增删改查点亮红星标记查询条件筛选 5.5 代码实现

后端登录注册功能使用了jwt-token认证模式来实现。使用Express、express-validator、body-parser、boom、cors、jsonwebtoken、express-jwt、MySQL组件库来简化开发。

express-validator:一个基于 Express 的数据验证中间件,可以方便的判断传入的表单数据是否合法。body-parser:对 post 请求的请求体进行解析的 express 中间件。boom:处理程序异常状态,boom 是一个兼容 HTTP 的错误对象,他提供了一些标准的 HTTP 错误,比如 400(参数错误)等。cors:实现 Node 服务端跨域的 JS 库。jsonwebtoken:基于jwt的概念实现安全的加密方案库,实现加密 token 和解析 token 的功能。express-jwt:express-jwt 是在 jsonwebtoken 的基础上做了上层封装,基于 Express 框架下认证 jwt 的中间件,来实现 jwt 的认证功能。MySQL:Node.js 连接 MySQL 数据库。 5.5.1 安装相关依赖库

npm i -S express npm i -S body-parser npm i -S express-validator npm i -S boom npm i -S cors npm i -S jsonwebtoken npm i -S express-jwt npm i -S mysql

5.5.2 后端目录结构 │ app.js // 入口文件 │ ecosystem.config.js // pm2默认配置文件 │ package.json // npm包管理所需模块及配置信息 ├─db │ dbConfig.js // mysql数据库基础配置 ├─routes │ index.js // 初始化路由信息,自定义全局异常处理 │ tasks.js // 任务路由模块 │ users.js // 用户路由模块 ├─services │ taskService.js // 业务逻辑处理 - 任务相关接口 │ userService.js // 业务逻辑处理 - 用户相关接口 └─utils constant.js // 自定义常量 index.js // 封装连接mysql模块 md5.js // 后端封装md5方法 user-jwt.js // jwt-token验证和解析函数 5.5.3 实现后端功能 5.5.3.1 工具类方法

在 utils 文件夹新建constant.js文件,定义一些常量信息,代码如下:

module.exports = { CODE_ERROR: -1, // 请求响应失败code码 CODE_SUCCESS: 0, // 请求响应成功code码 CODE_TOKEN_EXPIRED: 401, // 授权失败 PRIVATE_KEY: 'jackchen', // 自定义jwt加密的私钥 JWT_EXPIRED: 60 * 60 * 24, // 过期时间24小时 }

在 utils 文件夹新建user-jwt.js文件,定义 jwt-token 验证和 jwt-token 解析函数,代码如下:

const jwt = require('jsonwebtoken'); // 引入验证jsonwebtoken模块 const expressJwt = require('express-jwt'); // 引入express-jwt模块 const { PRIVATE_KEY } = require('./constant'); // 引入自定义的jwt密钥 // 验证token是否过期 const jwtAuth = expressJwt({ // 设置密钥 secret: PRIVATE_KEY, // 设置为true表示校验,false表示不校验 credentialsRequired: true, // 自定义获取token的函数 getToken: (req) => { if (req.headers.authorization) { return req.headers.authorization } else if (req.query && req.query.token) { return req.query.token } } // 设置jwt认证白名单,比如/api/login登录接口不需要拦截 }).unless({ path: [ '/', '/api/login', '/api/register', '/api/resetPwd' ] }) // jwt-token解析 function decode(req) { const token = req.get('Authorization') return jwt.verify(token, PRIVATE_KEY); } module.exports = { jwtAuth, decode }

在 utils 文件夹新建md5.js文件,密码使用 md5 加密。代码如下:

const crypto = require('crypto'); // 引入crypto加密模块 function md5(s) { return crypto.createHash('md5').update('' + s).digest('hex'); } module.exports = md5;

在 db 文件夹新建dbConfig.js文件,定义数据库基本配置信息,代码如下:

const mysql = { host: 'localhost', // 主机名称,一般是本机 port: '3306', // 数据库的端口号,如果不设置,默认是3306 user: 'root', // 创建数据库时设置用户名 password: '123456', // 创建数据库时设置的密码 database: 'my_test', // 创建的数据库 connectTimeout: 5000 // 连接超时 } module.exports = mysql;

在 utils 文件夹新建index.js文件,连接 MySQL 数据库,代码如下:

const mysql = require('mysql'); const config = require('../db/dbConfig'); //连接mysql function connect() { const { host, user, password, database } = config; return mysql.createConnection({ host, user, password, database }) } //新建查询连接 function querySql(sql) { const conn = connect(); return new Promise((resolve, reject) => { try { conn.query(sql, (err, res) => { if (err) { reject(err); } else { resolve(res); } }) } catch (e) { reject(e); } finally { //释放连接 conn.end(); } }) } //查询一条语句 function queryOne(sql) { return new Promise((resolve, reject) => { querySql(sql).then(res => { console.log('res===',res) if (res && res.length > 0) { resolve(res[0]); } else { resolve(null); } }).catch(err => { reject(err); }) }) } module.exports = { querySql, queryOne } 5.5.3.2 业务逻辑层

在 services 文件夹下新建userService.js文件,定义用户登录注册查询等 API 接口,代码如下:

const { querySql, queryOne } = require('../utils/index'); const md5 = require('../utils/md5'); const jwt = require('jsonwebtoken'); const boom = require('boom'); const { body, validationResult } = require('express-validator'); const { CODE_ERROR, CODE_SUCCESS, PRIVATE_KEY, JWT_EXPIRED } = require('../utils/constant'); const { decode } = require('../utils/user-jwt'); // 登录 function login(req, res, next) { const err = validationResult(req); // 如果验证错误,empty不为空 if (!err.isEmpty()) { // 获取错误信息 const [{ msg }] = err.errors; // 抛出错误,交给我们自定义的统一异常处理程序进行错误返回 next(boom.badRequest(msg)); } else { let { username, password } = req.body; // md5加密 password = md5(password); const query = `select * from sys_user where username='${username}' and password='${password}'`; querySql(query) .then(user => { // console.log('用户登录===', user); if (!user || user.length === 0) { res.json({ code: CODE_ERROR, msg: '用户名或密码错误', data: null }) } else { // 登录成功,签发一个token并返回给前端 const token = jwt.sign( // payload:签发的 token 里面要包含的一些数据。 { username }, // 私钥 PRIVATE_KEY, // 设置过期时间 { expiresIn: JWT_EXPIRED } ) let userData = { id: user[0].id, username: user[0].username, nickname: user[0].nickname, avator: user[0].avator, sex: user[0].sex, gmt_create: user[0].gmt_create, gmt_modify: user[0].gmt_modify }; res.json({ code: CODE_SUCCESS, msg: '登录成功', data: { token, userData } }) } }) } } // 注册 function register(req, res, next) { const err = validationResult(req); if (!err.isEmpty()) { const [{ msg }] = err.errors; next(boom.badRequest(msg)); } else { let { username, password } = req.body; findUser(username) .then(data => { // console.log('用户注册===', data); if (data) { res.json({ code: CODE_ERROR, msg: '用户已存在', data: null }) } else { password = md5(password); const query = `insert into sys_user(username, password) values('${username}', '${password}')`; querySql(query) .then(result => { // console.log('用户注册===', result); if (!result || result.length === 0) { res.json({ code: CODE_ERROR, msg: '注册失败', data: null }) } else { const queryUser = `select * from sys_user where username='${username}' and password='${password}'`; querySql(queryUser) .then(user => { const token = jwt.sign( { username }, PRIVATE_KEY, { expiresIn: JWT_EXPIRED } ) let userData = { id: user[0].id, username: user[0].username, nickname: user[0].nickname, avator: user[0].avator, sex: user[0].sex, gmt_create: user[0].gmt_create, gmt_modify: user[0].gmt_modify }; res.json({ code: CODE_SUCCESS, msg: '注册成功', data: { token, userData } }) }) } }) } }) } } // 重置密码 function resetPwd(req, res, next) { const err = validationResult(req); if (!err.isEmpty()) { const [{ msg }] = err.errors; next(boom.badRequest(msg)); } else { let { username, oldPassword, newPassword } = req.body; oldPassword = md5(oldPassword); validateUser(username, oldPassword) .then(data => { console.log('校验用户名和密码===', data); if (data) { if (newPassword) { newPassword = md5(newPassword); const query = `update sys_user set password='${newPassword}' where username='${username}'`; querySql(query) .then(user => { // console.log('密码重置===', user); if (!user || user.length === 0) { res.json({ code: CODE_ERROR, msg: '重置密码失败', data: null }) } else { res.json({ code: CODE_SUCCESS, msg: '重置密码成功', data: null }) } }) } else { res.json({ code: CODE_ERROR, msg: '新密码不能为空', data: null }) } } else { res.json({ code: CODE_ERROR, msg: '用户名或旧密码错误', data: null }) } }) } } // 校验用户名和密码 function validateUser(username, oldPassword) { const query = `select id, username from sys_user where username='${username}' and password='${oldPassword}'`; return queryOne(query); } // 通过用户名查询用户信息 function findUser(username) { const query = `select id, username from sys_user where username='${username}'`; return queryOne(query); } module.exports = { login, register, resetPwd } 5.5.3.3 请求路由处理

在 routes 文件夹下新建index.js和user.js文件。

index.js 文件是初始化路由信息,自定义全局异常处理,代码如下:

const express = require('express'); // const boom = require('boom'); // 引入boom模块,处理程序异常状态 const userRouter = require('./users'); // 引入user路由模块 const taskRouter = require('./tasks'); // 引入task路由模块 const { jwtAuth, decode } = require('../utils/user-jwt'); // 引入jwt认证函数 const router = express.Router(); // 注册路由 router.use(jwtAuth); // 注入认证模块 router.use('/api', userRouter); // 注入用户路由模块 router.use('/api', taskRouter); // 注入任务路由模块 // 自定义统一异常处理中间件,需要放在代码最后 router.use((err, req, res, next) => { // 自定义用户认证失败的错误返回 console.log('err===', err); if (err && err.name === 'UnauthorizedError') { const { status = 401, message } = err; // 抛出401异常 res.status(status).json({ code: status, msg: 'token失效,请重新登录', data: null }) } else { const { output } = err || {}; // 错误码和错误信息 const errCode = (output && output.statusCode) || 500; const errMsg = (output && output.payload && output.payload.error) || err.message; res.status(errCode).json({ code: errCode, msg: errMsg }) } }) module.exports = router;

user.js 文件是用户路由模块,代码如下:

const express = require('express'); const router = express.Router(); const { body } = require('express-validator'); const service = require('../services/userService'); // 登录/注册校验 const vaildator = [ body('username').isString().withMessage('用户名类型错误'), body('password').isString().withMessage('密码类型错误') ] // 重置密码校验 const resetPwdVaildator = [ body('username').isString().withMessage('用户名类型错误'), body('oldPassword').isString().withMessage('密码类型错误'), body('newPassword').isString().withMessage('密码类型错误') ] // 用户登录路由 router.post('/login', vaildator, service.login); // 用户注册路由 router.post('/register', vaildator, service.register); // 密码重置路由 router.post('/resetPwd', resetPwdVaildator, service.resetPwd); module.exports = router; 5.5.3.4 入口文件配置

在根目录 app.js 程序入口文件中,导入 Express 模块,再引入常用的中间件和自定义 routes 路由的中间件,代码如下:

const bodyParser = require('body-parser'); // 引入body-parser模块 const express = require('express'); // 引入express模块 const cors = require('cors'); // 引入cors模块 const routes = require('./routes'); //导入自定义路由文件,创建模块化路由 const app = express(); app.use(bodyParser.json()); // 解析json数据格式 app.use(bodyParser.urlencoded({extended: true})); // 解析form表单提交的数据application/x-www-form-urlencoded app.use(cors()); // 注入cors模块解决跨域 app.use('/', routes); app.listen(8088, () => { // 监听8088端口 console.log('服务已启动 http://localhost:8088'); })

到此基于 Vue + iView + Express + Node.js + MySQL 实现的前后端功能已基本完成

六. 工具整合 6.1 自动重启服务

每次修改 js 文件,我们都需要重启服务器,这样修改的内容才会生效,但是每次重启比较麻烦,影响开发效果。所以我们在开发环境中引入 nodemon 插件,实现实时热更新,自动重启项目。我们在开发环境中启动项目应该使用npm start命令,因为我们在 package.json 文件中配置了以下命令:

"scripts": { "start": "nodemon app.js" } 6.2 PM2 - Node 进程管理

PM2 是 Node 进程管理工具,可以利用它来简化很多 Node 应用管理的繁琐任务,如性能监控、自动重启、负载均衡等,而且使用非常简单。

下面就对 PM2 进行入门性的介绍,基本涵盖了 PM2 的常用功能和配置:

全局安装 PM2:npm i pm2 -g监听应用:pm2 start index.js查看所有进程:pm2 list查看某个进程:pm2 describe App name/id停止某个进程:pm2 stop App name/id。停止所有进程:pm2 stop all重启某个进程:pm2 restart App name/id删除某个进程:pm2 delete App name/id

配置文件信息如下:

module.exports = { apps : [{ name: 'todo_node_api', script: 'app.js', instances: 1, autorestart: true, watch: false, max_memory_restart: '1G', env: { NODE_ENV: 'development' }, env_production: { NODE_ENV: 'production' } }], };

这里作者就不详细介绍 pm2,如需了解更多请移步到PM2 实用入门指南 | 博客园 - 程序猿小卡。

七. 运维和发布 7.1 部署发布

项目部署发布之前,必须准备好一台服务器和域名以及相关配置。作者购买的服务器是CentOS7操作系统,也要安装对应的工具库。命令如下:

// 系统升级命令 yum update // 安装 nginx yum install nginx // 启动/重启 nginx 服务 nginx / nginx -s reload // 压缩包 zip 上传下载命令 yum install lrzsz

// 安装 nodejs wget https://nodejs.org/dist/v10.16.2/node-v10.16.2-linux-x64.tar.xz tar xf node-v10.16.2-linux-x64.tar.xz mv node-v10.16.2-linux-x64 nodejs // 建立软连接 ln -s /usr/local/nodejs/bin/npm /usr/local/bin/ ln -s /usr/local/nodejs/bin/node /usr/local/bin/ // 重启服务,打印显示版本号表示安装成功 node -v

// 安装 pm2 npm install -g pm2 ln -s /usr/local/nodejs/bin/pm2 /usr/local/bin/ // 打印显示版本号表示安装成功 pm2 -v

// 安装 MySQL wget https://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm rpm -ivh mysql57-community-release-el7-9.noarch.rpm yum -y install mysql-community-server // 启动 MySQL 服务 systemctl start mysqld.service // 测试访问数据库端口是否开启 netstat -tnlp grep 3306 // 查看数据库初始密码 grep “password” /var/log/mysqld.log // 连接数据库,输入密码登录 mysql -uroot -p // 设置字符编码 UTF8 vim /etc/my.cnf [client] default-character-set=utf8 [mysqld] character-set-server=utf8 collation-server=utf8_general_ci // 重启 MySQL 服务 systemctl restart mysqld.service

前端代码打包命令

npm run build

后端代码直接上传到 github,通过命令将 github 上的代码下载到线上服务器。命令如下:

wget https://github.com/jackchen0120/todo-nodejs-api.git

7.2 运维事项

我们开发人员将项目部署发布线上后,接下来的工作就交给运维人员进行维护,而需要提供哪些给到运维人员如下:

启动命令:pm2 start/restart ecosystem.config.js运维命令:pm2 log运维文档:注意事项比如项目部署的代码程序目录路径,常用命令(启动、重启、查看日志)等等 八. 写在最后

写到这,兴许在前面代码的摧残下,能看到这里的小伙伴已经寥寥无几了,但我坚信我该交代的基本都交代了,不该交代的也交代了~🐶

所以,如果小伙伴看完真觉得不错,那就点个 👍 或者给个 💖 吧!你们的赞和 star 是我编写更多更精彩文章的动力!

github 地址:https://github.com/jackchen0120

此项目其实还有很多不足或优化的地方,也期望与大家一起交流学习。

获取更多项目实战经验及各种源码资源

请关注个人公众号:懒人码农



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3