国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

vue3 + tsrpc +mongodb 實(shí)現(xiàn)后臺(tái)管理系統(tǒng)

這篇具有很好參考價(jià)值的文章主要介紹了vue3 + tsrpc +mongodb 實(shí)現(xiàn)后臺(tái)管理系統(tǒng)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

前言

之前上線了一個(gè)vue后臺(tái)管理系統(tǒng),有小伙伴問(wèn)我有沒(méi)有后端代碼,咱只是個(gè)小前端,這就有點(diǎn)為難我了。不過(guò)不能辜負(fù)小伙伴的信任,nodejs也可以啊,廢話不多說(shuō),開(kāi)搞!后端采用 TSRPC 框架實(shí)現(xiàn) API 接口,前端采用 vue-manage-system 后臺(tái)管理系統(tǒng)框架,數(shù)據(jù)庫(kù)采用 mongodb。TSRPC 是專為 TypeScript 設(shè)計(jì)的 RPC 框架,經(jīng)千萬(wàn)級(jí)用戶驗(yàn)證。適用于 HTTP API、WebSocket 實(shí)時(shí)應(yīng)用、NodeJS 微服務(wù)等場(chǎng)景。有興趣深入了解可以參考 TSRPC官方文檔。

創(chuàng)建項(xiàng)目

用 TSRPC 腳手架快速創(chuàng)建一個(gè)項(xiàng)目,會(huì)生成 backend 和 frontend 兩個(gè)文件夾,把 vue-manage-system 前端代碼替換到 frontend 中,安裝相關(guān)依賴,就完成一個(gè)基本的前后端完整項(xiàng)目了。

使用 mongodb,在backend/src下創(chuàng)建目錄和文件 mongodb/index.ts

import { Db, MongoClient } from "mongodb";

export class Global {
    static db: Db;
    static async initDb() {
        const uri = 'mongodb://127.0.0.1:27017/test?authSource=admin';
        const client = await new MongoClient(uri).connect();
        this.db = client.db();
    }
}

在 src/index.ts 中初始化 mongodb 連接

import { Global } from './mongodb/index';

async function init() {
    // ...
    await Global.initDb();
};

vue-manage-system 是基于vue3實(shí)現(xiàn)的一個(gè)后臺(tái)管理系統(tǒng)解決方案,代碼簡(jiǎn)單,上手容易,已經(jīng)在多個(gè)項(xiàng)目中應(yīng)用。下載代碼覆蓋到 frontend 文件夾下,保留 src/client.ts 文件,這是 tsrpc 框架提供給客戶端調(diào)用后端接口的方法。重裝依賴,即可運(yùn)行起來(lái)。
接下來(lái)實(shí)現(xiàn)一個(gè)用戶管理的前后端功能。

后端接口

在 backend/shared/protocols 下新建一個(gè) users 文件夾,用于定義用戶管理的相關(guān)接口。在該目錄下,新建 db_User.ts 文件,用于定義用戶集合的字段類型,先按照vue-manage-system前端框架中已有的表格字段隨便定義下吧。

import { ObjectId } from 'mongodb';

export interface db_User {
    _id: ObjectId;
    name: string;	// 用戶名
    pwd: string;    // 密碼
    thumb?: string;  // 頭像
    money: number;  // 賬戶余額
    state: number;  // 賬戶狀態(tài)
    address: string;    // 地址
    date: Date; // 注冊(cè)日期
}

一個(gè)用戶擁有以上的字段,接下來(lái)實(shí)現(xiàn)用戶管理的增刪查改操作。在users目錄下分別創(chuàng)建 PtlAdd.ts、PtlDel.ts、PtlGet.ts、PtlUpdate.ts文件,TSRPC 完全通過(guò)文件名和類型名來(lái)識(shí)別協(xié)議,務(wù)必要嚴(yán)格按照 TSRPC 規(guī)定的名稱前綴來(lái)命名,文件名為:Ptl{接口名}.ts,在 src/api/users 目錄下,也會(huì)生成對(duì)應(yīng)的 Apixxx.ts 文件,就是對(duì)應(yīng)的接口 users/Add、users/Del、users/Get、users/Update。

新增

// PtlAdd.ts
import { BaseRequest, BaseResponse, BaseConf } from "../base";
import { db_User } from "./db_User";

export interface ReqAdd extends BaseRequest {
    query: Omit<db_User, '_id'>		// 除了_id自動(dòng)生成,db_User其它屬性都作為入?yún)?/span>
}

export interface ResAdd extends BaseResponse {
    newID: string;		// 請(qǐng)求成功時(shí)返回_id
}

TSRPC 有統(tǒng)一的 錯(cuò)誤處理 規(guī)范,這里不需要考慮成功、失敗和錯(cuò)誤的情況,不用定義code、data、message等字段,TSRPC 會(huì)返回以下格式

{
	isSucc: true,
	data: {
		newID: 'xxx'
	}
}

在 src/api/users/ApiAdd.ts 中,實(shí)現(xiàn)接口的主要邏輯,把數(shù)據(jù)插入數(shù)據(jù)庫(kù)集合中。

import { Global } from './../../mongodb/index';
import { ApiCall } from "tsrpc";
import { ReqAdd, ResAdd } from "../../shared/protocols/users/PtlAdd";

export default async function (call: ApiCall<ReqAdd, ResAdd>) {
	// 這里就省略了各種判斷
    const ret = await Global.db.collection('User').insertOne(call.req.query);
    return call.succ({ newID: ret.insertedId.toString() })
}

同理,把另外三個(gè)接口也加上

刪除

// PtlDel.ts
import { ObjectId } from "mongodb";
import { BaseRequest, BaseResponse, BaseConf } from "../base";

export interface ReqDel extends BaseRequest {
    _id: ObjectId
}

export interface ResDel extends BaseResponse {
    matchNum: number;
}

// ApiDel.ts
import { ApiCall } from "tsrpc";
import { Global } from "../../mongodb";
import { ReqDel, ResDel } from "../../shared/protocols/users/PtlDel";

export default async function (call: ApiCall<ReqDel, ResDel>) {
    const ret = await Global.db.collection('User').deleteOne({ _id: call.req._id });
    return call.succ({ matchNum: ret.deletedCount })
}

查詢

// PtlGet.ts
import { db_User } from './db_User';
import { BaseRequest, BaseResponse, BaseConf } from "../base";

export interface ReqGet extends BaseRequest {
    query: {
        pageIndex: number;
        pageSize: number;
        name?: string;
    };
}

export interface ResGet extends BaseResponse {
    data: db_User[],
    pageTotal: number
}

// ApiGet.ts
import { Global } from './../../mongodb/index';
import { ApiCall } from "tsrpc";
import { ReqGet, ResGet } from "../../shared/protocols/users/PtlGet";

export default async function (call: ApiCall<ReqGet, ResGet>) {
    const { pageIndex, pageSize, name } = call.req.query;
    const filter: any = {}
    if (name) {
        filter.filter = new RegExp(name!)
    }
    const ret = await Global.db.collection('User').aggregate([
        {
            $match: filter
        },
        {
            $facet: {
                total: [{ $count: 'total' }],
                data: [{ $sort: { _id: -1 } }, { $skip: (pageIndex - 1) * pageSize }, { $limit: pageSize }],
            },
        },
    ]).toArray()
    return call.succ({
        data: ret[0].data,
        pageTotal: ret[0].total[0]?.total || 0
    })
}

修改

// PtlUpdate.ts
import { BaseRequest, BaseResponse, BaseConf } from "../base";
import { db_User } from "./db_User";

export interface ReqUpdate extends BaseRequest {
    updateObj: Pick<db_User, '_id'> & Partial<Pick<db_User, 'name' | 'money' | 'address' | 'thumb'>>;
}

export interface ResUpdate extends BaseResponse {
    updatedNum: number;
}

// ApiUpdate.ts
import { Global } from './../../mongodb/index';
import { ApiCall } from "tsrpc";
import { ReqUpdate, ResUpdate } from "../../shared/protocols/users/PtlUpdate";

export default async function (call: ApiCall<ReqUpdate, ResUpdate>) {
    let { _id, ...reset } = call.req.updateObj;

    let op = await Global.db.collection('User').updateOne(
        {
            _id: _id,
        },
        {
            $set: reset,
        }
    );

    call.succ({
        updatedNum: op.matchedCount,
    });
}

后端的增刪查改接口已經(jīng)完成,接下來(lái)在前端中調(diào)用接口。

前端調(diào)用接口

在 frontend/src/client.ts 中,TSRPC 提供了 client.callApi 來(lái)調(diào)用 API 接口,在 table.vue 中我們來(lái)調(diào)用查詢接口并加載到表格中。

import { client } from '../client';
const query = reactive({
	name: '',
	pageIndex: 1,
	pageSize: 10
});
const tableData = ref<TableItem[]>([]);
const pageTotal = ref(0);
// 獲取表格數(shù)據(jù)
const getData = async () => {
	const ret = await client.callApi('users/Get', {
		query: query
	});
	if (ret.isSucc) {
		tableData.value = ret.res.data;
		pageTotal.value = ret.res.pageTotal;
	}
};
getData();

刪除操作

const handleDelete = async (id: string) => {
	const ret = await client.callApi('users/Del', { _id });
	if (ret.isSucc) {
		ElMessage.success('刪除成功');
	}
};

接口調(diào)用比較簡(jiǎn)單,新增和修改這里就不多描述了,有需要可以看代碼。在用戶字段中,有個(gè)頭像,需要后端提供上傳圖片的接口,在實(shí)際業(yè)務(wù)中,大多數(shù)文件上傳都會(huì)上傳到cdn服務(wù)器上,不過(guò)這里沒(méi)錢買cdn存儲(chǔ),就只能直接上傳到服務(wù)器本地。

上傳文件

先實(shí)現(xiàn)后端上傳文件的接口,在 backend/shared/protocols 下新建一個(gè) upload 文件夾,然后在 upload 里創(chuàng)建 PtlUpload.ts 文件

// PtlUpload.ts
import { BaseRequest, BaseResponse, BaseConf } from "../base";

export interface ReqUpload extends BaseRequest {
    fileName: string;
    fileData: Uint8Array;
}

export interface ResUpload extends BaseResponse {
    url: string;
}

這里用到了 Uint8Array 類型,它用于表示8位無(wú)符號(hào)整數(shù)的值的數(shù)組。Uint8Array主要提供字節(jié)級(jí)別的處理能力,如文件讀寫(xiě)、二進(jìn)制數(shù)據(jù)處理等。

import { ApiCall } from "tsrpc";
import { ReqUpload, ResUpload } from "../../shared/protocols/upload/PtlUpload";
import fs from 'fs/promises';

export default async function (call: ApiCall<ReqUpload, ResUpload>) {
    await fs.access('uploads').catch(async () => {
        await fs.mkdir('uploads')
    })
    await fs.writeFile('uploads/' + call.req.fileName, call.req.fileData);

    call.succ({
        url: call.req.fileName,
    });
}

把上傳的文件存儲(chǔ)到 uploads 目錄下,如果該目錄不存在,則先創(chuàng)建。如果想要比較細(xì)的話,可以多創(chuàng)建出一個(gè)日期的目錄,按天存儲(chǔ)。

注意:這里文件名是由用戶傳過(guò)來(lái)的,有可能出現(xiàn)重名的,按上面的邏輯會(huì)覆蓋到之前的文件,所以這里可以改成文件名由后端自己生成。

在前端結(jié)合 element-plus 的上傳組件調(diào)用api上傳

<el-upload class="avatar-uploader" action="#" :show-file-list="false" :http-request="localUpload">
	<img v-if="form.thumb" :src="UPLOADURL + form.thumb" class="avatar" />
	<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
const localUpload = async (params: UploadRequestOptions) => {
	const ab = await params.file.arrayBuffer();
	var array = new Uint8Array(ab);
	const res = await client.callApi('upload/Upload', {
		fileName: Date.now() + '__' + params.file.name,
		fileData: array
	});
	if (res.isSucc) {
		form.value.thumb = res.res.url;
	} else {
		ElMessage.error(res.err.message);
	}
};

可是在上傳后會(huì)發(fā)現(xiàn),上傳接口成功了,服務(wù)器的圖片文件也存在,但是圖片地址加載失敗。原來(lái)是 TSRPC 默認(rèn)創(chuàng)建的項(xiàng)目中沒(méi)有直接支持靜態(tài)文件服務(wù),需要我們通過(guò)中間件簡(jiǎn)單處理下即可

靜態(tài)文件服務(wù)

創(chuàng)建 getStaticFile.ts 文件,在中間件中自定義 HTTP 響應(yīng),對(duì) Get 類型的請(qǐng)求,找到服務(wù)器上對(duì)應(yīng)的文件并返回

import { HttpConnection, HttpServer } from 'tsrpc';
import fs from 'fs/promises';
import * as path from 'path';

export function getStaticFile(server: HttpServer) {
    server.flows.preRecvDataFlow.push(async (v) => {
        let conn = v.conn as HttpConnection;
        if (conn.httpReq.method === 'GET') {
            // 靜態(tài)文件服務(wù)
            if (conn.httpReq.url) {
                // 檢測(cè)文件是否存在
                let resFilePath = path.join('./', decodeURI(conn.httpReq.url));
                let isExisted = await fs
                    .access(resFilePath)
                    .then(() => true)
                    .catch(() => false);
                if (isExisted) {
                    // 返回文件內(nèi)容
                    let content = await fs.readFile(resFilePath);
                    conn.httpRes.end(content);
                    return undefined;
                }
            }
            // 默認(rèn) GET 響應(yīng)
            conn.httpRes.end('Not Found');
            return undefined;
        }
        return v;
    });
}

在 backend/src/index.ts 中使用,讓每個(gè)網(wǎng)絡(luò)請(qǐng)求都經(jīng)過(guò)這個(gè)工作流

import { HttpServer } from "tsrpc";
import { serviceProto } from "./shared/protocols/serviceProto";
import { getStaticFile } from './models/getStaticFile'
const server = new HttpServer(serviceProto, {
    port: 3000,
    json: true
});
getStaticFile(server);

于是圖片在前端就可以正常加載出來(lái)了。

總結(jié)

作為一個(gè)小前端,也能做一個(gè)完整前后端功能的后臺(tái)管理系統(tǒng),再也不用可憐兮兮的等后端接口了,自己一把梭哈,挺適合發(fā)展自己的副業(yè)余愛(ài)好。上面只是個(gè)基礎(chǔ)的功能,還有許多功能需要慢慢完善,有興趣可以看代碼:tsrpc-manage-system文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-793994.html

到了這里,關(guān)于vue3 + tsrpc +mongodb 實(shí)現(xiàn)后臺(tái)管理系統(tǒng)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包