首页 前端知识 TEGG学习总结

TEGG学习总结

2024-08-07 00:08:45 前端知识 前端哥 426 200 我要收藏

TEGG学习总结

我也是初次接触到TEGG,下面内容是根据GITHUB上的npmmirror 项目总结而出,仅代表个人理解,如有错误,请指出。
tegg将任务组件化,每个组件负责一个任务模块,在每个组件文件夹中需要定义个json文件夹。

{
  "name": "cnpmcore-port",
  "eggModule": {
    "name": "cnpmcorePort"
  }
}

@SingletonProto()语法糖

​ 全局单例语法糖,整个应用单例。

@SingletonProto(params: {
  // 原型的实例化名称
  // 默认行为:会把 Proto 的首字母转为小写
  // 如 UserAdapter 会转换为 userAdapter
  // 如果有不符合预期的可以手动指定,比如
  // @SingletonProto({ name: 'mistAdapter' })
  // MISTAdapter
  // MISTAdapter 的实例名称即为 mistAdapter
  name?: string;
  // 对象是在 module 内可访问还是全局可访问
  // PRIVATE: 仅 module 内可访问
  // PUBLIC: 全局可访问
  // 默认值为 PRIVATE
  accessLevel?: AccessLevel;
})

@ContextProto()语法糖

​ 每次请求都会实例化一个ContextProto,并且只会实例化一次。

@ContextProto(params: {
  // 原型的实例化名称
  // 默认行为:会把 Proto 的首字母转为小写
  // 如 UserAdapter 会转换为 userAdapter
  // 如果有不符合预期的可以手动指定,比如
  // @ContextProto({ name: 'mistAdapter' })
  // MISTAdapter
  // MISTAdapter 的实例名称即为 mistAdapter
  name?: string;

  // 对象是在 module 内可访问还是全局可访问
  // PRIVATE: 仅 module 内可访问
  // PUBLIC: 全局可访问
  // 默认值为 PRIVATE
  accessLevel?: AccessLevel;
})

@Inject()语法糖

​ 注入变量,向控制器类中注入变量。

@Inject(param?: {
  // 注入对象的名称,在某些情况下一个原型可能有多个实例
  // 比如说 egg 的 logger
  // 默认为属性名称
  name?: string;
  // 注入原型的名称
  // 在某些情况不希望注入的原型和属性使用一个名称
  // 默认为属性名称
  proto?: string;
})

{root}/app/ceshi/controller/home.ts

import { Inject } from '@eggjs/tegg';
import { EggLogger } from 'egg';

export class DownloadController extends AbstractController {
  @Inject()
  private readonly logger: EggLogger;
}

@Middleware()语法糖

​ 注入中间件,向控制器类中注入中间件。

import { Middleware } from '@eggjs/tegg';
import { traceMethod } from 'app/middleware/trace_method';  // 中间件

@Middleware(traceMethod)
export class HelloController {}

@HTTPController()语法糖

​ 控制器的类型为HTTP类型,可以实现HTTP请求。

import {
  HTTPController,
  HTTPMethod,
  HTTPMethodEnum,
  Context,
  EggContext,
  HTTPQuery,
  Middleware,
  Inject,
} from '@eggjs/tegg';
import { traceMethod } from 'app/middleware/trace_method';

@HTTPController()
@Middleware(traceMethod)
export class HelloController {
  @HTTPMethod({
    method: HTTPMethodEnum.GET,
    path: '/hello',
  })
  async hello(@Context() ctx: EggContext, @HTTPQuery() name: string) {
    return {
      success: true, 
      data: { message }
    };
  }
}

@Context()语法糖

​ 注入CTX对象语法糖,在@HTTPController()控制器语法糖中使用。

import { Context, EggContext } from '@eggjs/tegg';

export class HelloController {

  async hello(@Context() ctx: EggContext) {
    const message = await this.helloService.hello(name);
    return {
      success: true,
      data: {message}
    };
  }
}

@HTTPMethod()语法糖

​ HTTP请求方法语法糖。

import { HTTPMethod } from '@eggjs/tegg';

export class HelloController {
  @HTTPMethod({
    method: HTTPMethodEnum.GET,   // HTTP请求类型
    path: '/hello',
  })
}

@HTTPQuery()语法糖

​ HTTP请求query参数语法糖。

import { HTTPQuery } from '@eggjs/tegg';

export class HelloController {
  async hello(@HTTPQuery() name: string) {
      
    return {
      success: true,
      data: { message }
    };
  }
}

@HTTPBody()语法糖

​ HTTP请求的请求体。

import { Context, EggContext, HTTPBody} from '@eggjs/tegg';
import { E400 } from 'egg-errors';

export class ScopeController extends AbstractController {
  async createScope(@Context() ctx: EggContext, @HTTPBody() scopeOptions: Static<typeof ScopeCreateOptions>) {
    await this.scopeManagerService.createScope({
      name,
      registryId,
      operatorId: authorizedUser.userId,
    });
    return { ok: true };
  }
}

@HTTPParam()语法糖

​ HTTP请求的Param参数语法糖。

import {
  Context,
  EggContext,
  HTTPParam,
} from '@eggjs/tegg';


@HTTPController()
export class ScopeController extends AbstractController {
  async removeScope(@Context() ctx: EggContext, @HTTPParam() id: string) {
    const authorizedUser = await this.userRoleManager.requiredAuthorizedUser(ctx, 'setting');
    await this.scopeManagerService.remove({ scopeId: id, operatorId: authorizedUser.userId });
    return { ok: true };
  }
}

HTTPMethodEnum

​ HTTP请求的枚举类型。

import { HTTPController, HTTPMethod, HTTPMethodEnum } from '@eggjs/tegg';

@HTTPController()
export class BinarySyncController extends AbstractController {
  @HTTPMethod({
    path: '/-/binary/:binaryName(@[^/]{1,220}\/[^/]{1,220}|[^@/]{1,220})',
    method: HTTPMethodEnum.GET,
  })
  async showBinaryIndex(@Context() ctx: EggContext, @HTTPParam() binaryName: BinaryName) {
    // check binaryName valid
    try {
      ctx.tValidate(BinaryNameRule, binaryName);
    } catch (e) {
      throw new NotFoundError(`Binary "${binaryName}" not found`);
    }
    return await this.showBinary(ctx, binaryName, '/');
  }
}

EGG中类

EggLogger、EggAppConfig

EGG-ERRORS中类

NotFoundError、UnavailableForLegalReasonsError、UnprocessableEntityError、ForbiddenError、UnauthorizedError、BadRequestError、E400

backgroundTaskHelper

​ 异步任务函数类。

import { BackgroundTaskHelper } from '@eggjs/tegg';

@HTTPController()
export class PackageSyncController extends AbstractController {
      async createSyncTask(@Context() ctx: EggContext, @HTTPParam() fullname: string, @HTTPBody() data: SyncPackageTaskType) {
    if (data.force) {
      if (isAdmin) {
        // set background task timeout to 5min
        this.backgroundTaskHelper.timeout = 1000 * 60 * 5;
        this.backgroundTaskHelper.run(async () => {
          ctx.logger.info('[PackageSyncController.createSyncTask:execute-immediately] taskId: %s',
            task.taskId);
          // execute task in background
          await this._executeTaskAsync(task);
        });
      }
    }
    ctx.status = 201;
    return {
      ok: true,
      id: task.taskId,
      type: task.type,
      state: task.state,
    };
  }
}

生命周期 hook

​ 由于对象的生命周期交给了容器来托管,代码中无法感知什么时候对象初始化,什么时候依赖被注入了。所以提供了生命周期 hook 来实现这些通知的功能。

/**
 * lifecycle hook interface for egg object
 */
interface EggObjectLifecycle {
  /**
   * call after construct
   */
  postConstruct?(): Promise<void>;

  /**
   * call before inject deps
   */
  preInject?(): Promise<void>;

  /**
   * call after inject deps
   */
  postInject?(): Promise<void>;

  /**
   * before object is ready
   */
  init?(): Promise<void>;

  /**
   * call before destroy
   */
  preDestroy?(): Promise<void>;

  /**
   * destroy the object
   */
  destroy?(): Promise<void>;
}

{root}/app/ceshi/controller/home.ts

import {
  HTTPController,
  HTTPMethod,
  HTTPMethodEnum,
  Inject,
  HTTPQuery,
  Context,
  EggContext, 
  EggObjectLifecycle
} from '@eggjs/tegg';
import { EggLogger } from 'egg';
import {Name} from '../typings/ceshi';


@HTTPController({
  controllerName: 'HomeController',
  path: '/'
})
export class HomeController implements EggObjectLifecycle {
  @Inject()
  logger: EggLogger;
  @HTTPMethod({
    path: '/',
    method: HTTPMethodEnum.GET
  })
  async index(@Context() ctx: EggContext, @HTTPQuery() name: string) {
    this.logger.info("hello egg info");
    ctx.tValidate(Name, ctx.query);
    const res = await ctx.model.Users.findOne({
      where: {
        id: 1
      }
    });
    return {
      res, 
      name
    };
  }

  async postConstruct(): Promise<void> {
    console.log('对象构造完成');
  }
    
  async preInject(): Promise<void> {
    console.log('依赖将要注入');
  }

  async postInject(): Promise<void> {
    console.log('依赖注入完成');
  }

  async init(): Promise<void> {
    console.log('执行一些异步的初始化过程');
  }

  async preDestroy(): Promise<void> {
    console.log('对象将要释放了');
  }

  async destroy(): Promise<void> {
    console.log('执行一些释放资源的操作');
  }
}

组件内原型名称冲突

​ 一个组件内,有两个原型,原型名相同,实例化不同,这时直接Inject是不行的,组件无法理解具体需要哪个对象。这时就需要告知组件需要注入的对象实例化方式是哪种。

@InitTypeQualifier(initType: ObjectInitType)

{root}/app/ceshi/controller/home.ts

import {
  HTTPController,
  HTTPMethod,
  HTTPMethodEnum,
  Inject,
  HTTPQuery,
  Context,
  EggContext, 
  EggObjectLifecycle, 
  ObjectInitType,
  ContextProto,
  InitTypeQualifier
} from '@eggjs/tegg';
import { EggLogger } from 'egg';
import {Name} from '../typings/ceshi';

@ContextProto()
@HTTPController({
  controllerName: 'HomeController',
  path: '/'
})
export class HomeController implements EggObjectLifecycle {
  @Inject()
  @InitTypeQualifier(ObjectInitType.CONTEXT)
  logger: EggLogger;
  @HTTPMethod({
    path: '/',
    method: HTTPMethodEnum.GET
  })
  async index(@Context() ctx: EggContext, @HTTPQuery() name: string) {
    this.logger.info("hello egg info");
    ctx.tValidate(Name, ctx.query);
    const res = await ctx.model.Users.findOne({
      where: {
        id: 1
      }
    });
    return {
      res, 
      name
    };
  }
}

组件间原型名称冲突

​ 可能多个组件都实现了名称为HelloAdapter的原型,且accessLevel = AccessLevel.PUBLIC,需要明确的告知组件需要注入的原型来自哪个组件。

@ModuleQualifier(moduleName: string)

{root}/app/ceshi/controller/home.ts

import {
  HTTPController,
  HTTPMethod,
  HTTPMethodEnum,
  Inject,
  HTTPQuery,
  Context,
  EggContext, 
  EggObjectLifecycle, 
  ContextProto,
  ModuleQualifier
} from '@eggjs/tegg';
import { EggLogger } from 'egg';
import {Name} from '../typings/ceshi';

@ContextProto()
@HTTPController({
  controllerName: 'HomeController',
  path: '/'
})
export class HomeController implements EggObjectLifecycle {
  @Inject()
  @ModuleQualifier('foo')
  logger: EggLogger;
  @HTTPMethod({
    path: '/',
    method: HTTPMethodEnum.GET
  })
  async index(@Context() ctx: EggContext, @HTTPQuery() name: string) {
    this.logger.info("hello egg info");
    ctx.tValidate(Name, ctx.query);
    const res = await ctx.model.Users.findOne({
      where: {
        id: 1
      }
    });
    return {
      res, 
      name
    };
  }
}

eggctx/app命名冲突

egg内可能出现ctxapp上有同名对象的存在,我们可以通过使用 EggQualifier 来明确指定注入的对象来自ctx还是app。不指定时,默认注入`app上的对象。

@EggQualifier(eggType: EggType)

{root}/app/ceshi/controller/home.ts

import {
  HTTPController,
  HTTPMethod,
  HTTPMethodEnum,
  Inject,
  HTTPQuery,
  Context,
  EggContext, 
  EggObjectLifecycle, 
  ContextProto,
  EggQualifier, 
  EggType
} from '@eggjs/tegg';
import { EggLogger } from 'egg';
import {Name} from '../typings/ceshi';

@ContextProto()
@HTTPController({
  controllerName: 'HomeController',
  path: '/'
})
export class HomeController implements EggObjectLifecycle {
  @Inject()
  @EggQualifier(EggType.CONTEXT)
  logger: EggLogger;
  @HTTPMethod({
    path: '/',
    method: HTTPMethodEnum.GET
  })
  async index(@Context() ctx: EggContext, @HTTPQuery() name: string) {
    this.logger.info("hello egg info");
    ctx.tValidate(Name, ctx.query);
    const res = await ctx.model.Users.findOne({
      where: {
        id: 1
      }
    });
    return {
      res, 
      name
    };
  }
}

MiddleWare结构

import { EggContext, Next } from '@eggjs/tegg';

export async function Tracing(ctx: EggContext, next: Next) {
  // headers: {
  //   'user-agent': 'npm/8.1.2 node/v16.13.1 darwin arm64 workspaces/false',
  //   'npm-command': 'adduser',
  //   'content-type': 'application/json',
  //   accept: '*/*',
  //   'content-length': '124',
  //   'accept-encoding': 'gzip,deflate',
  //   host: 'localhost:7001',
  //   connection: 'keep-alive'
  // }
  ctx.set('request-id', ctx.tracer.traceId);
  if (ctx.method !== 'HEAD') {
    ctx.logger.info('[Tracing] auth: %s, npm-command: %s, referer: %s, user-agent: %j',
      ctx.get('authorization') ? 1 : 0,
      ctx.get('npm-command') || '-',
      ctx.get('referer') || '-',
      ctx.get('user-agent'));
  }
  await next();
}

Schedule结构

​ 通过运行指令npm i @eggjs/tegg-schedule-plugin -S来安装插件。

{root}/config/plugin.ts

import { EggPlugin } from 'egg';

const plugin: EggPlugin = {
  ...

  teggSchedule: {
    enable: true,
    package: '@eggjs/tegg-schedule-plugin',
  }
};

export default plugin; 

{root}/app/ceshi/schedule/task.ts

import { IntervalParams, Schedule, ScheduleType } from '@eggjs/tegg/schedule';
import { Inject } from '@eggjs/tegg';

@Schedule<IntervalParams>({
  type: ScheduleType.WORKER,
  scheduleData: {
    interval: 60000,
    // cron: '0 2 * * *'
  },
})
export class ChangesStreamWorker {

  async subscribe() {

  }
}

egg-typebox-validate插件

​ 用于tegg的参数验证。

​ 通过运行指令npm i egg-typebox-validate -S来安装插件。

{root}/config/plugin.ts

import { EggPlugin } from 'egg';

const plugin: EggPlugin = {
  typeboxValidate: {
    enable: true,
    package: 'egg-typebox-validate',
  },
};

export default plugin;

​ 基本使用

import { Static, Type } from 'egg-typebox-validate/typebox';
const paramsSchema = Type.Object({
   id: Type.String(),
   name: Type.String(),
   timestamp: Type.Integer(),
});

export type ParamsType = Static<typeof paramsSchema>;
                                
ctx.tValidate(paramsSchema, ctx.params);

ioredisegg-redis插件

​ 用于操作redis数据库。

​ 通过运行指令npm i ioredis -D来安装插件。通过运行指令npm i egg-redis -S来安装插件。

{root}/config/plugin.ts

import { EggPlugin } from 'egg';

const plugin: EggPlugin = {
  redis: {
    enable: true,
    package: 'egg-redis',
  }
};

export default plugin;

{root}/config/config.default.ts

import { EggAppConfig, EggAppInfo, PowerPartial } from 'egg';

export default (appInfo: EggAppInfo) => {
  ...

  config.redis = {
    client: {
      port: 6379,
      host: '127.0.0.1',
      password: '',
      db: 2
    }
  };

  // the return config will combines to EggAppConfig
  return {
    ...config,
    ...bizConfig,
  };
};

​ 简单使用

import { SingletonProto, AccessLevel, Inject } from '@eggjs/tegg';
// FIXME: egg-redis should use ioredis v5
// https://github.com/eggjs/egg-redis/issues/35
import type { Redis } from 'ioredis';

const ONE_DAY = 3600 * 24;

@SingletonProto({
  accessLevel: AccessLevel.PUBLIC,
})
export class CacheAdapter {
  @Inject()
  private readonly redis: Redis; // 由 redis 插件引入

  async setBytes(key: string, bytes: Buffer) {
    await this.redis.setex(key, ONE_DAY, bytes);
  }

  async getBytes(key: string) {
    return await this.redis.getBuffer(key);
  }

  async set(key: string, text: string) {
    await this.redis.setex(key, ONE_DAY, text);
  }

  async get(key: string) {
    return await this.redis.get(key);
  }

  async delete(key: string) {
    await this.redis.del(key);
  }

  async lock(key: string, seconds: number) {
    const lockName = this.getLockName(key);
    const existsTimestamp = await this.redis.get(lockName);
    if (existsTimestamp) {
      if (Date.now() - parseInt(existsTimestamp) < seconds * 1000) {
        return null;
      }
      // lock timeout, delete it
      await this.redis.del(lockName);
    }
    const timestamp = `${Date.now() + seconds * 1000}`;
    const code = await this.redis.setnx(lockName, timestamp);
    // setnx fail, lock fail
    if (code === 0) return null;
    // expire
    await this.redis.expire(lockName, seconds);
    return timestamp;
  }
}

egg-sequelizemysql2插件

​ 通过运行指令npm install egg-sequelize mysql2 -S来安装插件和库。

{root}/config/plubin.ts

import { EggPlugin } from 'egg';

const plugin: EggPlugin = {
  ...

  sequelize: {
    enable: true,
    package: 'egg-sequelize'
  }
};

export default plugin;

{root}/config.default.ts

import { EggAppConfig, EggAppInfo, PowerPartial } from 'egg';

export default (appInfo: EggAppInfo) => {
  ...

  config.sequelize = {
    dialect: 'mysql',
    host: '127.0.0.1',
    username: 'root',
    password: 'xxx',
    port: 3306,
    database: 'ceshi',
    // 中国时区
    timezone: '+08:00',
    define: {
      // 取消数据表名复数
      freezeTableName: true,
      // 自动写入时间戳 created_at updated_at
      timestamps: true,
      createdAt: 'created_at',
      updatedAt: 'updated_at',
      // 所有驼峰命名格式化
      underscored: true
    }
  };

  // the return config will combines to EggAppConfig
  return {
    ...config,
    ...bizConfig,
  };
};

​ 通过运行指令npm install sequelize-cli -D来安装数据库管理工具。

​ 通过在根目录下新建一个名为.sequelizerc的文件。

{root}/.sequelizerc

'use strict';

const path = require('path');

module.exports = {
  config: path.join(__dirname, 'database/config.json'),
  'migrations-path': path.join(__dirname, 'database/migrations'),
  'seeders-path': path.join(__dirname, 'database/seeders'),
  'models-path': path.join(__dirname, 'app/model'),
};

​ 通过运行指令npx sequelize init:confignpx sequelize init:migrations来初始化配置文件。

{root}/database/config.json

{
  "development": {
    "username": "root",
    "password": "xxx",
    "database": "lovers",
    "host": "127.0.0.1",
    "dialect": "mysql"
 },
  "test": {
    "username": "root",
    "password": "xxx",
    "database": "lovers",
    "host": "127.0.0.1",
    "dialect": "mysql"
 },
  "production": {
    "username": "root",
    "password": "xxx",
    "database": "lovers",
    "host": "127.0.0.1",
    "dialect": "mysql"
 }
}

​ 通过运行指令npx sequelize db:create来创建数据库。

​ 通过执行指令npx sequelize migration:generate --name=表名,来创建迁移文件。

{root}/database/migrations/表名.js

'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    const { INTEGER, STRING, DATE, ENUM } = Sequelize;
    // 创建表
    await queryInterface.createTable('表名', {
      id: 
      { 
        type: INTEGER(20).UNSIGNED, 
        primaryKey: true, 
        autoIncrement: true 
      },
      username: 
      { 
        type: STRING(30), 
        allowNull: false, 
        comment: '用户名称', 
        defaultValue: ''
      },
      email: 
      { 
        type: STRING(160), 
        allowNull: false, 
        comment: '用户邮箱', 
        unique: true, 
        defaultValue: ''
      },
      password: 
      { 
        type: STRING(200), 
        allowNull: false, 
        comment: '用户密码', 
        defaultValue: ''
      },
      avatar: 
      { 
        type: STRING(200), 
        allowNull: true, 
        comment: '用户头像', 
        defaultValue: '' 
      },
      birthday: 
      { 
        type: DATE, 
        allowNull: false, 
        comment: '用户生日', 
        defaultValue: '2000-12-20'
      },
      gender: 
      { 
        type: ENUM, 
        values: ['男', '女'], 
        allowNull: false, 
        comment: '用户性别', 
        defaultValue: '男'
      },
      status: 
      { 
        type: ENUM, 
        values: ['1', '0'], 
        allowNull: false, 
        comment: '1:正常; 0:禁用',
        defaultValue: '1'
      },
      created_at: DATE,
      updated_at: DATE
    });
  },

  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('users');
  }
};

​ 通过运行指令npx sequelize db:migrate来更新升级数据表。

## 升级数据库
npx sequelize db:migrate
## 如果有问题需要回滚,可以通过 `db:migrate:undo` 回退一个变更
## npx sequelize db:migrate:undo
## 可以通过 `db:migrate:undo:all` 回退到初始状态
## npx sequelize db:migrate:undo:all

​ 通过在app文件夹下新建一个文件夹model,来实现在框架的app对象上挂载。

注意:文件夹model下的文件名一定要与数据库迁移文件的文件名一致。

{root}/app/model/表名.ts

'use strict';
const CryptoJS = require('crypto-js');

module.exports = app => {
  const { INTEGER, STRING, DATE, ENUM } = app.Sequelize;
  const Users = app.model.define('users',
    {
      id:
      {
        type: INTEGER(20).UNSIGNED,
        primaryKey: true,
        autoIncrement: true
      },
      username:
      {
        type: STRING(30),
        allowNull: false,
        comment: '用户名称',
        defaultValue: ''
      },
      email:
      {
        type: STRING(160),
        allowNull: false,
        comment: '用户邮箱',
        unique: true,
        defaultValue: ''
      },
      birthday: 
      { 
        type: DATE, 
        allowNull: false, 
        comment: '用户生日', 
        defaultValue: '2000-12-20'
      },
      password:
        <any>{
          type: STRING(200),
          allowNull: false,
          comment: '用户密码',
          defaultValue: '',
          set(val: string) {
            let hmac = CryptoJS.SHA256(`${val}${app.config.crypto.secret}`);
            const hash = hmac.toString(CryptoJS.enc.Hex);
            this.setDataValue('password', hash);
          }
        },
      avatar:
      {
        type: STRING(200),
        allowNull: true,
        comment: '用户头像',
        defaultValue: ''
      },
      gender:
      {
        type: ENUM,
        values: ['男', '女'],
        allowNull: false,
        comment: '用户性别',
        defaultValue: '男'
      },
      status:
      {
        type: ENUM,
        values: ['1', '0'],
        allowNull: false,
        comment: '1:正常; 0:禁用',
        defaultValue: '1'
      },
      created_at: DATE,
      updated_at: DATE
    });
  return Users;
}

​ 简单使用

{root}/app/ceshi/controller/home.ts

import {
  HTTPController,
  HTTPMethod,
  HTTPMethodEnum,
  Inject,
  HTTPQuery,
  Context,
  EggContext
} from '@eggjs/tegg';
import { EggLogger } from 'egg';
import {Name} from '../typings/ceshi';


@HTTPController({
  controllerName: 'HomeController',
  path: '/'
})
export class HomeController {
  @Inject()
  logger: EggLogger;
  @HTTPMethod({
    path: '/',
    method: HTTPMethodEnum.GET
  })
  async index(@Context() ctx: EggContext, @HTTPQuery() name: string) {
    this.logger.info("hello egg info");
    ctx.tValidate(Name, ctx.query);
    const res = await ctx.model.Users.findOne({
      where: {
        id: 1
      }
    })
    return {
      res, 
      name
    };
  }
  @HTTPMethod({
    path: '/set',
    method: HTTPMethodEnum.GET
  })
  async setKey(){
    return {
      data: 'ok'
    }
  }
}
转载请注明出处或者链接地址:https://www.qianduange.cn//article/14978.html
标签
评论
发布的文章

前端-axios应用在html文件

2024-08-15 23:08:39

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!