全棧項目(react+ant-desgin-mobile,express.js+mysql)

淺嘗全棧項目,體驗(yàn)前后端開發(fā)異同,會把開發(fā)過程中的一些亮點(diǎn)和難點(diǎn)記錄下來,已備不時之需。
前端:react + ant-design-mobile
后端:express.js + mysql

目錄結(jié)構(gòu)

  • 準(zhǔn)備階段
  • 數(shù)據(jù)庫
  • 后端-express
  • 前端-react

準(zhǔn)備階段:

一.準(zhǔn)備階段

1.數(shù)據(jù)庫建表
(1).用戶基礎(chǔ)信息表


用戶基礎(chǔ)信息表

(2).清單表(業(yè)務(wù)表)


清單表

2.后端構(gòu)建項目

下載express-generator
npm install -g express-generator
構(gòu)建項目
express --view=ejs myproject
安裝依賴包
cd myproject
啟動項目
npm start

目錄結(jié)構(gòu)通常如下:

  • app.js:應(yīng)用的主文件,設(shè)置中間件和路由。
  • bin/www:啟動腳本,設(shè)置服務(wù)器端口并啟動服務(wù)器。
  • public/:靜態(tài)文件目錄,存放 CSS、JavaScript 和圖片等。
  • routes/:路由目錄,定義應(yīng)用的路由。
  • views/:視圖目錄,存放模板文件。
  • node_modules/:依賴包目錄。

3.前端構(gòu)建項目

npm create-react-app my-app

cd my-app

npm start

目錄結(jié)構(gòu)通常如下:

  • node_modules:安裝依賴
  • public:公共文件
  • src:主體文件
  • .gitignore:忽略文件
  • package.json:版本管理信息
  • package-lock.json:版本管理信息

二.數(shù)據(jù)庫

作為一個前端開發(fā)的打工仔,凡事都是以前端思維為導(dǎo)向,大學(xué)的數(shù)據(jù)庫知識不扎實(shí),導(dǎo)致在一開始設(shè)立數(shù)據(jù)庫時,磕磕絆絆,但好在結(jié)果還算可以。

數(shù)據(jù)庫連接

1.建立mysql單個數(shù)據(jù)庫連接對象,并連接

const mysql = require("mysql"); //引入mysql
// 設(shè)置MySQL連接配置
const base_url = 'your_url'
const connection = mysql.createConnection({
    host: base_url,
    user: "root",  //數(shù)據(jù)庫賬號
    password: "your_code", //數(shù)據(jù)庫密碼
    database: "test", //數(shù)據(jù)庫名稱
    timezone: "Asia/Shanghai"
});
// 連接到MySQL數(shù)據(jù)庫
connection.connect((error) => {
    if (error) throw error;
    //連接成功打印結(jié)果`如果連接失敗,記得查看數(shù)據(jù)庫是否開啟`
});

2.建立數(shù)據(jù)庫連接池

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);
});

兩種方法的取舍,取決于使用的場景:
如果是高并發(fā),需要頻繁訪問多個數(shù)據(jù)庫的情況下,用createPool。
如果是復(fù)雜sql查詢,多表鏈表查詢,則用createConnection。

三.后端

1.基礎(chǔ)配置

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格式的數(shù)據(jù)
app.use(bodyParser.urlencoded({ extended: true })); // 解析URL編碼的數(shù)據(jù)
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');



// 設(shè)置跨域 需要用
app.use(cors({
  // 設(shè)置為本地前端域名,不設(shè)置為*是因?yàn)樾枰獋鱟ookies,不能違反cors的規(guī)范,出于安全考慮
  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']
}));




//驗(yàn)證jwt 驗(yàn)證這塊最好放在其他中間件的下面,因?yàn)橛械闹虚g件會影響
const expressjwt = require("express-jwt");

// 設(shè)置一個私鑰
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.登錄狀態(tài)管理

(1).session

session是會話管理,在服務(wù)端管理用戶狀態(tài),基本配置如下:

const session = require('express-session');

app.use(session({
  secret: 'your_secret_key', // 用于簽名會話ID的密鑰
  resave: false,
  saveUninitialized: true,
  cookie: { maxAge: 30000 } // 例如,設(shè)置會話有效期為30秒
}));

用法:在登錄接口完成后設(shè)置,退出登錄之后清空銷毀,在每個接口處驗(yàn)證一下是否含有session的值

app.post('/login', (req, res) => {
  // 假設(shè)用戶驗(yàn)證成功
  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,所以根據(jù)懶人定律,應(yīng)該把此處抽離出來。

(2).cookie

cookie進(jìn)行登陸狀態(tài)管理配合JWT,我項目中也是使用的這種,基本配置如下:

const jwt = require('jsonwebtoken');

app.post('/login', (req, res) => {
  // 假設(shè)用戶驗(yàn)證成功
  const token = jwt.sign({ username: 'exampleUser' }, 'your_secret_key', { expiresIn: '1h' });
  res.send({ token });
});

驗(yàn)證token過期時間
用expressJWT驗(yàn)證的時候需要注意:
1.版本問題。
2.jwt通過authorization獲取進(jìn)行驗(yàn)證的,所以需要手動設(shè)置一下token。
3.需要設(shè)置跨域
tips:試過直接設(shè)置頭進(jìn)行跨域,但是發(fā)現(xiàn)get請求可以獲取到cookie,post請求獲取不到,原因不詳,所以還是用cors插件。

const expressJWT = require('express-jwt');

//引入跨域中間件
const cors = require('cors');
// 設(shè)置跨域 需要用
app.use(cors({
  // 設(shè)置為本地前端域名,不設(shè)置為*是因?yàn)樾枰獋鱟ookies,不能違反cors的規(guī)范,出于安全考慮
  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.客戶端應(yīng)該設(shè)置

axios.defaults.withCredentials = true;
// 從cookie中讀取JWT的中間件
const jwtFromCookie = (req, res, next) => {
  const token = req.cookies.jwt; // 從cookie中獲取名為'jwt'的cookie值,jwt是自己設(shè)置的

  if (token) {
    req.headers.authorization = `Bearer ${token}`; // 設(shè)置Authorization請求頭
  }
  next();
};


// 使用自定義中間件,因?yàn)槟J(rèn)jwt驗(yàn)證是從authorization獲取的,所以要把a(bǔ)uthorization設(shè)置一下
app.use(jwtFromCookie);

app.use(expressJWT({ secret: 'your_secret_key' }).unless({ path: ['/login'] }));

app.get('/protected', (req, res) => {
  res.send(`Hello, ${req.user.username}`);
});

若驗(yàn)證不通過需拋出錯誤,填寫錯誤中間件

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過期或無效',
            },
        });
    }

    // 如果是開發(fā)環(huán)境,返回更詳細(xì)的錯誤信息
    if (process.env.NODE_ENV === 'development') {
        return res.status(statusCode).json({
            success: false,
            error: {
                status: statusCode,
                message: message,
                stack: err.stack,
            },
        });
    }

    // 如果是生產(chǎn)環(huán)境,只返回狀態(tài)碼和簡單的錯誤信息
    return res.status(statusCode).json({
        success: false,
        error: {
            status: statusCode,
            message: message,
        },
    });
}

module.exports = errorHandler;

如果token過期則讓用戶重新進(jìn)行登錄即可。

以上第二點(diǎn)手動設(shè)置token也可由前端傳過來,后端需返回token給前端進(jìn)行存儲并傳回后臺,一開始使用直接后臺設(shè)置,但是發(fā)現(xiàn)后臺設(shè)置的話,比較不容易判斷crud的時候是查哪個用戶的信息,大多是以下的操作步驟:
a.用戶登錄后,后臺會返回token給前端
b.前端拿到token后,在請求攔截器那里設(shè)置一下authorization,傳給后臺
c.后臺拿到后會經(jīng)過jwt校驗(yàn)登陸狀態(tài),成功后在每個接口處可以獲取到信息req.user,再做crud操作。
具體代碼等整理后在進(jìn)行發(fā)布....

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容