portal

  • Online demo account: guest, password: 123456
  • The source address

This chapter content

  1. Data query interface
  2. Ant Design ProAccess to the
  3. LinuxUnder theNginxThe deployment of
  4. JenkinsContinuous integration/deployment

Data query

Create a new dTO directory in modules/stock, create a new stock.dto.ts file, and define the following types:

import { Exclude } from 'class-transformer';

// Stock list type
export class StockDto {
  readonly code: string;
  readonly name: string;
  readonly market: string;
  readonly price: string;
  readonly peTtm: string;
  readonly peTtmAvg: string;
  readonly peTtmRate: string;
  readonly peTtmMid: string;
  @Exclude(a)readonly sourceData: object | null;

  us: any;
}

// List query parameter types
export class StockQueryDto {
  readonly pageSize: number;
  readonly pageIndex: number;
  readonly keywords: string;
  readonly orderBy: number;
}

export class UserStockDto {
  readonly code: string;
}

// Paging data type
export class PageListModel<T> {
  totalNum: number; / / the total number of
  pageSize: number; / / page number
  pageIndex: number; // Current page number
  list: T[];
}
Copy the code

Modules /stock

$ nest g s modules/stock
Copy the code

Query method demonstrates TypeOrm QueryBuilder and execution of native SQL statement writing (native SQL recommended to use parameterized query to prevent SQL injection, here only to demonstrate), code as follows:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, getRepository, getConnection, getManager } from 'typeorm';
import { plainToClass } from 'class-transformer';
import { StockDto, StockQueryDto, PageListModel } from './dto/stock.dto';
import { Stock } from '.. /.. /entities/Stock';
import { UserStock } from '.. /.. /entities/UserStock';

@Injectable(a)export class StockService {
  constructor(
    @InjectRepository(Stock)
    private readonly stStockRepository: Repository<Stock>,
  ) {}
  /** * Select * from the list where you want to query the stock@param param
   * @returns* /
  async getStockList(
    uid: number.param: StockQueryDto,
  ): Promise<PageListModel<StockDto>> {
    const pageIndex = param.pageIndex ? param.pageIndex : 1;
    const pageSize = param.pageSize ? param.pageSize : 10;
    const orderBy = param.orderBy ? param.orderBy : 0;
    let orderByMap: any = {
      'st.code': 'DESC'};switch (orderBy) {
      case 1:
        orderByMap = {
          'st.peTtmRate': 'ASC'};break;
      case 2:
        orderByMap = {
          'st.peTtmRate': 'DESC'};break;
      default:
        break;
    }
    let keyWordsWhere = 'st.is_delete = 0';
    if (param.keywords) {
      keyWordsWhere += ' and (st.code like :code or st.name like :name)';
    }
    const { totalNum } = await getRepository(Stock)
      .createQueryBuilder('st')
      .select('COUNT(1)'.'totalNum')
      .where(keyWordsWhere, {
        code: The '%' + param.keywords + The '%'.name: The '%' + param.keywords + The '%',
      })
      .getRawOne();

    const stockList = await getRepository(Stock)
      .createQueryBuilder('st')
      .select([
        'st.id'.'st.code'.'st.name'.'st.pe'.'st.peTtm'.'st.peTtmAvg'.'st.peTtmMid'.'st.peTtmRate'.'st.updateDt',
      ])
      .where(keyWordsWhere, {
        code: The '%' + param.keywords + The '%'.name: The '%' + param.keywords + The '%',
      })
      .orderBy(orderByMap)
      .skip((pageIndex - 1) * pageSize)
      .take(pageSize)
      .getMany();
    // Convert type, otherwise we need to add us attribute to Stock
    const stocks = plainToClass(StockDto, stockList);
    for (let i = 0; i < stocks.length; i++) {
      // Query whether the relational table exists
      const us = await getConnection()
        .createQueryBuilder()
        .select(['us.uid'])
        .from(UserStock, 'us')
        .where('us.uid = :uid and us.code = :code ', {
          uid,
          code: stocks[i].code,
        })
        .getOne();
      if (us) {
        stocks[i].us = us;
      } else {
        stocks[i].us = null; }}return {
      list: stocks,
      totalNum,
      pageIndex,
      pageSize,
    };
  }

  /** * Add optional *@param uid
   * @param code
   * @returns* /
  async addUserStock(uid: number.code: string) :Promise<boolean> {
    let result = false;
    // Determine whether the relationship already exists
    const userStock = await getConnection()
      .createQueryBuilder()
      .select(['ut.id'])
      .from(UserStock, 'ut')
      .where('ut.uid = :uid and ut.code = :code ', {
        uid,
        code,
      })
      .getOne();
    if(! userStock) {const ut = await getConnection()
        .createQueryBuilder()
        .insert()
        .into(UserStock)
        .values([{ uid, code }])
        .execute();
      if (ut) {
        result = true; }}return result;
  }

  /** * Select a list of native SQL statements to execute *@param uid
   * @param param
   * @returns* /
  async getUserStocts(
    uid: number.param: StockQueryDto,
  ): Promise<PageListModel<UserStock>> {
    const pageIndex = param.pageIndex ? param.pageIndex : 1;
    const pageSize = param.pageSize ? param.pageSize : 10;
    const orderBy = param.orderBy ? param.orderBy : 0;
    let orderByStr = `order by s.code desc `;
    switch (orderBy) {
      case 1:
        orderByStr = `order by s.pe_ttm_rate asc `;
        break;
      case 2:
        orderByStr = `order by s.pe_ttm_rate desc `;
        break;
      default:
        break;
    }
    let keyWordsWhere = `ut.uid=${uid} and s.is_delete=0`;
    if (param.keywords) {
      keyWordsWhere += ` and (s.code like '%${param.keywords}%'  or s.name like '%${param.keywords}` % ');
    }
    const limit = ` limit ${pageSize} offset ${(pageIndex - 1) * pageSize}`;
    const manager = getManager();
    const total = await manager.query(
      `select count(1) as totalNum
       from user_stock ut inner join stock s on ut.code=s.code where ${keyWordsWhere}`,);const stockList = await manager.query(
      `select ut.uid,ut.code,s.name,
       s.pe_ttm as peTtm,
       s.pe_ttm_avg as peTtmAvg,
       s.pe_ttm_mid as peTtmMid,
       s.pe_ttm_rate as peTtmRate,
       s.update_dt as updateDt
       from user_stock ut inner join stock s on ut.code=s.code where ${keyWordsWhere} ${orderByStr} ${limit}`,);return {
      list: stockList,
      totalNum: total.length > 0 ? Number(total[0].totalNum) : 0,
      pageIndex,
      pageSize,
    };
  }
  /** * Delete optional *@param uid
   * @param code
   * @returns* /
  async deleteUserStock(uid: number.code: string) :Promise<boolean> {
    let result = false;
    const res = await getConnection()
      .createQueryBuilder()
      .delete()
      .from(UserStock)
      .where('uid = :uid', { uid })
      .andWhere('code = :code', { code })
      .execute();
    if (res.affected > 0) {
      result = true;
    }
    returnresult; }}Copy the code

Create stock. Controller. Ts:

$ nest g co modules/stock
Copy the code

Modify the stock.controller.ts template code to note the use of UseGuards, scoped to all routes:

import { Controller, Post, Body, UseGuards } from '@nestjs/common';
import { StockService } from './stock.service';
import { StockQueryDto } from './dto/stock.dto';
import { AuthGuard } from '@nestjs/passport';
import { RolesGuard } from '.. /auth/guards/role.guard';
import { User } from '.. /.. /libs/decorators/user.decorator';
import { ProfileInfo } from '.. /.. /libs/decorators/profileInfo';
import { UserStockDto } from './dto/stock.dto';

@Controller('stock')
@UseGuards(AuthGuard('jwt'))
export class StockController {
  constructor(private readonly userService: StockService) {}

  @UseGuards(RolesGuard)
  @Post('list')
  async getStockList(@Body() parmas: StockQueryDto, @User() user: ProfileInfo) {
    return this.userService.getStockList(user.uid, parmas);
  }

  @UseGuards(RolesGuard)
  @Post('add-choice')
  async addChoice(@Body() parmas: UserStockDto, @User() user: ProfileInfo) {
    return this.userService.addUserStock(user.uid, parmas.code);
  }

  @UseGuards(RolesGuard)
  @Post('user-list')
  async getUserStocts(
    @Body() parmas: StockQueryDto,
    @User() user: ProfileInfo,
  ) {
    return this.userService.getUserStocts(user.uid, parmas);
  }

  @UseGuards(RolesGuard)
  @Post('delete-choice')
  async deleteChoice(@Body() parmas: UserStockDto, @User() user: ProfileInfo) {
    return this.userService.deleteUserStock(user.uid, parmas.code); }}Copy the code

To run the program, visit http://localhost:3000/stock/list POST (note with Authorization) parameters are as follows:

{
  "pageSize":10."pageIndex":1."keywords":"China"."orderBy":1
}
Copy the code

Return result:

Ant Design Pro

If the API is ready, install Ant Design Pro. For details, see Ant Design Pro

Note the following when the front-end page is initialized in config/config.ts:

  • With the proxy, the service is deployed to the server and the proxy configuration needs to correspond to the front end
  • The front-end project is deployed in a non-root directory of the serverbaseandpublicPathconfiguration
  • mockOpen and close, etc

Ant Design Pro uses @umijs/plugin-request as the request library by default, but the request library itself has stopped updating and is not flexible in obtaining Response header attributes, so it overwrites the request class based on AXIos. The HTTP code and business code are classified. For other details, please refer to the source code directly.

LinuxUnder theNginxThe deployment of

Service deployment can be based on docker packaging as an image or by building node+ Nginx. The second method is used this time.

Server environment:

$cat /etc/redhat-release CentOS Linux release 7.4.1708 (Core) $node -v v14.16.0 $NPM -v 6.14.11 $pm2 -v 4.5.4Copy the code

Note the following when deploying the Node service:

  • The local build/dist will not run if it is placed directly on the server. Dependencies are not packaged into the output package. It is recommended to install dependencies on the server and package them.

  • Because of the single-thread mechanism of node event loop, pM2 can be used to realize multiple processes.

  • Nginx agent Settings need to be consistent with Ant Design Pro, so that no code changes are required for local development or online deployment.

For front-end deployment, if the directory is not the root directory, see Non-root directory

JenkinsContinuous integration/deployment

Jenkins installation, please refer to Jenkins installation. Jenkins mainly uses single item construction or pipeline construction. Pipeline construction can be understood as simple and crude definition of multiple multi-step single items through Jenkinsfile, and manual confirmation process can be set between each item to decide whether to proceed to the next step. For more details, please refer to Jenkins documentation. This time, the single-item construction method can meet the requirements. The main implementation process can be divided into the following steps:

  • Manually or through the code repositorywebhooksTrigger a build
  • JenkinsPulls the specified repository code through a pre-configured repository access token
  • Execute build commands in a single build using a pre-configured execution environment
  • Post-build operations

General network environment code repository is not recommended to choose Github, pull speed will make people doubt life, this code cloud managed code for example reference document code cloud document, according to the above key steps to extract the relevant key configuration:

  1. The installationgiteePlug-ins: System configuration -> Plug-in management -> Searchgitee–> Click Install

  1. Global configurationgiteeParameters: System Configuration -> System Configuration -> FoundgiteeConfiguration -> Enter related configurations

  1. Click The Certificate token “Add” -> select “Gitee API Token” -> Enter the private token on the code cloud and add the ID for subsequent use;

  1. The installationnodePlug-ins: System configuration -> Plug-in management -> Searchnode–> Click Install
  2. configurationnodeEnvironment: system configuration – > global tool configuration – > NodeJs — — > click install > enter the alias – > select to install version – > save

  1. Create a single build: New Task -> Select Freestyle Project -> OK

  1. General Settings

  1. Source code management: selectiongit

  1. Add source access credentials

  1. Trigger the configuration. Select it as required

  1. Configure build target branch andwebhooksSecret key (to be entered in the code cloud)

  1. Binding configuration, select the previous global configurationnodeversion

  1. Build script:JenkinsIt has its own workspace where the pulled code and the output of executing the build are stored. The following example shows the build environment and the release environment in the same virtual machine, directlycpFile to the destination path and restartpm2Can. If it is not the same machine, you can use the SSH command to send the file to the target machine.

  1. Save, manually or submit the code to trigger the test, and check the console log to see why the error occurred.