서문
프로젝트 만들기
프로젝트를 빠르게 생성하기 위한 TSRPC 스캐폴딩은 전체 프로젝트의 기본 프론트엔드 및 백엔드가 완료되면 프론트엔드를 대체하고 관련 종속성을 설치하기 위해 백엔드 및 프론트엔드 두 개의 폴더인 vue-manage-system 프론트엔드 코드를 생성합니다.
mongodb를 사용하여 backend/src 아래에 디렉터리와 파일 mongodb/index.ts를 만듭니다.
import { Db, MongoClient } from "mongodb";
export class Global {
static db: Db;
static async initDb() {
const uri = 'mongodb://.1:27017/test?authSource=admin';
const client = await new MongoClient(uri).connect();
this.db = client.db();
}
}
src/index.ts에서 몽고DB 연결 초기화하기
import { Global } from './mongodb/index';
async function init() {
// ...
await Global.initDb();
};
vue-manage-system 백엔드 관리 시스템 솔루션의 vue3 구현을 기반으로하며, 시작하기 쉬운 간단한 코드를 기반으로하며 여러 프로젝트에 적용되었습니다. 코드를 다운로드하여 프론트엔드 폴더에 덮어쓰고 src/client.ts 파일을 보관하세요. 이것은 클라이언트가 백엔드 인터페이스를 호출하기 위해 tsrpc 프레임워크에서 제공하는 방법입니다. 종속성을 다시 설치하면 실행 준비가 완료됩니다. 다음으로 프론트엔드 및 백엔드 사용자 관리 기능을 구현합니다.
백엔드 인터페이스
백엔드/공유/프로토콜 아래에 새 사용자 폴더를 만들어 사용자 관리 인터페이스를 정의합니다. 해당 디렉터리에서 새 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; // 등록 날짜
}
사용자가 위의 필드를 가지고 있다면 다음으로 추가, 삭제, 확인 및 변경 작업의 사용자 관리를 구현합니다. 사용자 디렉토리에 각각 PtlAdd.ts, PtlDel.ts, PtlGet.ts, PtlUpdate.ts 파일을 생성합니다. 파일 이름과 유형 이름을 통해 프로토콜을 완전히 식별하고 TSRPC에서 규정하는 이름 접두사에 따라 이름을 지정해야 하며 파일 이름은 다음과 같습니다: Ptl{InterfaceName}.ts 그리고 해당 인터페이스 users/Add, users/Del, users/Users/Del에 대응하는 Apixxx.ts 파일도 src/api/ users 디렉토리에 생성됩니다. 사용자 디렉터리에 있는 경우, 해당 인터페이스에 해당하는 사용자/Add, 사용자/Del, 사용자/Get, 사용자/Update에 해당하는 Apixxx.ts 파일도 생성됩니다.
추가
// 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에는 오류 처리에 대한 통합 사양이 있으므로 성공, 실패 및 오류 사례를 고려할 필요가 없으며 코드, 데이터, 메시지 등과 같은 필드를 정의할 필요가 없습니다. 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,
});
}
백엔드의 추가, 삭제, 확인, 변경 인터페이스가 완료되면 프론트엔드에서 다음 인터페이스를 호출합니다.
프런트엔드 호출 인터페이스
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 스토리지를 구입할 돈이 없으며 로컬 서버에만 직접 업로드 할 수 있습니다.
파일 업로드
// PtlUpload.ts
import { BaseRequest, BaseResponse, BaseConf } from "../base";
export interface ReqUpload extends BaseRequest {
fileName: string;
fileData: Uint8Array;
}
export interface ResUpload extends BaseResponse {
url: string;
}
여기서는 8비트 부호 없는 정수 값의 배열을 나타내는 데 사용되는 Uint8Array 유형이 사용되며, 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,
});
}
업로드한 파일을 업로드 디렉터리에 저장하거나 디렉터리가 없는 경우 새로 만듭니다. 더 세분화하려면 별도의 날짜 디렉터리를 만들어 일별로 저장할 수 있습니다.
참고: 여기서 사용자가 전달한 파일 이름은 위의 로직에 따라 파일에 덮어쓰기되므로 백엔드 자체에서 생성된 파일 이름을 변경할 수 있습니다.
엘리먼트 플러스 업로드 컴포넌트를 사용하여 프런트엔드에서 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





