17 | 实现简洁架构的 Biz 层
提示:
- 所有体系课见专栏:Go 项目开发极速入门实战课;
- 欢迎加入 云原生 AI 实战 星球,12+ 高质量体系课、20+ 高质量实战项目助你在 AI 时代建立技术竞争力(聚焦于 Go、云原生、AI Infra);
- 本节课最终源码位于 fastgo 项目的 feature/s13 分支;
更详细的课程版本见:Go 项目开发中级实战课:26 | 业务实现(3):实现 Biz 层代码
Biz 层依赖 Store 层,所以实现了 Store 层代码之后,便可以实现 Biz 层代码。Biz 层代码,主要用来实现系统中 REST 资源的各类业务操作,例如用户资源的增删改查等。
整个 fastgo 项目的设计较为规范,规范化的项目设计带来的优点之一是开发方式的一致性和开发效率的提升。fastgo 项目 Biz 层的开发方式与 Store 层的开发方式保持一致。
API 接口定义
在开发 Biz 层代码之前,需要先定义好 API 接口的请求入参和返回参数。为此,新建了 pkg/api/apiserver/v1/post.go 文件和 pkg/api/apiserver/v1/user.go 文件,分别保存了用户接口和博客接口的请求入参和返回参数。
因为请求入参和返回参数(例如 CreateUserRequest
和 CreateUserResponse
)会提供给接口调用方(客户端),所以需要将接口定义保存在 pkg/api 目录下。另外,考虑到未来 fastgo 可能会加入多个服务,每个服务都有自己的 API 定义,fastgo 项目选择了将每个服务的 API 定义保存在独立的服务目录下,例如 pkg/api/apiserver。
考虑到未来 API 接口的版本升级,fastgo 项目将接口进行了版本化处理,v1 版本的接口保存在 pkg/api/apiserver/v1 目录下,v2 版本的接口保存在 pkg/api/apiserver/v2 目录下。
IBiz 接口定义及实现
Biz 层代码保存 internal/apiserver/biz/biz.go 文件中,接口名为 IBiz,定义如下:
// IBiz 定义了业务层需要实现的方法.
type IBiz interface {// 获取用户业务接口.UserV1() userv1.UserBiz// 获取帖子业务接口.PostV1() postv1.PostBiz// 获取帖子业务接口(V2版本).// PostV2() post.PostBiz
}
IBiz
接口包含了 User 资源和 Post 资源 v1 版本的接口,通过抽象工厂设计模式返回对应资源的接口。在 Go 项目开发中,业务层代码的代码量通常最大、变动最频繁,并且随着项目的迭代,可能会出现不兼容的变更。
这时需要对外暴露 v2 版本的 API 接口。因此,为了提高代码的可维护性并保留未来的扩展能力,Biz 层代码的存放结构如下:
internal/apiserver/biz/
├── biz.go
├── v1/ # v1 版本代码实现
│ ├── post/ # 提高代码可维护性,不同资源的代码实现分别存放在不同的目录中
│ │ └── post.go
│ └── user/
│ └── user.go
└── v2/ # 保留扩展能力:v2 代码保存目录
上述代码,将不同版本的代码保存在不同的版本化目录中,不同 REST 资源的业务逻辑实现保存在跟资源对应的目录中。不同资源的业务逻辑代码均在其对应的目录中实现,可以在目录级别隔离不同资源的代码实现,有利于提高代码的稳定性,并降低维护的复杂度。
Biz 层依赖于 Store 层的实现,所以在创建 IBiz
实例时,需要传入 IStore
类型的实例,IBiz
实例由 NewBiz
函数创建:
// biz 是 IBiz 的一个具体实现.
type biz struct {store store.IStore
}// 确保 biz 实现了 IBiz 接口.
var _ IBiz = (*biz)(nil)// NewBiz 创建一个 IBiz 类型的实例.
func NewBiz(store store.IStore) *biz {return &biz{store: store}
}
IBiz
的实现跟 IStore
的实现是保持一致,其他代码,本节不再详解。
UserBiz 接口定义及实现
User 资源的 Biz 层代码实现位于 internal/apiserver/biz/v1/user/user.go 文件中,其接口定义为 UserBiz,代码如下:
// UserBiz 定义处理用户请求所需的方法.
type UserBiz interface {Create(ctx context.Context, rq *apiv1.CreateUserRequest) (*apiv1.CreateUserResponse, error)Update(ctx context.Context, rq *apiv1.UpdateUserRequest) (*apiv1.UpdateUserResponse, error)Delete(ctx context.Context, rq *apiv1.DeleteUserRequest) (*apiv1.DeleteUserResponse, error)Get(ctx context.Context, rq *apiv1.GetUserRequest) (*apiv1.GetUserResponse, error)List(ctx context.Context, rq *apiv1.ListUserRequest) (*apiv1.ListUserResponse, error)UserExpansion
}// UserExpansion 定义用户操作的扩展方法.
type UserExpansion interface {
}
UserBiz
接口中的方法,同样也分为了两大类:标准资源 CURD 接口和扩展接口,扩展接口中实现了用户登录、Token 刷新、密码修改等方法。实现 UserBiz
接口的 Go 结构体是 *userBiz
。
创建用户:Create 方法实现
userBiz
结构体的 Create
方法实现如下:
// Create 实现 UserBiz 接口中的 Create 方法.
func (b *userBiz) Create(ctx context.Context, rq *apiv1.CreateUserRequest) (*apiv1.CreateUserResponse, error) {var userM model.User_ = copier.Copy(&userM, rq)if err := b.store.User().Create(ctx, &userM); err != nil {return nil, err}return &apiv1.CreateUserResponse{UserID: userM.UserID}, nil
}
为了提高开发效率,减少不必要的代码量,Create
方法使用了 [github.com/jinzhu/copier](https://github.com/jinzhu/copier)
的 Copy
函数给目标结构体变量 userM
赋值。
在 Create
方法中,通过 b.store.User().Create(ctx, &userM)
方法调用,将数据保存在数据库中。
在 Go 项目开发中,数据库禁止保存明文密码。用户密码在入库前需要进行加密处理。为了加密明文密码字符串,fastgo 引入了 github.com/onexstack/onexstack/pkg/auth
包,auth 包中的 Encrypt
函数可以用来加密一个明文密码字符串。
因为在入库前都需要对明文密码进行加密,所以,很自然的想到可以通过实现 GORM 框架的 BeforeCreate
钩子来实现。修改 internal/apiserver/model/hook.go 文件,添加以下代码:
import (..."github.com/onexstack/onexstack/pkg/auth"
)// BeforeCreate 在创建数据库记录之前加密明文密码.
func (m *User) BeforeCreate(tx *gorm.DB) error {// Encrypt the user password.var err errorm.Password, err = auth.Encrypt(m.Password)if err != nil {return err}return nil
}
更新用户:Update 方法实现
userBiz
结构体的 Update
方法实现如下:
// Update 实现 UserBiz 接口中的 Update 方法.
func (b *userBiz) Update(ctx context.Context, rq *apiv1.UpdateUserRequest) (*apiv1.UpdateUserResponse, error) {userM, err := b.store.User().Get(ctx, where.T(ctx))if err != nil {return nil, err}if rq.Username != nil {userM.Username = *rq.Username}if rq.Email != nil {userM.Email = *rq.Email}if rq.Nickname != nil {userM.Nickname = *rq.Nickname}if rq.Phone != nil {userM.Phone = *rq.Phone}if err := b.store.User().Update(ctx, userM); err != nil {return nil, err}return &apiv1.UpdateUserResponse{}, nil
}
在更新用户时,根据是否传入待更新的字段来判断是否更新该字段。这样的设计方式,可以通过一个更新接口实现字段的选择性更新。
查询用户列表:List 方法实现
userBiz
结构体的 List
方法实现如下述代码所示:
// List 实现 UserBiz 接口中的 List 方法.
func (b *userBiz) List(ctx context.Context, rq *apiv1.ListUserRequest) (*apiv1.ListUserResponse, error) {whr := where.P(int(rq.Offset), int(rq.Limit))count, userList, err := b.store.User().List(ctx, whr)if err != nil {return nil, err}var m sync.Mapeg, ctx := errgroup.WithContext(ctx)// 设置最大并发数量为常量 MaxConcurrencyeg.SetLimit(known.MaxErrGroupConcurrency)// 使用 goroutine 提高接口性能for _, user := range userList {eg.Go(func() error {select {case <-ctx.Done():return nildefault:count, _, err := b.store.Post().List(ctx, where.T(ctx))if err != nil {return err}converted := conversion.UserodelToUserV1(user)converted.PostCount = countm.Store(user.ID, converted)return nil}})}if err := eg.Wait(); err != nil {slog.ErrorContext(ctx, "Failed to wait all function calls returned", "err", err)return nil, err}users := make([]*apiv1.User, 0, len(userList))for _, item := range userList {user, _ := m.Load(item.ID)users = append(users, user.(*apiv1.User))}slog.DebugContext(ctx, "Get users from backend storage", "count", len(users))return &apiv1.ListUserResponse{TotalCount: count, Users: users}, nil
}
List
方法会查询所有的用户列表,并统计用户所属的博客数,这种遍历多个列表,并且针对列表中每个元素都有耗时处理逻辑的代码,可能会导致 List
方法执行时间较久。为了提高 List
方法的性能,List
方法中使用了 errgroup 包,并发查询每个用户的博客数。
在上述代码中 eg.SetLimit
方法调用的作用是限制程序中同时运行的 Go 协程数量,以避免过多协程并发任务导致的系统资源过载(如高 CPU 和内存占用或高 I/O 消耗)。通过 eg.Go
启动的 goroutine 会按照 SetLimit
的限制规则执行。当已经有 MaxErrGroupConcurrency
个任务在运行时,新任务会阻塞,直到某个正在运行的任务完成。
因为会并发处理 userList
列表中的每个元素,所以需要一个并发安全的数据类型,保存处理后的数据。Go 标准库提供了 sync.Map
数据类型,该类型是并发安全的,可以直接在 Go 协程中使用 sync.Map
的 Store
方法添加 key-value 对。在上述代码中,使用 m.Store
保存了 *apiv1.User
类型的数据。
Store 层返回的数据类型为 *model.UserM
,需要转换为 Biz 层使用的数据类型 *apiv1.User
。*model.UserM
和 *apiv1.User
数据类型之间的相互转换,在 Biz 层经常会发生,为了提高代码的可维护性,将这类转换实现统一保存在 internal/apiserver/pkg/conversion 目录下的 conversion 包中。
Store 层返回的用户列表是降序排列的,为了保证 List
返回的列表也是降序排列的,上述代码的最后,使用以下代码段重新排列了 sync.Map
类型变量 m
中保存的数据:
for _, item := range userList {user, _ := m.Load(item.ID)users = append(users, user.(*apiv1.User))
}
相关文章:
17 | 实现简洁架构的 Biz 层
提示: 所有体系课见专栏:Go 项目开发极速入门实战课;欢迎加入 云原生 AI 实战 星球,12 高质量体系课、20 高质量实战项目助你在 AI 时代建立技术竞争力(聚焦于 Go、云原生、AI Infra);本节课最终…...
DNS解析错误要怎么处理
在互联网时代,网络已经成为人们生活和工作中不可或缺的一部分。然而,当遇到DNS 解析错误时,原本畅通无阻的网络访问会突然陷入困境,让人感到十分困扰。DNS,即域名系统,它如同互联网的电话簿,将人…...
【Rust基础】Rust后端开发常用库
使用Rust有一段时间了,期间尝试过使用Rust做后端开发、命令行工具开发,以及做端侧模型部署,也尝试过交叉编译、FFI调用等,也算是基本入门了。在用Rust做后端接口开发时,常常会找不到一些合适库,而这些库在J…...
【附JS、Python、C++题解】Leetcode面试150题(9)——三数之和
一、题目 15. 三数之和 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足: i!j、i!k 且 j! k ,同时还满足:nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意…...
脑电波控制设备:基于典型相关分析(CCA)的脑机接口频率精准解码方法
文章目录 前言一、CCA的用途二、频率求解思路三、输入数据结构四、判断方法五、matlab实践1.数据集获取及处理2.matlab代码3.运行及结果 六、参考文献 前言 在脑机接口(BCI)领域,有SSVEP方向,中文叫做稳态视觉诱发电位,当人观看闪烁的视觉刺激…...
Golang学习笔记_44——命令模式
Golang学习笔记_41——观察者模式 Golang学习笔记_42——迭代器模式 Golang学习笔记_43——责任链模式 文章目录 一、核心概念1. 定义2. 解决的问题3. 核心角色4. 类图 二、特点分析三、适用场景1. 事务管理系统2. 多媒体遥控器3. 操作审计系统 四、Go语言实现示例五、高级应用…...
vue项目如何实现条件查询?
目录 1.前端 2.后端 3.mybatis的sql语句 结语 1.前端 说白了就是,无论该参数是否是空字符串,都会传递到后端。(反正不是null就行)。 2.后端 在controller层中,使用RequestParam注解接收名为registerName的参数&…...
windows平台的ffmpeg编译使用
windows平台的ffmpeg编译使用 一、现状 本人使用libgdx开发galGame,发现扩展包gdx-video不支持mp4,不能忍,正好看到官网有支持自定义编译的文档,所以操作一下,自定义编译。本文重点在于操作windows平台,linux平台太简单了。 整个过程包括如下几个步骤。 二、代码下载…...
Android 14 昼夜色切换多屏时候非主屏的Activity无法收到onConfigurationChanged
记录一下遇见的这个问题和查看源码的过程 首先先说遇见的问题 Android 14 昼夜色切换多屏时候 非主屏的Activity 会经常收不到 onConfigurationChanged的回调 分析原因源码中ActivityThread::performActivityConfigurationChanged 里面 private Configuration performActivi…...
NLP常见任务专题介绍(4)-ConditionalGeneration和CasualLM区别
在 transformers 库中,ConditionalGeneration 和 CausalLM 是两种不同类型的语言模型,各自适用于不同的任务: 类别Conditional Generation (条件生成)CausalLM (因果语言模型)核心区别依赖输入 条件 生成文本只能 自回归 生成文本训练方式Encoder-Decoder(编码-解码) 结构…...
【vue+excel】导出excel(目前是可以导出两个sheet)
项目里经常用到的导出ecxel功能是默认导出一个sheet页 现在需要导出两个sheet,一个是总计,另一个是明细 效果如下: 我就在现有的单个导出的功能上改造了一下,只支持导出两个(代码不够灵活,如果需要多个&…...
【数据结构】6栈
0 章节 3.1到3.3小节。 认知与理解栈结构; 列举栈的操作特点。 理解并列举栈的应用案例。 重点 栈的特点与实现; 难点 栈的灵活实现与应用 作业或思考题 完成学习测试2,? 内容达成以下标准(考核…...
【C++】每日一练(有效的括号)
本篇博客给大家带来的是用C语言来解答有效的括号! 🐟🐟文章专栏:每日一练 🚀🚀若有问题评论区下讨论,我会及时回答 ❤❤欢迎大家点赞、收藏、分享! 今日思想:不服输的少年…...
数字化新零售与 AI 大模型,如何重塑大健康赛道?
在数字化浪潮中,大健康赛道正经历深刻变革。数字化新零售营销模式的兴起,与 AI 大模型的强大能力相结合,为大健康领域带来了全新的发展机遇。 数字化新零售营销模式融合线上线下,运用大数据、云计算分析消费者行为,实…...
Android实现Socket通信
问题: 我在Android端对接后端Socket服务实现消息模块的时候,使用WebSocketClient 库,手动构造了订阅和发送消息的 STOMP 帧,,Socket连接成功建立,但是当用户发送消息的时候,对方无法接受到。 …...
✅ Vue 3 响应式写法小抄表(Composition API 实战模板)
📦 引入核心响应式工具 import { ref, reactive, computed, watch, toRefs } from vue1️⃣ 基本类型(string / number / boolean) → 推荐使用 ref() const username ref() const count ref(0) const isLoading ref(false)2️⃣ 对象/表…...
Python数据分析之数据可视化
Python 数据分析重点知识点 本系列不同其他的知识点讲解,力求通过例子让新同学学习用法,帮助老同学快速回忆知识点 可视化系列: Python基础数据分析工具数据处理与分析数据可视化机器学习基础 四、数据可视化 图表类型与选择 根据数据特…...
TCP协议支持全双工原因TCP发送接收数据是生产者消费者模型
一、TCP支持全双工的原因 TCP协议支持全双工,即使用TCP协议进行通信时,服务端和客户端可以同时进行数据的发送和接收,互不干扰,实现同时双向传输数据。 这是因为使用TCP协议通信时,读写套接字的文件描述符既用来发送…...
【ODHead】BEVDet的 CenterHead的推理和拓展到蒸馏损失的算法细节
文章目录 背景常识1、BEVDet的CenterHead整体方案2、蒸馏部分3、输出 preds_dicts 部分3.1、headmap3.2、bbox3.3、Mask掩膜3.4、损失 背景常识 在BEVDet和BEVFormer里,使用了不同的3D detection head(BEVDet用了centerhead,BEVFormer用了de…...
在 CentOS 7 上安装 PHP 7.3
在 CentOS 7 上安装 PHP 7.3 可以按照以下步骤进行操作: 1. 安装必要的依赖和 EPEL 仓库 EPEL(Extra Packages for Enterprise Linux)是为企业级 Linux 提供额外软件包的仓库,yum-utils 用于管理 yum 仓库。 sudo yum install -…...
vue+dhtmlx-gantt 实现甘特图-快速入门【甘特图】
文章目录 一、前言二、使用说明2.1 引入依赖2.2 引入组件2.3 引入dhtmlx-gantt2.4 甘特图数据配置2.5 初始化配置 三、代码示例3.1 Vue2完整示例3.2 Vue3 完整示例 四、效果图 一、前言 dhtmlxGantt 是一款功能强大的甘特图组件,支持 Vue 3 集成。它提供了丰富的功…...
关于ModbusTCP/RTU协议对接Ethernet/IP(CIP)协议的方案
IGT-DSER智能网关模块支持西门子、倍福(BECKHOFF)、罗克韦尔AB,以及三菱、欧姆龙等各种品牌的PLC之间通讯,支持Ethernet/IP(CIP)、Profinet(S7),以及FINS、MC等工业自动化常用协议,同时也支持PLC与Modbus协议的工业机器人、智能仪…...
python-leetcode 49.二叉树中的最大路径和
题目: 二叉树中的路径被定义为一条节点序列,序列中每对相邻节点之间都存在一条边,同一个节点在一条路径序列中至多出现一次,该路径至少包含一个节点,且不一定经过根节点。 路径和是路径中各节点值得总和,…...
C语言基础知识04
指针 指针概念 指针保存地址,地址是字节的编号 指针类型和保存的地址类型要一直 使用时注意,把地址转换为&变量的格式来看 int a[3]; a转为&a[0] 指针的大小 64bit 固定8字节, 32bit 固定4字节 指针…...
使用 Golang 操作 MySQL
在Go语言中,操作SQL数据库,通常会用到一些第三方库来简化数据库的连接、查询和操作过程。其中原生的 database/sql go-sql-driver/mysql 库更符合sql语句使用习惯。 安装 go get github.com/go-sql-driver/mysql 直接上代码来演示基本的创建ÿ…...
前端面试:cookie 可以实现不同域共享吗?
在前端开发中,Cookie 不能直接实现不同域之间的共享。Cookie 的作用域受到域的限制,浏览器不会允许一个域下的 Cookie 被另一个域访问。这是为了保护用户隐私及安全,防止跨站请求伪造(CSRF)等安全问题。 Cookie 的基本…...
MyBatis-Plus接入和简单使用
如何接入 https://baomidou.com/getting-started/ 简单使用方法 使用 MyBatis-Plus 时,大多数场景下不需要编写 XML 和 SQL,因为它提供了强大的通用 CRUD 操作和条件构造器。但以下情况可能需要手动编写 SQL: 1. 不需要写 XML/SQL 的场景 …...
【Go万字洗髓经】Golang内存模型与内存分配管理
本文目录 1. 操作系统中的虚拟内存分页与进程管理虚拟内存与内存隔离 2. Golang中的内存模型内存分配流程内存单元mspan线程缓存mcache中心缓存mcentral全局堆缓存mheapheapArena空闲页索引pageAlloc 3. Go对象分配mallocgc函数tiny对象分配内存 4.结合GMP模型来看内存模型tiny…...
mov格式视频如何转换mp4?
mov格式视频如何转换mp4?在日常的视频处理中,经常需要将MOV格式的视频转换为MP4格式,以兼容更多的播放设备和平台。下面给大家分享如何将MOV视频转换为MP4,4款视频格式转换工具分享。 一、牛学长转码大师 牛学长转码大师是一款功…...
鸿蒙OS开发ForEach循环渲染
摘要 在ForEach循环渲染过程中,如果修改列表项中的数据,但是UI页面不会刷新。在最近开发公司app时遇到了这个问题,经过查看官方文档找到了解决方式 官方地址:数据变化不刷新 一、具体解决方案 思路:通过父子组件传…...
【算法】DFS、BFS、拓扑排序
⭐️个人主页:小羊 ⭐️所属专栏:算法 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 持续更新中...1、DFS2、BFSN 叉树的层序遍历二叉树的锯齿形层序遍历二叉树最大宽度 3、多源BFS腐烂的苹果 4、拓扑排序 持续更新中…...
【Godot4.0】贝塞尔曲线在游戏中的实际应用
概述 之前研究贝塞尔曲线绘制,完全是以绘图函数,以及实现节点连接为思考。并没有实际考虑贝塞尔曲线在游戏中的应用。今日偶然看到悦千简一年多前发的一个用贝塞尔曲线实现追踪弹或箭矢效果,还有玩物不丧志的老李杀戮尖塔系列中的卡牌动态箭…...
MongoDB 数据导出与导入实战指南(附完整命令)
1. 场景说明 在 MongoDB 运维中,数据备份与恢复是核心操作。本文使用 mongodump 和 mongorestore 工具,演示如何通过命令行导出和导入数据,解决副本集连接、路径指定等关键问题。 2. 数据导出(mongodump) 2.1 导出命…...
『Rust』Rust运行环境搭建
文章目录 rust编译工具rustupVisual Studio VS Code测试编译手动编译VSCode编译配置 参考完 rust编译工具rustup https://www.rust-lang.org/zh-CN/tools/install 换源 RUSTUP_DIST_SERVER https://rsproxy.cn RUSTUP_UPDATE_ROOT https://rsproxy.cn修改rustup和cargo的安…...
CPU+GPU结合的主板设计思路与应用探讨
在高性能计算和图形处理需求不断增长的背景下,CPUGPU结合的主板设计逐渐成为硬件架构的重要趋势。本文将探讨基于CPUGPU架构的主板设计思路、关键技术考量以及应用前景。 1. 设计思路概述 CPU(中央处理器)擅长处理复杂的逻辑运算和多任务控制…...
latex问题汇总
latex问题汇总 环境问题1 环境 texlive2024 TeXstudio 4.8.6 (git 4.8.6) 问题1 编译过程有如下错 ! Misplaced alignment tab character &. l.173 International Conference on Infrared &Millimeter Waves, 2004: 667--... I cant figure out why you would wa…...
学习springboot-Bean管理(Bean 注册,Bean 扫描)
Bean 扫描 可以浏览下面的博客链接 :spring 学习 (注解)-CSDN博客 在学习spring 注解时,我们使用 Component ,Service,Controller等 这样的注解,将目标类信息,传递给IOC容器,为其创…...
iOS开发,SQLite.swift, Missing argument label ‘value:‘ in call问题
Xcode16中,集成使用SQLite.swift,创建表的时候: let id Expression<Int64>("id"),报错Missing argument label value: in call 直接使用SQLite.Expression<Int64>("id") 或者定义一个全局typ…...
【GIT】重新初始化远程仓库
有的时候我们克隆远端仓库会出错: git clone --depth 1 git116.*.*.*:/srv/customs.git D:\dev\projects\kdy\customs11\customs Cloning into D:\dev\projects\kdy\customs11\customs... remote: Enumerating objects: 1494, done. remote: Counting objects: 100…...
Vue3中 ref 与 reactive区别
ref 用途: ref 通常用于创建一个响应式的基本类型数据(如 string、number、boolean 等),但它也可以用于对象或数组 返回值: ref 返回一个带有 .value 属性的对象,访问或修改数据需要通过 .value 进行 使用场景: …...
apollo3录音到wav播放解决方法
SDK DEMO项目:ap3bp_evb_vos_pcm_recorder_20210901 pcm_recorder.c //***************************************************************************** // // Options // //***************************************************************************** #define PRINT…...
信号处理抽取多项滤波的数学推导与仿真
昨天的《信号处理之插值、抽取与多项滤波》,已经介绍了插值抽取的多项滤率,今天详细介绍多项滤波的数学推导,并附上实战仿真代码。 一、数学变换推导 1. 多相分解的核心思想 将FIR滤波器的系数 h ( n ) h(n) h(n)按相位分组,每…...
Java网络多线程
网络相关概念: 关于访问: IP端口 因为一个主机上可能有多个服务, 一个服务监听一个端口,当你访问的时候主机通过端口号就能知道要和哪个端口发生通讯.因此一个主机上不能有两个及以上的服务监听同一个端口. 协议简单来说就是数据的组织形式 好像是两个人交流一样,要保证自己说…...
linux centos 忘记root密码拯救
在CentOS 7中,如果忘记root密码,可以通过修改系统启动参数进入单用户模式或紧急模式进行重置。以下是两种常用方法,适用于物理机或虚拟机环境: 方法一:通过rd.break参数重置密码 步骤: 重启系统并进入GRU…...
C# 事件使用详解
总目录 前言 在C#中,事件(Events)是一种基于委托的重要机制,用于实现对象之间的松耦合通信。它通过发布-订阅模式(Publisher-Subscriber Pattern),允许一个对象(发布者)…...
flink cdc同步mysql数据
一、api 添加依赖 <dependency><groupId>org.apache.flink</groupId><artifactId>flink-connector-mysql-cdc</artifactId><!-- 请使用已发布的版本依赖,snapshot 版本的依赖需要本地自行编译。 --><version>3.3-SNAP…...
tomcat负载均衡配置
这里拿Nginx和之前做的Tomcat 多实例来实现tomcat负载均衡 1.准备多实例与nginx tomcat单机多实例部署-CSDN博客 2.配置nginx做负载均衡 upstream tomcat{ server 192.168.60.11:8081; server 192.168.60.11:8082; server 192.168.60.11:8083; } ser…...
Ceph(1):分布式存储技术简介
1 分布式存储技术简介 1.1 分布式存储系统的特性 (1)可扩展 分布式存储系统可以扩展到几百台甚至几千台的集群规模,而且随着集群规模的增长,系统整体性能表现为线性增长。分布式存储的水平扩展有以下几个特性: 节点…...
16、JavaEE核心技术-EL与 JSTL
EL与 JSTL 实践 一. EL(Expression Language) EL(表达式语言)是 JSP 2.0 中引入的一种简单的脚本语言,用于在 JSP 页面中简化数据的访问和显示。它通过一种类似于 JavaScript 的语法,允许开发者在 JSP 页面…...
RabbitMQ报错:Shutdown Signal channel error; protocol method
报错信息: Shutdown Signal: channel error; protocol method: #method<channel.close>(reply-code406, reply-textPRECONDITION_FAILED - unknown delivery tag 1, class-id60, method-id80) 原因 默认情况下 RabbitMQ 是自动ACK(确认签收&…...