NestJS——重构日志、数据库、配置
个人简介
👀个人主页: 前端杂货铺
🙋♂️学习方向: 主攻前端方向,正逐渐往全干发展
📃个人状态: 研发工程师,现效力于中国工业软件事业
🚀人生格言: 积跬步至千里,积小流成江海
🥇推荐学习:🍍前端面试宝典 🎨100个小功能 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js实战 🍒Three.js🌕个人推广:每篇文章最下方都有加入方式,旨在交流学习&资源分享,快加入进来吧
内容 | 参考链接 |
---|---|
NestJS(一) | Docker入门 |
NestJS(二) | NestJS——创建项目、编写User模块 |
NestJS(三) | TypeScript入门 |
NestJS(四) | 编程思想——FP、OOP、FRP、AOP、IOC、DI、MVC、DTO、DAO |
NestJS(五) | NestJS——多环境配置方案(dotenv、config、@nestjs/config、joi配置校验) |
NestJS(六) | NestJS——使用TypeORM连接MySQL数据库(Docker拉取镜像、多环境适配) |
NestJS(七) | NestJS——使用TypeORM操作数据库、增删改查、关联查询、QueryBuilder |
NestJS(八) | NestJS——日志、NestJS-logger、pino、winston、全局异常过滤器 |
文章目录
- 重构 logger 配置
- 数据库配置重构
- 重构数据库、配置
- 总结
重构 logger 配置
其实前文中我们对于 logger 的配置不太合理,接下来,我们进行重构。
GitHub 提交记录
创建 logger 模块。
nest g mo logs
简化 main.ts
文件,把日志相关配置抽离出去。
import { NestFactory } from "@nestjs/core"; // 导入 NestFactory,用于创建 Nest 应用实例
import { AppModule } from "./app.module"; // 导入应用的根模块
import { WINSTON_MODULE_NEST_PROVIDER } from "nest-winston";/*** 应用程序的入口文件* - 使用 NestFactory 创建应用实例* - 配置全局设置并启动应用*/
async function bootstrap() {// 创建应用实例,并可选配置日志级别const app = await NestFactory.create(AppModule, {// 日志级别配置(可选)// logger: ["error", "warn"], // 仅记录错误和警告日志});app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER)); // 使用 Winston 日志记录器// 设置全局路由前缀app.setGlobalPrefix("api"); // 所有路由将以 "api" 为前缀,例如 /api/userconst port = 3000; // 定义应用监听的端口号// 启动应用并监听指定端口await app.listen(port);
}// 启动应用
bootstrap();
在 logs.module.ts
中进行日志相关配置。
import { Module } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import * as winston from "winston";
import { utilities, WinstonModule, WinstonModuleOptions } from "nest-winston"; // 导入 nest-winston,用于 Nest 集成 Winston 日志库
import { Console } from "winston/lib/winston/transports";
import * as DailyRotateFile from "winston-daily-rotate-file";
import { LogEnum } from "src/enum/config.const";/*** 日志模块* - 集成 winston 日志库,支持控制台输出和文件轮转* - 日志级别、是否开启文件日志等参数可通过配置动态控制*/
@Module({imports: [// 异步注册 Winston 日志模块,支持依赖注入 ConfigService 动态配置WinstonModule.forRootAsync({inject: [ConfigService], // 注入配置服务useFactory: (configSerivice: ConfigService) => {// 控制台日志输出配置const consoleTransports = new Console({level: "info", // 日志级别为 infoformat: winston.format.combine(winston.format.timestamp(), // 添加时间戳utilities.format.nestLike() // 格式化日志为 Nest 风格),});// 按天轮转的 warn 级别日志文件配置const dailyTransports = new DailyRotateFile({level: "warn", // 日志级别为 warndirname: "logs", // 日志文件存储目录filename: "application-%DATE%.log", // 日志文件名,包含日期占位符datePattern: "YYYY-MM-DD-HH", // 日期格式zippedArchive: true, // 启用压缩存档maxSize: "20m", // 每个日志文件的最大大小为 20MBmaxFiles: "14d", // 保留日志文件的天数为 14 天format: winston.format.combine(winston.format.timestamp(), // 添加时间戳winston.format.simple() // 简单格式化日志),});// 按天轮转的 info 级别日志文件配置,日志级别可通过配置动态获取const dailyInfoTransports = new DailyRotateFile({level: configSerivice.get(LogEnum.LOG_LEVEL), // 日志级别通过配置获取dirname: "logs", // 日志文件存储目录filename: "info-%DATE%.log", // 日志文件名,包含日期占位符datePattern: "YYYY-MM-DD-HH", // 日期格式zippedArchive: true, // 启用压缩存档maxSize: "20m", // 每个日志文件的最大大小为 20MBmaxFiles: "14d", // 保留日志文件的天数为 14 天format: winston.format.combine(winston.format.timestamp(), // 添加时间戳winston.format.simple() // 简单格式化日志),});// 根据配置决定是否启用文件日志return {transports: [consoleTransports,...(configSerivice.get(LogEnum.LOG_ON)? [dailyTransports, dailyInfoTransports]: []), // 如果开启日志文件,则添加文件日志输出],} as WinstonModuleOptions;},}),],
})
export class LogsModule {}
扩充 .env
配置文件,分别控制日志的打开 和 日志等级。
LOG_ON=true
LOG_LEVEL=info
扩充 config.const.ts
文件,添加枚举。
export enum LogEnum {LOG_LEVEL = "LOG_LEVEL",LOG_ON = "LOG_ON",
}
在 user.controller.ts
中使用。
import {Controller,Get,HttpException,HttpStatus,Inject,LoggerService,Post,
} from "@nestjs/common"; // 导入控制器和 HTTP 请求装饰器
import { WINSTON_MODULE_NEST_PROVIDER } from "nest-winston";
/*** 用户控制器类* - 定义与用户相关的 HTTP 路由和处理逻辑* - 使用 `UserService` 提供的业务逻辑操作用户数据*/
@Controller("user") // 定义控制器的路由前缀为 "user"
export class UserController {// private logger = new Logger(UserController.name); // 创建 Logger 实例,用于记录日志/*** 构造函数* - 注入用户服务* @param userService 用户服务*/constructor(private userService: UserService,@Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService) {this.logger.log("UserController initialized"); // 控制器初始化时记录日志}...
}
数据库配置重构
接下来,我们需要把数据库的相关配置放入一个单独的 module 中去管理。
我们先进行一次版本控制。
git init git add ./git commit -m "first commit"
安装 ts-node,它是一个 TypeScript 执行引擎,能让我们在 Node.js 环境下直接运行 TypeScript 代码。
pnpm install ts-node --save-dev
修改 package.json
文件,添加如下内容。
"scripts": {..."typeorm": "typeorm-ts-node-commonjs"},
GitHub 提交记录
接下来,我们创建 ormconfig.ts
文件,把 app.module.ts
的相关配置迁移至此文件。
ormconfig.ts
import { DataSource, DataSourceOptions } from "typeorm";
import { Logs } from "./src/logs/logs.entity";
import { Roles } from "./src/roles/roles.entity";
import { Profile } from "./src/user/profile.entity";
import { User } from "./src/user/user.entity";
import { TypeOrmModuleOptions } from "@nestjs/typeorm";/*** TypeORM 连接参数配置* - 配置数据库连接信息、实体、同步选项等*/
export const connectionParams = {type: "mysql", // 数据库类型host: "127.0.0.1", // 数据库主机地址port: 3090, // 数据库端口username: "root", // 数据库用户名password: "example", // 数据库密码database: "test", // 数据库名称entities: [User, Profile, Roles, Logs], // 实体类数组,自动映射到数据库表synchronize: true, // 是否自动同步数据库结构(开发环境建议开启,生产环境建议关闭)// logging: ["error", "warn"], // 日志级别(可选,记录错误和警告)// logging: process.env.NODE_ENV === "development", // 仅开发环境开启日志logging: false, // 是否开启 SQL 日志
} as TypeOrmModuleOptions;/*** 导出 DataSource 实例* - 用于 TypeORM CLI 和运行时的数据源管理* - 包含迁移和订阅者配置*/
export default new DataSource({...connectionParams, // 继承上面的数据库连接参数migrations: ["src/migrations/**"], // 迁移文件路径subscribers: [], // 订阅者(可选)
} as DataSourceOptions);
GitHub 提交记录
初始化一个TypeORM项目,使用MySQL数据库,并选择mysql2作为驱动程序。
npx typeorm init --database mysql2
解决一些报错问题后,我们运行:
npx ts-node src/index.ts
即可得到数据库中的相关数据。
重构数据库、配置
GitHub 提交记录
修改 ormconfig.ts
文件
import { DataSource, DataSourceOptions } from "typeorm";
import { Logs } from "./src/logs/logs.entity";
import { Roles } from "./src/roles/roles.entity";
import { Profile } from "./src/user/profile.entity";
import { User } from "./src/user/user.entity";
import { TypeOrmModuleOptions } from "@nestjs/typeorm";
import * as fs from "fs";
import * as dotenv from "dotenv";
import { ConfigEnum } from "./src/enum/config.const";// 通过环境变量读取不同的.env文件
function getEnv(env: string): Record<string, unknown> {if (fs.existsSync(env)) {return dotenv.parse(fs.readFileSync(env));}return {};
}// 通过 dotenv 来解析不同的配置
function buildConnectionOptions() {const defaultConfig = getEnv(".env");const envConfig = getEnv(`.env.${process.env.NODE_ENV || "development"}`);const config = { ...defaultConfig, ...envConfig };return {type: config[ConfigEnum.DB_TYPE], // 数据库类型host: config[ConfigEnum.DB_HOST], // 数据库主机地址port: config[ConfigEnum.DB_PORT], // 数据库端口username: config[ConfigEnum.DB_USERNAME], // 数据库用户名password: config[ConfigEnum.DB_PASSWORD], // 数据库密码database: config[ConfigEnum.DB_DATABASE], // 数据库名称entities: [User, Profile, Roles, Logs], // 实体类数组,自动映射到数据库表synchronize: true, // 是否自动同步数据库结构(开发环境建议开启,生产环境建议关闭)// logging: ["error", "warn"], // 日志级别(可选,记录错误和警告)// logging: process.env.NODE_ENV === "development", // 仅开发环境开启日志logging: false, // 是否开启 SQL 日志} as TypeOrmModuleOptions;
}/*** TypeORM 连接参数配置* - 配置数据库连接信息、实体、同步选项等*/
export const connectionParams = buildConnectionOptions();/*** 导出 DataSource 实例* - 用于 TypeORM CLI 和运行时的数据源管理* - 包含迁移和订阅者配置*/
export default new DataSource({...connectionParams, // 继承上面的数据库连接参数migrations: ["src/migrations/**"], // 迁移文件路径subscribers: [], // 订阅者(可选)
} as DataSourceOptions);
修改 pageage.json
文件
"scripts": {..."build": "cross-env NODE_ENV=production nest build","start:prod": "cross-env NODE_ENV=production node dist/src/main",}
修改 tsconfig.json
文件
{"compilerOptions": {"module": "commonjs","declaration": true,"removeComments": true,"emitDecoratorMetadata": true,"experimentalDecorators": true,"allowSyntheticDefaultImports": true,"target": "ES2021","sourceMap": true,"outDir": "./dist","baseUrl": "./","incremental": true,"skipLibCheck": true,"strictNullChecks": false,"noImplicitAny": false,"strictBindCallApply": false,"forceConsistentCasingInFileNames": false,"noFallthroughCasesInSwitch": false}
}
修改 app.module.ts
文件的中的 Joi 校验规则。
validationSchema: Joi.object({NODE_ENV: Joi.string().valid("development", "production", "test") // 限制 NODE_ENV 的合法值.default("development"), // 默认值为 "development"DB_PORT: Joi.number().default(3306), // 数据库端口,默认值为 3306DB_HOST: Joi.alternatives().try(Joi.string().ip(),Joi.string().domain()), // 数据库主机地址,必须是合法的 IP 地址DB_TYPE: Joi.string().valid("mysql", "postgres"), // 数据库类型,不能为空DB_DATABASE: Joi.string().required(), // 数据库名称,不能为空DB_USERNAME: Joi.string().required(), // 数据库用户名,不能为空DB_PASSWORD: Joi.string().required(), // 数据库密码,不能为空DB_SYNC: Joi.boolean().default(false), // 是否自动同步数据库结构,默认值为 falseLOG_ON: Joi.boolean(),LOG_LEVEL: Joi.string(),}),
修改 logs.module.ts
文件,添加 createDailyRotateFileTransport 方法,控制日志的相关配置。抽离出来的原因为控制 logs 日志文件的生成。
import { Module } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import * as winston from "winston";
import { utilities, WinstonModule, WinstonModuleOptions } from "nest-winston"; // 导入 nest-winston,用于 Nest 集成 Winston 日志库
import { Console } from "winston/lib/winston/transports";
import * as DailyRotateFile from "winston-daily-rotate-file";
import { LogEnum } from "../enum/config.const";function createDailyRotateFileTransport(filename: string,level: string
): DailyRotateFile {return new DailyRotateFile({level, // 日志级别通过配置获取dirname: "logs", // 日志文件存储目录filename: `${filename}-%DATE%.log`, // 日志文件名,包含日期占位符datePattern: "YYYY-MM-DD-HH", // 日期格式zippedArchive: true, // 启用压缩存档maxSize: "20m", // 每个日志文件的最大大小为 20MBmaxFiles: "14d", // 保留日志文件的天数为 14 天format: winston.format.combine(winston.format.timestamp(), // 添加时间戳winston.format.simple() // 简单格式化日志),});
}/*** 日志模块* - 集成 winston 日志库,支持控制台输出和文件轮转* - 日志级别、是否开启文件日志等参数可通过配置动态控制*/
@Module({imports: [// 异步注册 Winston 日志模块,支持依赖注入 ConfigService 动态配置WinstonModule.forRootAsync({inject: [ConfigService], // 注入配置服务useFactory: (configSerivice: ConfigService) => {// 控制台日志输出配置const consoleTransports = new Console({level: "info", // 日志级别为 infoformat: winston.format.combine(winston.format.timestamp(), // 添加时间戳utilities.format.nestLike() // 格式化日志为 Nest 风格),});// 根据配置决定是否启用文件日志return {transports: [consoleTransports,...(configSerivice.get(LogEnum.LOG_ON)? [createDailyRotateFileTransport("info", "application"),createDailyRotateFileTransport("warn", "error"),]: []), // 如果开启日志文件,则添加文件日志输出],} as WinstonModuleOptions;},}),],
})
export class LogsModule {}
由于实体类将会继续扩充,因此我们可以采用获取文件目录的方式获取实体类数组。
const entitiesDir =process.env.NODE_ENV === "test"? [__dirname + "/**/*/entity.ts"]: [__dirname + "/**/*.entity{.js,.ts}"];return {...entities: entitiesDir, // 实体类数组,自动映射到数据库表} as TypeOrmModuleOptions;
至此,完成重构。
总结
本篇文章,我们进行了日志、数据库和相关配置的重构,使得我们的项目构成更加合理。
好啦,本篇文章到这里就要和大家说再见啦,祝你这篇文章阅读愉快,你下篇文章的阅读愉快留着我下篇文章再祝!
参考资料:
- DeepSeek
- NestJS 从入门到实战
相关文章:

NestJS——重构日志、数据库、配置
个人简介 👀个人主页: 前端杂货铺 🙋♂️学习方向: 主攻前端方向,正逐渐往全干发展 📃个人状态: 研发工程师,现效力于中国工业软件事业 🚀人生格言: 积跬步…...

c++数据结构8——二叉树的性质
一、二叉树的基本性质 示图1: 性质1:层节点数上限 在一棵二叉树中,第i层至多有2^{i-1}个节点(首层是第1层) 这个性质可以通过数学归纳法证明: 第1层:2^{1-1}2^01个节点(根节点&am…...

Window Server 2019--08 网络负载均衡与Web Farm
本章要点 1、了解网络负载均衡技术 2、掌握Web Farm核心原理 3、掌握如何使用Windows NLB搭建Web Farm环境 网络负载均衡技术将外部计算机发送的连接请求均匀的分配到服务器集群中的每台服务器上,接受到请求的服务器独立地响应客户的请求。 网络负载均衡技术还…...
arcgis字段计算器中计算矢量面的每个点坐标
python脚本 函数 def ExportCoordinates(feat):coors = []partnum = 0partcount = feat.partCountwhile partnum < partcount:part = feat.getPart(partnum)pnt = part.next()while pnt:coors.append("({}, {})".format(pnt.X,pnt.Y))pnt = part.next()if not p…...

SpringBoot:统一功能处理、拦截器、适配器模式
文章目录 拦截器什么是拦截器?为什么要使用拦截器?拦截器的使用拦截路径执行流程典型应用场景DispatcherServlet源码分析 适配器模式适配器模式定义适配器模式角色适配器模式的实现适配器模式应用场景 统⼀数据返回格式优点 统一处理异常总结 拦截器 什…...

AI Agent工具全景解析:从Coze到RAGflow,探索智能体自动化未来!
在人工智能技术持续深入行业应用的背景下,越来越多的企业和个人寻求通过自动化技术来提高效率和减少重复性劳动,AI Agent的崛起已经成为了不可忽视的趋势。AI Agent,即人工智能代理,是一种基于先进的人工智能技术,特别…...
GitLab CI流水线权限隔离
方案概述 本方案实现在GitLab CI/CD中根据不同人员的权限级别执行不同的流水线步骤,主要基于GitLab的以下特性: rules 条件判断variables 变量传递only/except 条件限制用户权限API查询 基础权限模型设计 1. 用户角色定义 角色描述对应GitLab权限De…...
xcode卡死问题,无论打开什么程序xcode总是在转菊花,重启电脑,卸载重装都不行
很可能是因为我们上次没有正常关闭Xcode,而Xcode保留了上次错误的一些记录,而这次打开Xcode依然去加载错误的记录,所以必须完全删除这些记录Xcode才能加载正常的项目。 那么也就是说,我们是不是只需要删除这部分错误记录文件就可以…...

Onvif协议:IPC客户端开发-IPC相机控制(c语言版)
前言: 本博文主要是借鉴OceanStar大神的博文,在他的博文的基础之上做了一部分修改与简化。 博文链接: Onvif协议:IPC客户端开发之鉴权_onvif鉴权方式-CSDN博客 Onvif协议:IPC客户端开发之PTZ控制_onvif ptz-CSDN博客…...

如何最简单、通俗地理解Pytorch?神经网络中的“梯度”是怎么自动求出来的?PyTorch的动态计算图是如何实现即时执行的?
PyTorch是一门科学——现代深度学习工程中的一把锋利利器。它的简洁、优雅、强大,正在让越来越多的AI研究者、开发者深度应用。 1. PyTorch到底是什么?为什么它重要? PyTorch是一个开源的深度学习框架,由Facebook AI Research(FAIR)于2016年发布,它的名字由两个部分组成…...

QT+opecv如何更改图片的拍摄路径
如何更改相机拍摄图片的路径 前言:基础夯实:效果展示:实现功能:遇到问题:未解决: 核心代码: 前言: 最近在项目开发中遇到需要让用户更改相机拍摄路径的问题,用户可自己选…...
WebSocket学习总结
WebSocket 是一种基于TCP的网络通信协议,允许浏览器和服务器之间进行全双工、实时、低延迟的双向数据传输。它突破了传统HTTP协议的限制(请求-响应模式),特别适合需要实时通信的场景(如聊天、实时数据推送、游戏等&…...

秋招Day11 - JVM - 类加载机制
了解类的加载机制吗? JVM是运行Java字节码,也就是运行.class文件的虚拟机,JVM把.class文件中描述类的数据结构加载到内存中,并对数据进行校验,解析和初始化,最终转化为JVM可以使用的类型(Klass…...

Webug4.0靶场通关笔记03- 第3关SQL注入之时间盲注(手注法+脚本法 两种方法)
目录 一、源码分析 1.分析闭合 2.分析输出 (1)查询成功 (2)查询失败 (3)SQL语句执行报错 二、第03关 延时注入 1.打开靶场 2.SQL手注 (1)盲注分析 (2…...
PostgreSQL 数据完整性检查工具对比:amcheck 与 pg_checksums
PostgreSQL 数据完整性检查工具对比:amcheck 与 pg_checksums PostgreSQL 提供了两种重要的数据完整性检查机制:amcheck 扩展和 pg_checksums 工具。它们在功能定位、检查层次和使用场景上有显著区别。 核心对比概览 特性amcheckpg_checksums检查对象…...

Vert.x学习笔记-什么是Handler
Vert.x学习笔记 在Vert.x中,Handler是一个核心概念,用于处理异步事件和回调。它是Vert.x响应式编程模型的核心组件之一,通过函数式接口的方式简化了异步编程的复杂性。 1. Handler的定义 Handler是一个函数式接口,定义如下&#…...
浏览器游戏的次世代革命:WebAssembly 3.0 实战指南
破局开篇:开发者必须跨越的性能鸿沟 在2025年,WebAssembly(WASM)技术已经成为高性能Web应用的核心驱动力。特别是WASM3引擎的广泛应用,使得在浏览器中实现主机级游戏画质成为可能。本文将深入探讨WASM3的关键特性、性…...
Java设计模式之工厂模式与策略模式简单案例学习
目录 1.前言2.工厂模式2.1 简单工厂方法2.2 静态工厂方法2.3 抽象工厂方法 3.策略模式4.区别与联系4.1定义与核心意图4.2 UML 结构对比4.3 关键组成对比4.4 应用场景对比 1.前言 最近接手的项目真的是太无语了,经历了多数人的编写,什么牛马鬼神写法都有&…...

【Echarts】象形图
目录 效果代码 效果 代码 <!-- 业务类型 --> <template><div class"ywlx" :style"{ --height: height }"><div class"header_count count_linear_bg"><div>当月业务总量<span class"common_count text_s…...
git 本地合并怎么撤回
在Git中,如果你已经执行了合并(merge)操作,但发现合并的结果不符合预期,你可以通过以下几种方式来撤销这次合并: 1. 使用git merge --abort 如果你在合并过程中还没有完成合并的提交(即合并冲…...

集星云推短视频矩阵系统的定制化与私有化部署方案
在当今数字化营销时代,短视频矩阵系统成为众多企业和机构拓展影响力、实现精准营销的关键工具。集星云推短视频矩阵系统凭借其强大的功能和灵活的定制性,为企业提供了全方位的解决方案。 一、API接口定制:无缝对接自有系统 集星云推短视频矩…...
npm run build 报错:Some chunks are larger than 500 KB after minification
当我们的 Vue 项目太大,使用 npm run build 打包项目的时候,就有可能会遇到以下报错: (!) Some chunks are larger than 500 kB after minification. Consider: - Using dynamic import() to code-split the application - Use build.rollup…...

XCTF-web-file_include
解析 <?php highlight_file(__FILE__); // 高亮显示当前PHP文件源代码 include("./check.php"); // 包含检查文件(可能包含安全过滤逻辑)if(isset($_GET[filename])) { // 检查是否传入filename参数$filename $_GET[f…...

5.28 后端面经
为什么golang在并发环境下更有优势 Go语言(Golang)在并发环境下的优势主要源自其设计哲学和内置的并发机制,这些机制在语言层面提供了高效、简洁且安全的并发编程工具。以下是其核心优势的详细分析: 1. Goroutine:轻量…...

CPP中CAS std::chrono 信号量与Any类的手动实现
前言 CAS(Compare and Swap) 是一种用于多线程同步的原子指令。它通过比较和交换操作来确保数据的一致性和线程安全性。CAS操作涉及三个操作数:内存位置V、预期值E和新值U。当且仅当内存位置V的值与预期值E相等时,CAS才会将内存位…...

PHP生成pdf方法
1:第一种方法: 主要使用PHP的扩展 【 “spatie/browsershot”: “3.57”】 使用这个扩展生成PDF需要环境安装以下依赖 1.1:NPM【版本:9.2.0】 1.2:NODE【版本:v18.19.1】 1.3:puppeteer【npm in…...

【Android笔记】记一次 CMake 构建 Filament Android 库的完整排错过程(安卓交叉编译、CMake、Ninja)
写在前面的话,为了保持Sceneform-EQR始终是采用最新的filament,每隔一段时间我都会编译filament,并根据新增内容完善Sceneform-EQR。 现由于更换电脑,环境需重新配置。简单记录下编译出错和解决方式。 Sceneform-EQR 是EQ对谷歌“…...

C#中的BeginInvoke和EndInvoke:异步编程的双剑客
文章目录 引言1. BeginInvoke和EndInvoke的基本概念1.1 什么是BeginInvoke和EndInvoke1.2 重要概念解释 2. 委托中的BeginInvoke和EndInvoke2.1 BeginInvoke方法2.2 EndInvoke方法2.3 两者的关系 3. 使用方式与模式3.1 等待模式3.2 轮询模式3.3 等待句柄模式3.4 回调模式 4. 底…...

告别延迟!modbus tcp转profine网关助力改造电厂改造升级
发电需求从未如此旺盛。无论您是为客户发电还是为自身运营发电,您都需要提高运营效率,并在资产老化、资源萎缩的情况下,紧跟不断变化的法规。如今,智能系统和技术能够帮助您实现运营转型,提高可视性并实现关键流程自动…...

《软件工程》第 5 章 - 需求分析模型的表示
目录 5.1需求分析与验证 5.1.1 顺序图 5.1.2 通信图 5.1.3 状态图 5.1.4 扩充机制 5.2 需求分析的过程模型 5.3 需求优先级分析 5.3.1 确定需求项优先级 5.3.2 排定用例分析的优先顺序 5.4 用例分析 5.4.1 精化领域概念模型 5.4.2 设置分析类 5.4.3 构思分析类之间…...