node + koa + ts 構建應用

適合新手或前端學習 node.js 作後臺的起步教程javascript

內容:

  • 項目構建和配置
  • 先後端請求 header 基本配置說明
  • 接口編寫和傳參處理
  • 上傳圖片
  • 連接數據庫和接口操做
  • 登陸註冊用戶模塊
  • session 的使用(這裏我用的是本身寫的一個模塊)
  • 增刪改查功能
  • 項目構建推送到線上

這裏我使用 typescript 去編寫的理由是由於很是好用的類型提示和代碼追蹤,因此在純 javascript 編程的項目中,typescript 是最好維護和閱讀的,這裏建議使用 vs code 這個代碼編輯器。第一次寫後臺應用,因此編程思路可能跟純後臺的編程思惟有所不一樣,不過我習慣標準的 jsdoc 註釋規範去編碼,因此應該是不存在代碼閱讀難度的。html

代碼地址:node-koa前端

先來看下目錄結構java

項目構建和配置

1. cd project 並建立 src 目錄
mkdir src
複製代碼
2. 初始化 package.json,以後的全部配置和命令都會寫在裏面
npm init
複製代碼
3. 安裝 koa 和對應的路由 koa-router
npm install koa koa-router 
複製代碼
4. 安裝 TypeScript 對應的類型檢測提示
npm install --save-dev @types/koa @types/koa-router 
複製代碼
5. 而後就是 TypeScript 熱更新編譯
npm install --save-dev typescript ts-node nodemon
複製代碼
這裏會有個坑(這裏使用的是window環境下)就是 ts-nodenodemon 這兩個須要全局安裝才能執行熱更新的命令
npm install -g -force ts-node nodemon
複製代碼
6. 再配置一下 package.json 設置
"scripts": {
    "start": "tsc && node dist/index.js",
    "watch-update": "nodemon --watch 'src/**/*' -e ts,tsx --exec 'ts-node' ./src/index.ts"
},
複製代碼
若是執行不了 npm watch-update 那就執行 nodemon --watch 'src/**/*' -e ts,tsx --exec 'ts-node' ./src/index.ts
不肯定是否 window 環境下的問題仍是 npm 的問題,項目首次建立並執行的時候,全部依賴均可以本地安裝而且 npm watch-update 也能夠完美執行

可是再次打開項目的時候就出錯了,目前還沒找到緣由,不過以上方法能夠解決node

7. 最後選裝的中間件 koa-body 中間件做爲解析POST傳參和上傳圖片用
npm install koa-body
複製代碼
8. 配置代碼參數

modules/config.ts 項目設置mysql

class ModuleConfig {
    /** 端口號 */
    public readonly port = 1995;
    /** 數據庫配置 */
    public readonly db = {
        host: 'localhost',
        user: 'root',
        password: 'root',
        /** 數據庫名 */
        database: 'test',
        /** 連接上限次數 */
        connection_limit: 10
    }
    /** 上傳圖片存放目錄 */
    public readonly upload_path = 'public/upload/images/';
    /** 上傳圖片大小限制 */
    public readonly upload_img_size = 5 * 1024 * 1024;
    // formData.append('img', file)
    /** 前端上傳圖片時約定的字段 */
    public readonly upload_img_name = 'img';    
    /** 用戶臨時表 */
    public readonly user_file = 'public/user.json';
    /** token 長度 */
    public readonly token_size = 28;
}
/** 項目配置 */
const config = new ModuleConfig();
export default config;
複製代碼

先後端請求 header 基本配置說明

index.ts 文件下git

import * as Koa from 'koa';
import * as koaBody from 'koa-body';
import config from './modules/config';          // 項目配置
import router from './api/main';                // 路由模塊,後面有說
import stateInfo from './modules/state';        // 自定義的請求返回格式模塊
import session from './modules/session';        // 自定義的 session 模塊,後面有說
import './api/apiUser';                         // 用戶模塊
import './api/apiUpload';                       // 上傳文件模塊
import './api/apiTest';                         // 基礎測試模塊
import './api/apiTodo';                         // 用戶列表模塊

const App = new Koa();

// 先統一設置請求頭信息...
App.use(async (ctx, next) => {
    ctx.set({
        'Access-Control-Allow-Origin': '*', // 打開跨域
        'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept Authorization',
    });
    
    // 若是前端設置了 XHR.setRequestHeader('Content-Type', 'application/json')
    // ctx.set 就必須攜帶 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept Authorization'
    // 若是前端設置了 XHR.setRequestHeader('Authorization', 'xxxx') 一樣的就是上面 Authorization 字段
    // 而且這裏要轉換一下狀態碼
    if (ctx.request.method === 'OPTIONS') {
        ctx.response.status = 200;
    }
    try {
        await next();
    } catch (err) {
        ctx.response.status = err.statusCode || err.status || 500;
        ctx.response.body = {
            message: err.message
        }
    }
});
// 使用中間件處理 post 傳參 和上傳圖片
App.use(koaBody({
    multipart: true,
    formidable: {
        maxFileSize: config.upload_img_size
    }
}));
// 開始使用路由
App.use(router.routes())
複製代碼

完事以後運行項目 (代碼熱更新)github

nodemon --watch 'src/**/*' -e ts,tsx --exec 'ts-node' ./src/index.ts
複製代碼

接口編寫和傳參處理

先來定義一個路由而後導出來使用,後面可能會有多個模塊的接口,因此所有都是基於這個去使用,index.ts 也是 api/main.ts 文件下sql

import * as Router from 'koa-router';       
/** api路由模塊 */
const router = new Router();
export default router;
複製代碼

api/apiTest.ts 文件下,來寫個不用鏈接數據庫的 GETPOST 請求做爲測試用,而且接收參數,寫好以後在前端請求,前端的代碼我就不作說明了,看註釋應該懂,聲明文件都寫好了。寫完以後再到前端頁面請求一下是否正確跑通了。typescript

import router from './main';
import html from '../modules/template';
import stateInfo from '../modules/state'; // 這個是我寫好的狀態數據返回到前端的一個統一格式模塊,具體看代碼,這裏不作過多描述。
// '/*' 監聽所有
router.get('/', (ctx, next) => {
    // 指定返回類型
    ctx.response.type = 'html';
    ctx.body = html;
    console.log('根目錄');
    // 302 重定向到其餘網站
    // ctx.status = 302;
    // ctx.redirect('https://www.baidu.com');
})
// get 請求
router.get('/getHome', (ctx, next) => {
    /** 接收參數 */
    const params: object | string = ctx.query || ctx.querystring;
    console.log('get /getHome', params);
    ctx.body = stateInfo.getSuccessData({
        method: 'get',
        port: 1995,
        time: Date.now()
    });
})
// post 請求
router.post('/sendData', (ctx, next) => {
    /** 接收參數 */
    const params: object = ctx.request.body || ctx.params;
    console.log('post /sendData', params);
    const result = {
        data: '請求成功'
    }
    ctx.body = stateInfo.getSuccessData(result, 'post success')
})
複製代碼

上傳圖片

modules/api/apiUpload.ts 文件下,這裏我尚未用到七牛或者其餘平臺的接口,只是簡單模擬了一個上傳異步的方法代替,固然,有老哥作過的能夠告訴我~

import router from './main';
import * as fss from 'fs';
import * as path from 'path';
import config from '../modules/config';
import stateInfo from '../modules/state';
// 上傳圖片
router.post('/uploadImg', async (ctx, next) => {
    const file = ctx.request.files[config.upload_img_name];
    let fileName = ctx.request.body.name || `img_${Date.now()}`;
    fileName = `${fileName}.${file.name.split('.')[1]}`;
    // 建立可讀流
    const render = fs.createReadStream(file.path);
    const filePath = path.join(config.upload_path, fileName); // 這裏說明一下,文件是默認保存到配置好的本地目錄下,至於上到七牛那邊話,可能須要其餘操做,目前還沒作到那一步
    const fileDir = path.join(config.upload_path);
    // 判斷文件所在目錄是否存在(這兩行代碼能夠忽略不寫)
    if (!fs.existsSync(fileDir)) {
        fs.mkdirSync(fileDir);
    }
    // 建立寫入流
    const upStream = fs.createWriteStream(filePath);
    render.pipe(upStream);
    const result = {
        image: '',
        file: ''
    }
    /** 模擬上傳到七牛雲 */
    function uploadApi() {
        return new Promise(function (resolve, reject) {
            const delay = Math.floor(Math.random() * 5) * 100 + 500;
            setTimeout(() => {
                result.image = `http://${ctx.headers.host}/${config.upload_path}${fileName}`;
                result.file = `${config.upload_path}${fileName}`;
                resolve();
            }, delay);
        });
    }
    await uploadApi();
    ctx.body = stateInfo.getSuccessData(result, '上傳成功');
})
複製代碼

連接數據庫和接口操做

modules/api/mysql.ts 文件下,這裏封裝了一個數據庫增刪改查的方法,以後全部的數據庫操做都是經過這個方法去完成。

import * as mysql from 'mysql';         
import config from './config';
import { mysqlErrorType } from './interfaces'; // 這個是TS數據類型模塊,這裏也不作說明,用過TS的應該知道
/** 數據庫 */
const pool = mysql.createPool({
    host: config.db.host,
    user: config.db.user,
    password: config.db.password,
    database: config.db.database
});
/** * 數據庫增刪改查 * @param command 增刪改查語句 * @param value 對應的值 */
export default function query(command: string, value?: Array<any>): Promise<any> {
    /** 錯誤信息 */
    let errorInfo: mysqlErrorType = null;
    return new Promise((resolve, reject) => {
        pool.getConnection((error: any, connection) => {
            if (error) {
                errorInfo = {
                    info: error,
                    message: '數據庫鏈接出錯'
                }
                reject(errorInfo);
            } else {
                const callback: mysql.queryCallback = (error: any, results, fields) => {
                    connection.release();
                    if (error) {
                        errorInfo = {
                            info: error,
                            message: '數據庫增刪改查出錯'
                        }
                        reject(errorInfo);
                    } else {
                        resolve({ results, fields });
                    }
                }
                if (value) {
                    pool.query(command, value, callback);
                } else {
                    pool.query(command, callback);
                }
            }
        });
    });
}
複製代碼

這裏我用到的本地服務是用(upupw)搭建,超簡單的操做,數據庫表工具是navicat

upupw下載地址 navicat下載地址

我是一個徹底不懂 mysql 的前端,因此這裏的操做我是邊問個人 PHP 同事邊作筆記邊操做的,因此就沒什麼好說的了,可能看這篇文章的你會知道這些。

登陸註冊用戶模塊

user表的格式

api/apiUser.ts 文件下

import router from './main';
import query from '../modules/mysql';
import stateInfo from '../modules/state';
import session from '../modules/session'; // 這個模塊是我按照本身的思路去自行寫的,等下再說明
import config from '../modules/config';
import { mysqlErrorType, mysqlQueryType, userInfoType } from '../modules/interfaces';

// 註冊
router.post('/register', async (ctx) => {
    /** 接收參數 */
    const params: userInfoType = ctx.request.body;
    /** 返回結果 */
    let bodyResult = null;
    /** 帳號是否可用 */
    let validAccount = false;
    // console.log('註冊傳參', params);
    if (!/^[A-Za-z0-9]+$/.test(params.account)) {
        return ctx.body = stateInfo.getFailData('註冊失敗!帳號必須爲6-12英文或數字組成');
    }
    if (!/^[A-Za-z0-9]+$/.test(params.password)) {
        return ctx.body = stateInfo.getFailData('註冊失敗!密碼必須爲6-12英文或數字組成');
    }
    if (!params.name.trim()) {
        params.name = '用戶未設置暱稱';
    }
    // 先查詢是否有重複帳號
    await query(`select account from user where account = '${ params.account }'`).then((res: mysqlQueryType) => {
        // console.log('註冊查詢', res);
        if (res.results.length > 0) {
            bodyResult = stateInfo.getFailData('該帳號已被註冊');
        } else {
            validAccount = true;
        }
    }).catch((error: mysqlErrorType) => {
        // console.log('註冊查詢錯誤', error);
        bodyResult = stateInfo.getFailData(error.message);
    })
    // 再寫入表格
    if (validAccount) {
        await query('insert into user(account, password, name) values(?,?,?)', [params.account, params.password, params.name]).then((res: mysqlQueryType) => {
            // console.log('註冊寫入', res);
            bodyResult = stateInfo.getSuccessData(params, '註冊成功');
        }).catch((error: mysqlErrorType) => {
            // console.log('註冊寫入錯誤', error);
            bodyResult = stateInfo.getFailData(error.message);
        })
    }
    ctx.body = bodyResult;
})

// 登陸
router.post('/login', async (ctx) => {
    /** 接收參數 */
    const params: userInfoType = ctx.request.body;
    /** 返回結果 */
    let bodyResult = null;
    // console.log('登陸', params);
    if (params.account.trim() === '') {
        return ctx.body = stateInfo.getFailData('登陸失敗!帳號不能爲空');
    }
    if (params.password.trim() === '') {
        return ctx.body = stateInfo.getFailData('登陸失敗!密碼不能爲空');
    }
    // 先查詢是否有當前帳號
    await query(`select * from user where account = '${ params.account }'`).then((res: mysqlQueryType) => {
        // console.log('登陸查詢', res.results);
        // 再判斷帳號是否可用
        if (res.results.length > 0) {
            const data: userInfoType = res.results[0];
            // 最後判斷密碼是否正確
            if (data.password == params.password) {
                data.token = session.setRecord(data);
                bodyResult = stateInfo.getSuccessData(data ,'登陸成功');
            } else {
                bodyResult = stateInfo.getFailData('密碼不正確');
            }
        } else {
            bodyResult = stateInfo.getFailData('該帳號不存在,請先註冊');
        }
    }).catch((error: mysqlErrorType) => {
        // console.log('登陸查詢錯誤', error);
        bodyResult = stateInfo.getFailData(error.message);
    })
    ctx.body = bodyResult;
})

// 獲取用戶信息
router.get('/getUserInfo', async (ctx) => {
    const token: string = ctx.header.authorization;
    /** 接收參數 */
    const params = ctx.request.body;
    /** 返回結果 */
    let bodyResult = null;
    console.log('getUserInfo', params, token);
    if (token.length != config.token_size) {
        return ctx.body = stateInfo.getFailData('token 不正確');
    }
    let state = session.updateRecord(token);
    if (!state.success) {
        return ctx.body = stateInfo.getFailData(state.message);
    }
    await query(`select * from user where account = '${ state.info.account }'`).then((res: mysqlQueryType) => {
        // 判斷帳號是否可用
        if (res.results.length > 0) {
            const data: userInfoType = res.results[0];
            bodyResult = stateInfo.getSuccessData(data);
        } else {
            bodyResult = stateInfo.getFailData('該帳號不存在,可能已經從數據庫中刪除');
        }
    }).catch((error: mysqlErrorType) => {
        bodyResult = stateInfo.getFailData(error.message);
    })
    ctx.body = bodyResult;
})

// 退出登陸
router.get('/logout', ctx => {
    const token: string = ctx.header.authorization;
    /** 接收參數 */
    const params = ctx.request.body;
    console.log('logout', params, token);
    if (token.length != config.token_size) {
        return ctx.body = stateInfo.getFailData('token 不正確');
    }
    const state = session.removeRecord(token);
    if (state) {
        return ctx.body = stateInfo.getSuccessData('退出登陸成功');
    } else {
        return ctx.body = stateInfo.getFailData('token 不存在');
    }
})
複製代碼

session 的使用(這裏我用的是本身寫的一個模塊)

實現思路:利用js內存進行讀寫用戶的token信息,只在對內存寫的時候(異步寫入防止阻塞)把信息以json格式寫入到json文件中,而後實例化的時候讀取上次紀錄的token信息並把過時的剔除掉。

思路實現過程:

  1. public/目錄下新建一個user.json的文件做爲一張臨時的 token 紀錄表
  2. 而後定義一個 ModuleSession 的模塊,userRecord 這個私有的屬實是 {} ,而後以 token 做爲key去存儲用戶信息,這個信息裏面帶有一個參數 online 意思是在線的時間,以後取值作判斷的時候會用到
  3. ModuleSession 的模塊中,對外暴露的只有3個方法:
    1. setRecord 首次設置紀錄並返回 token 登陸用,而且紀錄在 userRecord 裏面,再寫入到臨時表 user.json
    2. updateRecord 這個是每次請求的時候都會先執行的方法,用來判斷前端傳過來的 token 是否在 userRecord 裏面,如在存在再判斷 userRecord[token].online 及其餘操做,所有經過判斷就說明當前 token 沒問題而且更新 userRecord[token].online 爲當前時間,最後再寫入到臨時表去。這個過程可能比較複雜,仍是看代碼比較好理解點。
    3. removeRecord 這個邏輯就簡單了,直接從 userRecord 中刪除當前token,退出登陸用
  4. ModuleSession 在實例化時,先從 user.json 臨時表裏面讀取上次寫入的信息,而後剔除過期的token,保證數據同步。
  5. 最後就是在代碼運行的過程當中,定時去剔除過期的token,減小讀寫和內存大小的性能,具體仍是看代碼吧。

modules/session.ts 文件下

import * as fs from 'fs';
import config from './config';
import { userRecordType, userInfoType, sessionResultType } from '../modules/interfaces';

class ModuleSession {
    constructor() {
        this.init();
    }

    /** 效期(小時) */
    private maxAge = 12;

    /** 更新 & 檢測時間間隔(10分鐘) */
    private interval = 600000;

    /** 用戶 token 紀錄 */
    private userRecord: userRecordType = {}; 

    /** * 寫入文件 * @param obj 要寫入的對象 */
    private write(obj?: userRecordType) {
        const data = obj || this.userRecord;
        // 同步寫入(貌似不必)
        // fs.writeFileSync(config.user_file, JSON.stringify(data), { encoding: 'utf8' });
        // 異步寫入
        fs.writeFile(config.user_file, JSON.stringify(data), { encoding: 'utf8' }, (err) => {
            if (err) {
                console.log('session 寫入失敗', err);
            } else {
                console.log('session 寫入成功');
            }
        })
    }

    /** 從本地臨時表裏面初始化用戶狀態 */
    private init() {
        const userFrom = fs.readFileSync(config.user_file).toString();
        this.userRecord = userFrom ? JSON.parse(userFrom) : {};
        this.checkRecord();
        // console.log('token臨時表', userFrom, this.userRecord);
    }

    /** 生成 token */
    private getToken(): string {
        const getCode = (n: number): string => {
            let codes = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789';
            let code = '';
            for (let i = 0; i < n; i++) {
                code += codes.charAt(Math.floor(Math.random() * codes.length));
            }
            if (this.userRecord[code]) {
                return getCode(n);
            }
            return code;
        }
        const code = getCode(config.token_size);
        return code;
    }
    
    /** 定時檢測過時的 token 並清理 */
    private checkRecord() {
        const check = () => {
            const now = Date.now();
            let isChange = false;
            for (const key in this.userRecord) {
                if (this.userRecord.hasOwnProperty(key)) {
                    const item = this.userRecord[key];
                    if (now - item.online > this.maxAge * 3600000) {
                        isChange = true;
                        delete this.userRecord[key];
                    }
                }
            }
            if (isChange) {
                this.write();
            }
        }
        // 10分鐘檢測一次
        setInterval(check, this.interval);
        check();
    }

    /** * 設置紀錄並返回 token * @param data 用戶信息 */
    public setRecord(data: userInfoType) {
        const token = this.getToken();
        data.online = Date.now();
        this.userRecord[token] = data;
        this.write();
        return token;
    }

    /** * 更新並檢測 token * @param token */
    public updateRecord(token: string) {
        let result: sessionResultType = {
            message: '',
            success: false,
            info: null
        }

        if (!this.userRecord.hasOwnProperty(token)) {
            result.message = 'token 已過時或不存在';
            return result;
        } 
        
        const userInfo = this.userRecord[token];

        const now = Date.now();

        if (now - userInfo.online > this.maxAge * 3600000) {
            result.message = 'token 已過時';
            return result;
        }

        result.message = 'token 經過驗證';
        result.success = true;
        result.info = userInfo;

        // 更新在線時間並寫入臨時表
        // 這裏優化一下,寫入和更新的時間間隔爲10分鐘,避免頻繁寫入
        if (now - userInfo.online > this.interval) {
            this.userRecord[token].online = now;
            this.write();
        }
        
        return result;
    }

    /** * 從紀錄中刪除 token 紀錄(退出登陸時用) * @param token */
    public removeRecord(token: string) {
        if (this.userRecord.hasOwnProperty(token)) {
            delete this.userRecord[token];
            this.write();
            return true;
        } else {
            return false;
        }
    }

}

/** session 模塊 */
const session = new ModuleSession();

export default session;
複製代碼

增刪改查功能

由於以後的接口都是依賴 token 的,因此這裏我把 token 判斷的代碼抽到 index.ts 中去了

// 先統一設置請求配置 => 跨域,請求頭信息...
App.use(async (ctx, next) => {
    ... // 以前的代碼不變
    
    if (ctx.request.method === 'OPTIONS') {
        ctx.response.status = 200;
    } else {
        /** 過濾掉不用 token 也能夠請求的接口 */
        const rule = /\/register|\/login|\/uploadImg|\/getData|\/postData/;
        /** 請求路徑 */
        const path = ctx.request.path;
        // 這裏進行全局的 token 驗證判斷
        if (!rule.test(path) && path != '/') {
            const token: string = ctx.header.authorization;
            if (token.length != config.token_size) {
                return ctx.body = stateInfo.getFailData(config.token_tip);
            }
            const state = session.updateRecord(token);
            if (!state.success) {
                return ctx.body = stateInfo.getFailData(state.message);
            }
            // 設置 token 信息到上下文中給接口模塊裏面調用
            ctx['the_state'] = state;
        } 
    }
})
複製代碼

先來看下數據庫表結構

而後新建一個 apiTodo.ts 做爲用戶列表的增刪改查接口模塊

import router from './main';
import query from '../modules/mysql';
import stateInfo from '../modules/state';
import { mysqlQueryType, mysqlErrorType, sessionResultType } from '../modules/interfaces';

// 獲取全部列表
router.get('/getList', async (ctx) => {
    const state: sessionResultType = ctx['the_state'];
    /** 返回結果 */
    let bodyResult = null;
    
    // console.log('getList');

    // 這裏要開始連表查詢
    await query(`select * from user_list where user_id = '${ state.info.id }'`).then((res: mysqlQueryType) => {
        // console.log('/getList 查詢', res.results);
        bodyResult = stateInfo.getSuccessData({
            list: res.results.length > 0 ? res.results : [] 
        });
    }).catch((err: mysqlErrorType) => {
        bodyResult = stateInfo.getFailData(err.message);
    })

    ctx.body = bodyResult;
})

// 添加列表
router.post('/addList', async (ctx) => {
    const state: sessionResultType = ctx['the_state'];
    /** 接收參數 */
    const params = ctx.request.body;
    /** 返回結果 */
    let bodyResult = null;

    if (!params.content) {
        return ctx.body = stateInfo.getFailData('添加的列表內容不能爲空!');
    }

    // 寫入列表
    await query('insert into user_list(list_text, list_time, user_id) values(?,?,?)', [params.content, new Date().toLocaleDateString(), state.info.id]).then((res: mysqlQueryType) => {
        // console.log('寫入列表', res.results.insertId);
        bodyResult = stateInfo.getSuccessData({
            id: res.results.insertId
        }, '添加成功');
    }).catch((err: mysqlErrorType) => {
        // console.log('註冊寫入錯誤', err);
        bodyResult = stateInfo.getFailData(err.message);
    })
    
    ctx.body = bodyResult;
})

// 修改列表
router.post('/modifyList', async (ctx) => {
    const state: sessionResultType = ctx['the_state'];
    /** 接收參數 */
    const params = ctx.request.body;
    /** 返回結果 */
    let bodyResult = null;

    if (!params.id) {
        return ctx.body = stateInfo.getFailData('列表id不能爲空');
    }

    if (!params.content) {
        return ctx.body = stateInfo.getFailData('列表內容不能爲空');
    }

    // 修改列表
    await query(`update user_list set list_text='${params.content}', list_time='${new Date().toLocaleDateString()}' where list_id='${params.id}'`).then((res: mysqlQueryType) => {
        console.log('修改列表', res);
        if (res.results.affectedRows > 0) {
            bodyResult = stateInfo.getSuccessData({}, '修改爲功');
        } else {
            bodyResult = stateInfo.getFailData('列表id不存在');
        }
    }).catch((err: mysqlErrorType) => {
        // console.log('註冊寫入錯誤', err);
        bodyResult = stateInfo.getFailData(err.message);
    })

    ctx.body = bodyResult;
})

// 刪除列表
router.post('/deleteList', async (ctx) => {
    const state: sessionResultType = ctx['the_state'];
    /** 接收參數 */
    const params = ctx.request.body;
    /** 返回結果 */
    let bodyResult = null;
    
    // 從數據庫中刪除
    await query(`delete from user_list where list_id=${params.id} and user_id = ${state.info.id}`).then((res: mysqlQueryType) => {
        console.log('從數據庫中刪除', res);
        if (res.results.affectedRows > 0) {
            bodyResult = stateInfo.getSuccessData({}, '刪除成功');
        } else {
            bodyResult = stateInfo.getFailData('當前列表id不存在或已刪除');
        }
    }).catch((err: mysqlErrorType) => {
        console.log('從數據庫中刪除失敗', err);
        bodyResult = stateInfo.getFailData(err.message);
    })

    ctx.body = bodyResult;
})
複製代碼

項目構建推送到線上

待更新...

最後說明

項目的全部前端調試都是寫好的,由於是後端的分享,因此這裏作代碼說明太費時間了。前端的網絡請求和其餘一下基本操做能夠看開頭個人博客,基本的網頁操做所有都有了,須要的老哥能夠看下,最後能夠的話給個人 GitHub 點個 star 吧~

相關文章
相關標籤/搜索