📖 文档
Hello World API 编写

Hello World API 编写

上一步我们设置了 TypeScript 环境。

这一步,我们用 tsdk 写一个简单的返回 Hello World 接口,和一个更新 Hello World 的接口。

新建 server/src/gen-route.ts

gen-route.ts 该文件负责中间件,错误处理等逻辑。

server/src/gen-route.ts
import { ZodError } from 'zod';
import { genRouteFactory, Protocol } from 'tsdk-server-adapters';
import { ProtocolTypes } from '@/src/shared/tsdk-helper';
import { APIConfig, APITypesKey } from '@/src/shared/tsdk-types';
 
const middlewares = [authMiddleware];
const genRouteObj = genRouteFactory<APIConfig, RequestInfo>(
  onErrorHandler,
  ProtocolTypes,
  middlewares,
  APITypesKey
);
export const routeBus = genRouteObj.routeBus;
export const genRoute = genRouteObj.genRoute;
 
export interface RequestInfo {
  type: string;
  ip: string;
  lang: string;
  username?: string;
  userId?: number;
  token?: string;
}
export type ReadonlyRequestInfo = Readonly<RequestInfo>;
 
async function authMiddleware(protocol: Protocol, apiConfig: APIConfig, reqInfo: RequestInfo) {
  if (protocol === 'socket.io' || protocol === 'ws') {
    // only parse once for socket
  }
  if (!apiConfig.needAuth) {
    return Promise.resolve();
  }
  if (!reqInfo.token) {
    return Promise.reject(new AuthError());
  }
 
  return Promise.resolve();
}
 
class AuthError extends Error {
  message = 'AuthError';
}
 
class CustomError extends Error {
  statusCode: number;
}
 
function onErrorHandler(
  e: CustomError,
  { protocol, send, msgId }: Parameters<Parameters<typeof genRouteFactory>[0]>[1]
) {
  if (e instanceof ZodError) {
    return send({
      _id: msgId,
      status: 400,
      result: {
        msg: e.errors,
      },
    });
  }
 
  let status = e.statusCode || 500;
  const msg = e.message;
 
  if (e instanceof AuthError) {
    status = 401;
  }
  return send({ _id: msgId, status, result: { msg } });
}

新建入口 server/src/main.ts

server/src/main.ts
import http from 'http';
import express, { Request } from 'express';
import multer from 'multer'
import { Server } from 'socket.io';
import { socketIOAdapterFactory } from 'tsdk-server-adapters/lib/socket.io-adapter';
import { expressAdapterFactory } from 'tsdk-server-adapters/lib/express-adapter';
import { checkMethodHasBody, ProtocolTypes } from '@/src/shared/tsdk-helper';
import { RequestInfo, routeBus } from './gen-route';
 
const port = 3030;
 
const app = express();
const server = http.createServer(app);
 
const io = new Server(server);
io.on('connection', (socket) => {
  const { address, query, headers } = socket.handshake;
 
  const reqInfo = {
    ip: address,
    lang: 'zh-CN',
    token: query.token as string,
    type: query.type as string,
  };
 
  socketIOAdapterFactory<RequestInfo>({
    routeBus,
    async getReqInfo() {
      return reqInfo;
    },
    getType(reqInfo) {
      return reqInfo.type;
    },
    async getData(data) {
      return data;
    },
    protocolType: ProtocolTypes,
  })(socket);
});
 
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(multer().none());
 
app.get('/', (req, res) => {
  res.end('hi, from express.');
});
app.use(
  '/api/:type',
  expressAdapterFactory<RequestInfo>({
    routeBus,
    async getReqInfo(req: Request) {
      return {
        ip: req.ip as string,
        lang: 'zh-CN',
        type: req.params.type,
        token: req.headers.authorization,
      };
    },
    getType(reqInfo) {
      return reqInfo.type;
    },
    async getData(req) {
      return checkMethodHasBody(req.method) ? req.body : req.query;
    },
  })
);
 
server.listen(port, () => {
  console.log(`Server listening at ${port}`);
});

编写配置 src/Hello.apiconf.ts

💡

别慌!接口配置可以通过 tsdk 内置的代码片段 Snippet Code 快速生成!! 查看说明

server/src/Hello.apiconf.ts
import * as z from 'zod';
import { APIConfig, transformPath } from '@/src/shared/tsdk-helper';
 
/**
 * Get hello ({@link APIConfig})
 * @category hello
 */
export const GetHelloConfig: APIConfig = {
  type: 'user',
  method: 'get',
  path: transformPath('GetHello'),
  description: 'Get hello',
  category: 'hello',
  needAuth: false,
};
/**
 * @category hello
 */
export type GetHelloReq = {};
 
/**
 * @category hello
 */
export type GetHelloRes = {
  result: string;
};
// --------- GetHello END ---------
 
/**
 * Update hello ({@link APIConfig})
 * @category hello
 */
export const UpdateHelloConfig = {
  type: 'user' as const,
  method: 'post' as const,
  path: transformPath('UpdateHello'),
  description: 'Update hello',
  category: 'hello',
  needAuth: false,
  schema: z.object({
    payload: z.string().min(1),
  }),
};
/**
 * @category hello
 */
export type UpdateHelloReq = z.infer<typeof UpdateHelloConfig.schema>;
// export type UpdateHelloReq = {payload: string};
/**
 * @category hello
 */
export type UpdateHelloRes = {
  result: string;
};
// --------- UpdateHello END ---------

编写接口 src/Hello.api.ts

server/src/Hello.api.ts
import { genRoute, type RequestInfo } from './gen-route';
import {
  GetHelloReq,
  GetHelloRes,
  GetHelloConfig,
  UpdateHelloReq,
  UpdateHelloRes,
  UpdateHelloConfig,
} from './Hello.apiconf';
 
const DB = {
  data: 'Hello World',
};
 
export function setupHelloAPI() {
  genRoute<GetHelloReq, GetHelloRes>(
    GetHelloConfig,
    async (data, reqInfo: Readonly<RequestInfo>) => {
      return { result: DB.data };
    }
  );
 
  genRoute<UpdateHelloReq, UpdateHelloRes>(
    UpdateHelloConfig,
    async (data, reqInfo: Readonly<RequestInfo>) => {
      DB.data = data.payload;
      return { result: data.payload };
    }
  );
}

安装接口到 server/src/main/ts

server/src/main.ts 上面已创建好,我们只需要更新入口文件即可。
server/src/main.ts
import http from 'http';
import { socketIOAdapterFactory } from 'tsdk-server-adapters/lib/socket.io-adapter';
import express from 'express';
import multer from 'multer';
import { expressAdapterFactory } from 'tsdk-server-adapters/lib/express-adapter';
import { checkMethodHasBody, ProtocolTypes } from '@/src/shared/tsdk-helper';
import { RequestInfo, routeBus } from './gen-route';
import { setupHelloAPI } from './Hello.api';
 
const port = 3030;
 
const io = new Server(server);
io.on('connection', (socket) => {
  const { address, query, headers } = socket.handshake;
 
  const reqInfo = {
    ip: address,
    lang: 'zh-CN',
    token: query.token as string,
    type: query.type as string,
  };
 
  socketIOAdapterFactory<RequestInfo>({
    routeBus,
    async getReqInfo() {
      return reqInfo;
    },
    getType(reqInfo) {
      return reqInfo.type;
    },
    async getData(data) {
      return data;
    },
    protocolType: ProtocolTypes,
  })(socket);
});
 
const app = express();
const server = http.createServer(app);
 
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(multer().none());
 
app.get('/', (req, res) => {
  res.end('hi, from express.');
});
app.use(
  '/api/:type',
  expressAdapterFactory<RequestInfo>({
    routeBus,
    async getReqInfo(req) {
      return {
        ip: req.ip,
        lang: 'zh-CN',
        type: req.params.type,
        token: req.headers.authorization,
      };
    },
    getType(reqInfo) {
      return reqInfo.type;
    },
    async getData(req) {
      return checkMethodHasBody(req.method) ? req.body : req.query;
    },
  })
);
 
setupHelloAPI();
 
server.listen(port, () => {
  console.log(`Server listening at ${port}`);
});

导出模块 ./fe-sdk

执行以下命令导出模块到 ./fe-sdk 文件夹

cd server
npm run sync-sdk

创建 nest-cli.json

nest-cli.json
{
  "collection": "@nestjs/schematics",
  "monorepo": true,
  "root": "./",
  "sourceRoot": "src",
  "entryFile": "main",
  "compilerOptions": {
    "webpack": false,
    "tsConfigPath": "tsconfig.json"
  }
}

添加运行命令到 server/package.json

server/package.json
{
  ...
  "scripts": {
    ...
    "dev": "nest start --watch",
    "build": "nest build",
    "checktype": "tsc --noEmit"
  }
  ...
}

运行开发环境

cd server
npm run dev

打包

npm run build