vue3 + tsrpc +mongodb 实现后台管理系统

这篇具有很好参考价值的文章主要介绍了vue3 + tsrpc +mongodb 实现后台管理系统。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

之前上线了一个vue后台管理系统,有小伙伴问我有没有后端代码,咱只是个小前端,这就有点为难我了。不过不能辜负小伙伴的信任,nodejs也可以啊,废话不多说,开搞!后端采用 TSRPC 框架实现 API 接口,前端采用 vue-manage-system 后台管理系统框架,数据库采用 mongodb。TSRPC 是专为 TypeScript 设计的 RPC 框架,经千万级用户验证。适用于 HTTP API、WebSocket 实时应用、NodeJS 微服务等场景。有兴趣深入了解可以参考 TSRPC官方文档。

创建项目

用 TSRPC 脚手架快速创建一个项目,会生成 backend 和 frontend 两个文件夹,把 vue-manage-system 前端代码替换到 frontend 中,安装相关依赖,就完成一个基本的前后端完整项目了。

使用 mongodb,在backend/src下创建目录和文件 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实现的一个后台管理系统解决方案,代码简单,上手容易,已经在多个项目中应用。下载代码覆盖到 frontend 文件夹下,保留 src/client.ts 文件,这是 tsrpc 框架提供给客户端调用后端接口的方法。重装依赖,即可运行起来。
接下来实现一个用户管理的前后端功能。

后端接口

在 backend/shared/protocols 下新建一个 users 文件夹,用于定义用户管理的相关接口。在该目录下,新建 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;  // 账户状态
    address: string;    // 地址
    date: Date; // 注册日期
}

一个用户拥有以上的字段,接下来实现用户管理的增删查改操作。在users目录下分别创建 PtlAdd.ts、PtlDel.ts、PtlGet.ts、PtlUpdate.ts文件,TSRPC 完全通过文件名和类型名来识别协议,务必要严格按照 TSRPC 规定的名称前缀来命名,文件名为:Ptl{接口名}.ts,在 src/api/users 目录下,也会生成对应的 Apixxx.ts 文件,就是对应的接口 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自动生成,db_User其它属性都作为入参
}

export interface ResAdd extends BaseResponse {
    newID: string;		// 请求成功时返回_id
}

TSRPC 有统一的 错误处理 规范,这里不需要考虑成功、失败和错误的情况,不用定义code、data、message等字段,TSRPC 会返回以下格式

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

在 src/api/users/ApiAdd.ts 中,实现接口的主要逻辑,把数据插入数据库集合中。

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

同理,把另外三个接口也加上

删除

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

后端的增删查改接口已经完成,接下来在前端中调用接口。

前端调用接口

在 frontend/src/client.ts 中,TSRPC 提供了 client.callApi 来调用 API 接口,在 table.vue 中我们来调用查询接口并加载到表格中。

import { client } from '../client';
const query = reactive({
	name: '',
	pageIndex: 1,
	pageSize: 10
});
const tableData = ref<TableItem[]>([]);
const pageTotal = ref(0);
// 获取表格数据
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('删除成功');
	}
};

接口调用比较简单,新增和修改这里就不多描述了,有需要可以看代码。在用户字段中,有个头像,需要后端提供上传图片的接口,在实际业务中,大多数文件上传都会上传到cdn服务器上,不过这里没钱买cdn存储,就只能直接上传到服务器本地。

上传文件

先实现后端上传文件的接口,在 backend/shared/protocols 下新建一个 upload 文件夹,然后在 upload 里创建 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位无符号整数的值的数组。Uint8Array主要提供字节级别的处理能力,如文件读写、二进制数据处理等。

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

把上传的文件存储到 uploads 目录下,如果该目录不存在,则先创建。如果想要比较细的话,可以多创建出一个日期的目录,按天存储。

注意:这里文件名是由用户传过来的,有可能出现重名的,按上面的逻辑会覆盖到之前的文件,所以这里可以改成文件名由后端自己生成。

在前端结合 element-plus 的上传组件调用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);
	}
};

可是在上传后会发现,上传接口成功了,服务器的图片文件也存在,但是图片地址加载失败。原来是 TSRPC 默认创建的项目中没有直接支持静态文件服务,需要我们通过中间件简单处理下即可

静态文件服务

创建 getStaticFile.ts 文件,在中间件中自定义 HTTP 响应,对 Get 类型的请求,找到服务器上对应的文件并返回

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') {
            // 静态文件服务
            if (conn.httpReq.url) {
                // 检测文件是否存在
                let resFilePath = path.join('./', decodeURI(conn.httpReq.url));
                let isExisted = await fs
                    .access(resFilePath)
                    .then(() => true)
                    .catch(() => false);
                if (isExisted) {
                    // 返回文件内容
                    let content = await fs.readFile(resFilePath);
                    conn.httpRes.end(content);
                    return undefined;
                }
            }
            // 默认 GET 响应
            conn.httpRes.end('Not Found');
            return undefined;
        }
        return v;
    });
}

在 backend/src/index.ts 中使用,让每个网络请求都经过这个工作流

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

于是图片在前端就可以正常加载出来了。

总结

作为一个小前端,也能做一个完整前后端功能的后台管理系统,再也不用可怜兮兮的等后端接口了,自己一把梭哈,挺适合发展自己的副业余爱好。上面只是个基础的功能,还有许多功能需要慢慢完善,有兴趣可以看代码:tsrpc-manage-system文章来源地址https://www.toymoban.com/news/detail-793994.html

到了这里,关于vue3 + tsrpc +mongodb 实现后台管理系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于VUE3+Layui从头搭建通用后台管理系统(前端篇)十一:通用表单组件封装实现

      本章实现通用表单组件,根据实体配置识别实体属性,并自动生成编辑组件,实现对应数据填充、校验及保存等逻辑。 1. 详细课程地址: https://edu.csdn.net/course/detail/38183 2. 源码下载地址: 点击下载

    2024年02月10日
    浏览(55)
  • 基于VUE3+Layui从头搭建通用后台管理系统(前端篇)二:登录界面及对应功能实现

      本章介绍系统登录界面、登录流程、登录接口等相关内容的开发,实现包括账号密码登录、短信验证登录等不同的登录方式,使用svg-capter生成图形验证码,使用expressjwt实现登录token的生成及验证。 1. 详细课程地址: https://edu.csdn.net/course/detail/38183 2. 源码下载地址: 点击

    2024年02月11日
    浏览(68)
  • 从零开始Vue3+Element Plus的后台管理系统(二)——Layout页面布局的实现

    项目搭建好之后,开始写基本的布局。后台管理系统的布局3大元素:头部、侧栏、主要内容,各种布局结构相差不大,我选择了下图所示的布局,其中头部、侧栏、页签在页面中是固定的,只有主要内容容器会跟随页面滚动。 Layout布局的目录结构 代码就不贴了,仓库有😄

    2024年02月16日
    浏览(42)
  • vue3 + TS + elementplus + pinia实现后台管理系统左侧菜单联动实现 tab根据路由切换联动内容

    效果图:  home.vue页面代码 left.vue页面代码 tab.vue页面代码 pinia里面的代码 安装 使用插件  在main.ts中注册 路由代码 我把代码放git上了,有需要的自行拉取 https://gitee.com/Flechazo7/vue3.git

    2024年02月09日
    浏览(49)
  • 基于VUE3+Layui从头搭建通用后台管理系统(前端篇)三:找回密码界面及对应功能实现

      本章实现找回密码功能,包括短信验证码找回、邮箱验证码找回等功能,并通过node-send-email发送邮箱验证码,实现找回密码界面、接口等功能。 1. 详细课程地址: https://edu.csdn.net/course/detail/38183 2. 源码下载地址: 点击下载

    2024年02月12日
    浏览(55)
  • 基于VUE3+Layui从头搭建通用后台管理系统(前端篇)十六:统计报表模块相关功能实现

      本章使用Echarts及DataV实现常用图表、特殊图表、地图及综合图表等图表展示功能。 1. 详细课程地址: https://edu.csdn.net/course/detail/38183 2. 源码下载地址: 点击下载 基于VUE3+Layui从

    2024年02月04日
    浏览(62)
  • vue3后台管理系统

    后面可参考下: vue系列(三)——手把手教你搭建一个vue3管理后台基础模板 TypeError: Failed to fetch dynamically imported module: 以下代码项目gitee地址 初始化项目 可参考:vite官网 https://vitejs.cn/guide/#scaffolding-your-first-vite-project 添加加载效果 在index.html中的id为app中,写入 配置 vite.c

    2023年04月15日
    浏览(50)
  • vue3+element-plus的后台管理系统模板 和 vue3+ant-design-vue的后台管理系统模板

    规范 :后台系统模板,按照企业级别的规范搭建的。 权限控制 :通过后端返回的路由表(这个路由表是由前端这边在系统配好的然后存储在后端的)来动态渲染菜单和注册路由,同时也根据页面内的接口权限对页面中的按钮做了是否可见的设置。前端这边有 路由、角色、用

    2024年02月08日
    浏览(81)
  • Vue3搭建后台管理系统模板

    今天来带大家从0开始搭建一个vue3版本的后台管理系统。一个项目要有统一的规范,需要使用eslint+stylelint+prettier来对我们的代码质量做检测和修复,需要使用husky来做commit拦截,需要使用commitlint来统一提交规范,需要使用preinstall来统一包管理工具。 下面我们就用这一套规范

    2024年02月08日
    浏览(62)
  • Vue3后台管理系统模板推荐

    Vue-Vben-Admin(github上的标星数为14.2k)是一个基于 Vue3.0、Vite、 Ant-Design-Vue、TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、按钮级别权限控制等功能。项目使用前端较新的技术栈,可以作为

    2024年02月13日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包