react+koa2+mysql零門(mén)檻的全棧體驗(yàn)店读,附上完整項(xiàng)目分享

寫(xiě)在前面

本文適用于對(duì)后端開(kāi)發(fā)領(lǐng)域較為陌生的初級(jí)前端開(kāi)發(fā)小伙伴屯断,如果你想一個(gè)人搞定一整個(gè)項(xiàng)目(服務(wù)端接口定義開(kāi)發(fā)+前端頁(yè)面渲染+數(shù)據(jù)庫(kù)搭建+服務(wù)器搭建+部署上線)殖演,希望這篇文章能給你帶來(lái)一點(diǎn)點(diǎn)參考價(jià)值趴久。

項(xiàng)目簡(jiǎn)介

實(shí)現(xiàn)簡(jiǎn)單的登錄彼棍、表單的增刪改查、文件上傳等功能。以下是項(xiàng)目地址:
前端源碼
服務(wù)端源碼(ps:數(shù)據(jù)庫(kù)rnpxDemo.sql在主目錄下)

技術(shù)棧

前端

主要基于React全家桶畦幢,UI框架選用ant-design-pro宇葱。
前端部分不做過(guò)多贅述,有興趣的可以直接查看源碼诸尽。

服務(wù)端

服務(wù)端采用Nodejs您机,框架采用的是Koa2.0

目錄結(jié)構(gòu)說(shuō)明

├── bin
│   ├── index.js # 項(xiàng)目入口文件
│   ├── www # 配置引入
├── config
│   ├── config.js # 配置項(xiàng)目
│   ├── db.js # 數(shù)據(jù)庫(kù)配置
│   ├── redis # redis配置
│   ├── render.js # 數(shù)據(jù)接口渲染
├── app
│   ├── controllers # 操作層 執(zhí)行服務(wù)端模板渲染际看,json接口返回?cái)?shù)據(jù)仲闽,頁(yè)面跳轉(zhuǎn)
│   │   ├── admin.js
│   │   ├── index.js
│   │   ├── user.js
│   │   └── work.js
│   ├── middleware # 中間件集合
│   │   └── index.js
│   ├── models # 數(shù)據(jù)模型層 執(zhí)行數(shù)據(jù)操作
│   │   └── user.js
│   ├── routers # 路由層 控制路由
│   │   ├── user.js # /user/* 子路由
│   │   ├── home.js # 主頁(yè)子路由
│   │   ├── index.js # 子路由匯總文件
├── static  #靜態(tài)資源

數(shù)據(jù)庫(kù)

數(shù)據(jù)庫(kù)采用Mysql赖欣,框架采用的是Sequelize,是一個(gè)基于promise的關(guān)系型數(shù)據(jù)庫(kù)Node.js ORM框架,用起來(lái)還蠻方便验庙,推薦使用壶谒。
安裝mySql數(shù)據(jù)庫(kù)
可以參考官方文檔
這里順便推薦一款比較實(shí)用的數(shù)據(jù)庫(kù)管理工具:Navicat Premium

集群管理

集群管理采用PM2汗菜,是一個(gè)帶有負(fù)載均衡功能的 Node 應(yīng)用的進(jìn)程管理器。pm2的安裝和使用非常容易巡揍,具體可參考pm2的安裝和使用

image

API文檔工具

這里再分享一個(gè)能自動(dòng)生成API文檔的庫(kù)koa2-swagger-ui,可以把 json 文件的 api 數(shù)據(jù)渲染成 swagger ui阱当,具體使用方法可以參考項(xiàng)目代碼糜工。

npm i koa2-swagger-ui -D
image

服務(wù)端配置說(shuō)明

www入口文件配置

const koa = require("koa");
const koaSwagger = require('koa2-swagger-ui');
const router = require('../app/routers/index');
const path = require('path');
const koaBody = require('koa-body');
const { cross, onerror, onheader } = require("../app/middleware");
const pkg = require("../package.json");
const app = new koa();
app.use(onerror()); // 系統(tǒng)錯(cuò)誤,統(tǒng)一錯(cuò)誤捕獲
/*更多的配置可以拉取倉(cāng)庫(kù)代碼進(jìn)行查看*/

數(shù)據(jù)庫(kù)配置

配置文件路徑:/config/db.js
針對(duì)不同環(huán)境油坝,配置不同的數(shù)據(jù)庫(kù)地址:

const Sequelize = require('sequelize');
const { env } = require("./config");
let config = {};
switch (env) {
    case "dev":
        config = {
            database: 'testDB', // 使用哪個(gè)數(shù)據(jù)庫(kù)
            username: 'root', // 用戶名
            password: 'dev@123', // 口令
            host: '127.0.01', // 主機(jī)名
            port: 3306 // 端口號(hào)澈圈,MySQL默認(rèn)3306
        };
        break;
    case "test":
        config = {
            database: 'testDB', // 使用哪個(gè)數(shù)據(jù)庫(kù)
            username: 'root', // 用戶名
            password: 'test@123', // 口令
            host: 'test.mysql.rds.aliyuncs.com', // 主機(jī)名
            port: 3306 // 端口號(hào)瞬女,MySQL默認(rèn)3306
        };
        break;
    case "prd":
        config = {
            database: 'testDB', // 使用哪個(gè)數(shù)據(jù)庫(kù)
            username: 'root', // 用戶名
            password: 'prd@123', // 口令
            host: 'prd.mysql.rds.aliyuncs.com', // 主機(jī)名
            port: 3306 // 端口號(hào)努潘,MySQL默認(rèn)3306
        };
        break;
}

export const sequelize = new Sequelize(config.database, config.username, config.password, {
    host: config.host,
    dialect: 'mysql',
    pool: {
        max: 5,
        min: 0,
        idle: 30000
    }
});

路由配置

配置文件路徑:/app/routers/index.js

const router = new require('koa-router')()
const home = require('./home')
const user = require("./backend/user")
const swaggerJSDoc = require('swagger-jsdoc')
const pkg = require("../../package.json")
const swaggerDefinition = {
    info: {
        title: 'API',
        version: '1.0.0',
        description: 'API',
    },
    host: `localhost:${pkg.port}`,
    basePath: '/' // Base path (optional)
};
const options = {
    swaggerDefinition,
    apis: ['./app/routers/backend/*.js', './app/routers/front/*.js'], // 寫(xiě)有注解的router的存放地址
};
const swaggerSpec = swaggerJSDoc(options)
// 通過(guò)路由獲取生成的注解文件
router.get('/swagger.json', async function (ctx) {
    ctx.set('Content-Type', 'application/json');
    ctx.body = swaggerSpec;
})

router.use('', home.routes())
router.use('/backend/user', user.routes())
module.exports = router

中間件集合

通俗的講:中間件就是匹配路由之前或者匹配路由完成做的一系列的操作渤刃。

const { renderData } = require("../../config/render")
const { log } = require("../../util/utils")
const koajwt = require('koa-jwt')
/**
 * 跨域處理
 * @example app.use(cross())
 */
export function cross() {
    return async function (ctx, next) {
        ctx.set('Access-Control-Allow-Origin', ctx.get("Origin") == null ? "*" : ctx.get("Origin"));
        ctx.set('Access-Control-Allow-Methods', ctx.get("Access-Control-Request-Method") || "PUT, GET, POST, DELETE, OPTIONS");
        ctx.set('Access-Control-Allow-Headers', ctx.get("Access-Control-Request-Headers") || "Content-Type");
        ctx.set('Access-Control-Allow-Credentials', "true");
        if (ctx.method == "OPTIONS") {
            ctx.status = 204
        }
        await next();
    }
}
/**
 * 兼容路由后綴卖子,如 xxx/xxx.json == xxx/xxx 地址
 * @param suffixes string[] 后綴列表
 * @example app.use(adaptRouteSuffix([".json"]))
 */
export function adaptRouteSuffix(suffixes = []) {
    return async function (ctx, next) {
        suffixes.forEach(e => {
            var re = new RegExp(e);
            ctx.path = ctx.path.replace(re, "")
        })
        await next();
    }
}
/**
 * 錯(cuò)誤日志捕獲
 * @example app.use(onerror())
 */
export function onerror() {
    return async function (ctx, next) {
        return next().catch((err) => {
            if (err.status === 401) {
                ctx.status = 200;
                renderData(ctx, null, 401, "用戶沒(méi)有權(quán)限(令牌洋闽、用戶名诫舅、密碼錯(cuò)誤)", err.message)
            } else if (ctx.status === 404) {
                var err = new Error('Not Found');
                err.status = 404;
                renderData(ctx, null, 404, "系統(tǒng)繁忙刊懈,請(qǐng)聯(lián)系管理員", err.message);
            } else {
                renderData(ctx, null, -1, "服務(wù)端錯(cuò)誤", err.message);
                log(err.message);
            }
        })
    }
}

/** 
 * 延遲
 * @example await delay(5000) // 延遲5s
 */
export function delay(time) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve();
        }, time);
    });
};
/**
 * 請(qǐng)求頭攔截
 */
export function onheader() {
    return koajwt({
        secret: 'polo_token'
    }).unless({
        path: [/\/user\/login|swagger|front/]
    })
}

基本功能展示

登錄

登錄模塊虚汛,這里就介紹下服務(wù)端的實(shí)現(xiàn)皇帮,主要依賴兩個(gè)插件属拾,jsonwebtoken(jwt)和koa-jwt冷溶,前者主要負(fù)責(zé)加密逞频,生成token返回給前端虏劲,后者實(shí)現(xiàn)對(duì)token的校驗(yàn)褒颈,在中間件中實(shí)現(xiàn)請(qǐng)求頭攔截谷丸,驗(yàn)證失敗返回401刨疼。具體可以參考代碼./app/controllers/user.js鹅龄。

/**
 * 用戶登錄
 * @return 用戶登錄成功對(duì)象
 */
export async function login(ctx) {
    const data = ctx.request.body;
    if (!data.name || !data.password) {
        renderData(ctx, null, 102, '參數(shù)不合法');
    }
    const result = await GetUser({
        name: data.userName,
        password: data.password
    })
    if (result !== null) {
        const token = jwt.sign({
            name: result.name,
            password: result.password
        }, 'polo_token', { expiresIn: '2h' });
        const userData = {
            userName: result.name,
            token: 'Bearer ' + token
        }
        renderData(ctx, userData, 0, '登錄成功');
    } else {
        renderData(ctx, null, 101, '用戶名或密碼錯(cuò)誤');
    }
}
/**
 * 請(qǐng)求頭攔截
 */
export function onheader() {
    return koajwt({
        secret: 'polo_token'
    }).unless({
        path: [/\/user\/login|swagger|front/]
    })
}
image

后臺(tái)部分內(nèi)容展示

image

image

image

image

寫(xiě)在最后

以上文章是我前段時(shí)間全棧開(kāi)發(fā)已上線項(xiàng)目的簡(jiǎn)略版分享玷坠,文章開(kāi)頭已附上了該項(xiàng)目的前后端源碼,順便提供了測(cè)試數(shù)據(jù)(rnpxDemo.sql),只需把代碼克隆下來(lái)并保證電腦已安裝所需軟件樟凄,另有不足之處也請(qǐng)多多指正缝龄,非常感謝挂谍。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末口叙,一起剝皮案震驚了整個(gè)濱河市庐扫,隨后出現(xiàn)的幾起案子仗哨,更是在濱河造成了極大的恐慌厌漂,老刑警劉巖苇倡,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旨椒,死亡現(xiàn)場(chǎng)離奇詭異堵漱,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)示惊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)米罚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)丈探,“玉大人碗降,你說(shuō)我怎么就攤上這事』豕” “怎么了弧圆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵搔预,是天一觀的道長(zhǎng)叶组。 經(jīng)常有香客問(wèn)我甩十,道長(zhǎng),這世上最難降的妖魔是什么鸭轮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任窃爷,我火速辦了婚禮按厘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘卿堂。我一直安慰自己,他們只是感情好漓藕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布享钞。 她就那樣靜靜地躺著栗竖,像睡著了一般渠啤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上份名,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音壶栋,去河邊找鬼。 笑死琉兜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的漆际。 我是一名探鬼主播奸汇,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼擂找,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼贯涎!你這毒婦竟也來(lái)了塘雳?” 一聲冷哼從身側(cè)響起败明,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤太防,失蹤者是張志新(化名)和其女友劉穎妻顶,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蜒车,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡讳嘱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了酿愧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沥潭。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嬉挡,靈堂內(nèi)的尸體忽然破棺而出叛氨,到底是詐尸還是另有隱情,我是刑警寧澤棘伴,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站仁连,受9級(jí)特大地震影響使鹅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜裁厅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一袋励、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧磺芭,春花似錦甘邀、人聲如沸坞琴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鳄梅。三九已至,卻和暖如春孙蒙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浑测。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工岖圈, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人导匣。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓缓待,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子接奈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容