浅尝全栈项目,体验前后端开发异同,会把开发过程中的一些亮点和难点记录下来,已备不时之需。
前端:react + ant-design-mobile
后端:express.js + mysql
目录结构
- 准备阶段
- 数据库
- 后端-express
- 前端-react
准备阶段:
一.准备阶段
1.数据库建表
(1).用户基础信息表
(2).清单表(业务表)
2.后端构建项目
下载express-generator
npm install -g express-generator
构建项目
express --view=ejs myproject
安装依赖包
cd myproject
启动项目
npm start
目录结构通常如下:
- app.js:应用的主文件,设置中间件和路由。
- bin/www:启动脚本,设置服务器端口并启动服务器。
- public/:静态文件目录,存放 CSS、JavaScript 和图片等。
- routes/:路由目录,定义应用的路由。
- views/:视图目录,存放模板文件。
- node_modules/:依赖包目录。
3.前端构建项目
npm create-react-app my-app
cd my-app
npm start
目录结构通常如下:
- node_modules:安装依赖
- public:公共文件
- src:主体文件
- .gitignore:忽略文件
- package.json:版本管理信息
- package-lock.json:版本管理信息
二.数据库
作为一个前端开发的打工仔,凡事都是以前端思维为导向,大学的数据库知识不扎实,导致在一开始设立数据库时,磕磕绊绊,但好在结果还算可以。
数据库连接
1.建立mysql单个数据库连接对象,并连接
const mysql = require("mysql"); //引入mysql
// 设置MySQL连接配置
const base_url = 'your_url'
const connection = mysql.createConnection({
host: base_url,
user: "root", //数据库账号
password: "your_code", //数据库密码
database: "test", //数据库名称
timezone: "Asia/Shanghai"
});
// 连接到MySQL数据库
connection.connect((error) => {
if (error) throw error;
//连接成功打印结果`如果连接失败,记得查看数据库是否开启`
});
2.建立数据库连接池
const mysql = require('mysql');
const pool = mysql.createPool({
host: 'localhost',
user: 'yourUsername',
password: 'yourPassword',
database: 'yourDatabase'
});
pool.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
if (error) throw error;
console.log('The solution is: ', results[0].solution);
});
两种方法的取舍,取决于使用的场景:
如果是高并发,需要频繁访问多个数据库的情况下,用createPool。
如果是复杂sql查询,多表链表查询,则用createConnection。
三.后端
1.基础配置
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
const bodyParser = require('body-parser');
require('dotenv').config();
//引入跨域中间件
const cors = require('cors');
//引入路由
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var todolistsRouter = require('./routes/todolists');
//引入中间件
const errorHandler = require('./utils/errorMiddleware');
var app = express();
//解析中间件
app.use(bodyParser.json()); // 解析JSON格式的数据
app.use(bodyParser.urlencoded({ extended: true })); // 解析URL编码的数据
app.use(cookieParser());//解析cookie
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
//配置
var session = require('express-session');
// 设置跨域 需要用
app.use(cors({
// 设置为本地前端域名,不设置为*是因为需要传cookies,不能违反cors的规范,出于安全考虑
origin: `http://${process.env.BASE_URL}:3000`, // 允许的源
credentials: true, // 允许凭证
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'Accept', 'Origin', 'Referer'],
exposedHeaders: ['Content-Range', 'Content-Disposition']
}));
//验证jwt 验证这块最好放在其他中间件的下面,因为有的中间件会影响
const expressjwt = require("express-jwt");
// 设置一个私钥
const secret = process.env.JWT_SECRET;
app.use(expressjwt({
secret: secret, algorithms: ['HS256'],
}).unless({ path: ['/users/login', '/users/register'] }));
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/todoLists', todolistsRouter);
app.set('trust proxy', 1)
// //使用错误中间件
app.use(errorHandler);
module.exports = app;
2.登录状态管理
(1).session
session是会话管理,在服务端管理用户状态,基本配置如下:
const session = require('express-session');
app.use(session({
secret: 'your_secret_key', // 用于签名会话ID的密钥
resave: false,
saveUninitialized: true,
cookie: { maxAge: 30000 } // 例如,设置会话有效期为30秒
}));
用法:在登录接口完成后设置,退出登录之后清空销毁,在每个接口处验证一下是否含有session的值
app.post('/login', (req, res) => {
// 假设用户验证成功
req.session.user = { id: 1, username: 'exampleUser' };
res.send('User logged in');
});
app.get('/profile', (req, res) => {
if (req.session.user) {
res.send(`Welcome, ${req.session.user.username}`);
} else {
res.send('You are not logged in');
}
});
app.get('/logout', (req, res) => {
req.session.destroy(err => {
if (err) {
return res.send('Error logging out');
}
res.send('Logged out');
});
});
这里有个小技巧,每个接口都要判断session,所以根据懒人定律,应该把此处抽离出来。
(2).cookie
cookie进行登陆状态管理配合JWT,我项目中也是使用的这种,基本配置如下:
const jwt = require('jsonwebtoken');
app.post('/login', (req, res) => {
// 假设用户验证成功
const token = jwt.sign({ username: 'exampleUser' }, 'your_secret_key', { expiresIn: '1h' });
res.send({ token });
});
验证token过期时间
用expressJWT验证的时候需要注意:
1.版本问题。
2.jwt通过authorization获取进行验证的,所以需要手动设置一下token。
3.需要设置跨域
tips:试过直接设置头进行跨域,但是发现get请求可以获取到cookie,post请求获取不到,原因不详,所以还是用cors插件。
const expressJWT = require('express-jwt');
//引入跨域中间件
const cors = require('cors');
// 设置跨域 需要用
app.use(cors({
// 设置为本地前端域名,不设置为*是因为需要传cookies,不能违反cors的规范,出于安全考虑
origin: 'http://127.0.0.1:3000', // 允许的源
credentials: true, // 允许凭证
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'Accept', 'Origin', 'Referer'],
exposedHeaders: ['Content-Range', 'Content-Disposition']
}));
4.客户端应该设置
axios.defaults.withCredentials = true;
// 从cookie中读取JWT的中间件
const jwtFromCookie = (req, res, next) => {
const token = req.cookies.jwt; // 从cookie中获取名为'jwt'的cookie值,jwt是自己设置的
if (token) {
req.headers.authorization = `Bearer ${token}`; // 设置Authorization请求头
}
next();
};
// 使用自定义中间件,因为默认jwt验证是从authorization获取的,所以要把authorization设置一下
app.use(jwtFromCookie);
app.use(expressJWT({ secret: 'your_secret_key' }).unless({ path: ['/login'] }));
app.get('/protected', (req, res) => {
res.send(`Hello, ${req.user.username}`);
});
若验证不通过需抛出错误,填写错误中间件
function errorHandler(err, req, res, next) {
let statusCode = err.statusCode || 500;
let message = err.message || 'Internal Server Error';
//主要是这部分
if (err.name === 'UnauthorizedError') {
console.log('err.code === credentials_required',)
return res.status(401).json({
success: false,
error: {
status: 401,
message: 'token过期或无效',
},
});
}
// 如果是开发环境,返回更详细的错误信息
if (process.env.NODE_ENV === 'development') {
return res.status(statusCode).json({
success: false,
error: {
status: statusCode,
message: message,
stack: err.stack,
},
});
}
// 如果是生产环境,只返回状态码和简单的错误信息
return res.status(statusCode).json({
success: false,
error: {
status: statusCode,
message: message,
},
});
}
module.exports = errorHandler;
如果token过期则让用户重新进行登录即可。
以上第二点手动设置token也可由前端传过来,后端需返回token给前端进行存储并传回后台,一开始使用直接后台设置,但是发现后台设置的话,比较不容易判断crud的时候是查哪个用户的信息,大多是以下的操作步骤:
a.用户登录后,后台会返回token给前端
b.前端拿到token后,在请求拦截器那里设置一下authorization,传给后台
c.后台拿到后会经过jwt校验登陆状态,成功后在每个接口处可以获取到信息req.user,再做crud操作。
具体代码等整理后在进行发布....