Go项目(商品微服务-2)
文章目录
- 简介
- handler
- 商品分类
- 轮播图
- 品牌和品牌与分类
- oss
- 前端直传
- 库存服务
- 数据不一致
- redis 分布式锁
- 小结
简介
- 开发商品微服务 API 层
- 类似的,将 user-web 目拷贝一份,全局替换掉 user-web
- 修改 config
- 去掉不用的配置
- 更新本地和远程 nacos 配置文件
- 把 proto 文件拷过来再生成一下,全部拷过来也行
- Q:grpc 和 HTTP 调用的优缺点是什么?
- Q:如何将我们的服务改为 HTTP 调用?
- 更新 initialize/router
ApiGroup := Router.Group("/g/v1")
- router 目录是单独设置的,方便管理,也需要更新;通过 package 区分各接口,也就是需要在 api 下新建目录(goods/brand/category/banners)
- 修改 global 文件
- 修改 initialize/srv_conn 文件,服务发现并拨号连接 goods_srv
- middlewares 部分是各微服务的共用代码,可以抽出来共用
- 但要有专人维护,并做好版本管理,避免因为改动影响所有微服务
- 或者就放在各微服务,代码虽多但是隔离性好
- 启动项目测试能否注册并发现 goods_srv
- 在 router 中临时配置一个接口
- 使用 yapi 配置商品服务的测试环境,请求我们的接口
- 配置环境别忘了带 token,这个是各服务都要验证的,可以先请求密码登录接口获取一个,过期时间设置长一些
handler
- 开始实现各接口
- 新建 test 目录,为每个接口编写 UT 测试,主要是看响应,先不必 assert
- 商品列表 List
- 获取商品列表需要一些请求参数,前端页面和后端(API)严格遵守文档(在yapi定义)
- API 这里获取页面请求参数:
ctx.DefaultQuery()
,按照 proto 的定义拼装好向 srv 的请求参数 - 注意检查用户输入,避免后端处理时异常
- 除了在文档中定义请求参数,还要定义返回值
- 注:yapi 是在前端页面和 API 层之间的一个东西,定义的请求参数和返回值都有两用
- 获取商品列表需要一些请求参数,前端页面和后端(API)严格遵守文档(在yapi定义)
- 注册
- 注册中心可能换成别的(etcd/zookeper),所以新建目录 util/register/consul,定义注册逻辑
- 连接 consul 需要 host 和 port,这里采用 go 语言的风格,由于没有构造方法,需要定义结构体并给这它关联函数,然后使用 NewRegistryClient 作为构造函数使用,返回 Registry 实例
type Registry struct {Host stringPort int }func NewRegistryClient(host string, port int) RegistryClient {return &Registry{Host: host,Port: port,} }
- 可以用 python 面向对象思想的 class/method/init 理解
- 但这里为了不限制 struct 的名称(不管是哪个 struct),使用 interface 作为返回值类型,表明只要 return 的这个实例实现了接口中规定的这两个方法,是哪个都行
type RegistryClient interface {Register(address string, port int, name string, tags []string, id string) errorDeRegister(serviceId string) error }
- go 语言中很多源码都用这种鸭子类型,在 proto 文件生成的 go stub 中也可以看到
type GoodsClient interface {//商品接口GoodsList(ctx context.Context, in *GoodsFilterRequest, opts ...grpc.CallOption) (*GoodsListResponse, error)// .... }type goodsClient struct {cc grpc.ClientConnInterface }func NewGoodsClient(cc grpc.ClientConnInterface) GoodsClient {return &goodsClient{cc} }
- 注销
- 在 main 函数用协程启动服务器,接收中断信号优雅退出
- 新建商品 New
- 定义路由,路由建议分多个 group 管理(商品、品牌、分类,分文件管理);下面这些 API 都是,记得在 router 下定义路由
- 这个接口的基本逻辑是接收 Form 数据,封装数据,转为 struct 实例,再请求后端接口
- 把 yapi 和 proto 文件利用好
- 定义 API 前先明确调用哪个 srv 接口,理清请求参数和响应值是什么
- 商品库存涉及到跨节点分布式事务,重难点,后续补充(TODO=>Done)
- 在分布式应用中,POST 比 GET 复杂得多
- 获取商品详情 Detail
- 使用异步请求所有数据,比如这里的库存,让前端再发一个请求
- 大型网站都这么做,能很好地提升性能
- 删除商品 Delete
- 更新商品信息 Update
- 更新商品状态 UpdateStatus
- 是否为 hot、new 之类的
- 获取商品库存
- 后面我们会做库存微服务,这里先查表
商品分类
- 这部分代码和上面类似,就不赘述了
- 新建分类和更新分类信息需要定义表单
- 在 python 中转换表单数据比较容易,就是一个 json 的 load 和 dump,轻松实现 json 字符串和字典对象之间的切换
- go 里面需要先定义 struct 并带上 tag 才能转成 go 实例;go 的实例也需要通过 map 转成 json 字符串
- 记得定义路由并初始化,我们的接口符合 RESTful 风格
CategoryRouter.GET("", category.List) // 商品类别列表页 CategoryRouter.DELETE("/:id", category.Delete) // 删除分类 CategoryRouter.GET("/:id", category.Detail) // 获取分类详情 CategoryRouter.POST("", category.New) //新建分类 CategoryRouter.PUT("/:id", category.Update) //修改分类信息
- 接口测试返回的数据可以在 yapi 设置返回值时直接导入,它会据此设置返回的字段并推断类型,可能需要微调;预览中看到的是它 mock 出的数据
- 删除分类有可以改进的地方(TODO)
- 其子分类也应该逻辑删除
轮播图
- 这里的接口和前面类似,不再赘述
- 再介绍个使用 yapi 的技巧,当我们测试用例太多的时候可以创建测试集合,配置好规则(断言),一键搞定;注意选择测试环境
- 当然,自动化测试是个很大的话题,有很多工具可以选择,也有很多种架构可以选择
- 感兴趣的话可以看这个系列的文章了解
品牌和品牌与分类
- 和前面的 API 类似,这里放个获取某分类下所有品牌的接口定义
// 根据某个分类找到所有品牌 func GetCategoryBrandList(ctx *gin.Context) {// 1. 获取请求参数id := ctx.Param("id")i, err := strconv.ParseInt(id, 10, 32)if err != nil {ctx.Status(http.StatusNotFound)return}// 2. 组装请求参数,请求后端接口rsp, err := global.GoodsSrvClient.GetCategoryBrandList(context.Background(), &proto.CategoryInfoRequest{Id: int32(i),})if err != nil {api.HandleGrpcErrorToHttp(err, ctx)return}// 3. 解析响应数据,返回给前端// 组成 json 格式result := make([]interface{}, 0) // 切片for _, value := range rsp.Data {reMap := make(map[string]interface{})reMap["id"] = value.IdreMap["name"] = value.NamereMap["logo"] = value.Logoresult = append(result, reMap)}ctx.JSON(http.StatusOK, result) }
- new 和 make 的区别
oss
- 到这里基本实现了商品服务的 API 层,说明一下,目前还没有和前端页面结合起来,后面接口开发完毕会直接来一套前端源码展示
- 正常的项目开发应该是前端先设计页面给出需求文档
- 因为关系到最底层的数据库设计,牵一发而动全部 API,最好能在一开始处理得当,避免后端加班
- 我们这里旨在学习架构设计和服务开发,所以顺序有些倒置,问题不大
- 但还是有两个坑
- 商品库存如何在分布式集群中同步
- 图片如何存储
- 这里先使用阿里云的对象存储服务(oss)解决图片上传存储的问题
- 分布式服务之间是隔离的,但图片访问或者说文件访问是公共的,这就需要我们有共用的文件服务,但自己开发成本太高,推荐第三方
- 在《Go云存储-四》部分使用过,这里记录一下基本使用流程
- 这个项目就是自己搭建文件服务器,后续会更新出来
- 简而言之就是需要申请阿里云的 oss 服务并创建 Bucket
- 需要了解这些概念,记住那个 Endpoint 并创建自己的 AccessKey
- OK,作为开发人员,主要还是了解它提供的 API 了,如果你不想直接用这些接口,它还提供了针对不同语言的 SDK
- 我们用 go 语言的 SDK
- 快速入门,建议创建 RAM 子用户获取你的 KEY;子用户也能登陆页面,子用户的 key 也能用来编程访问
package mainimport ("fmt""github.com/aliyun/aliyun-oss-go-sdk/oss""os" )func handleError(err error) {fmt.Println("Error:", err)os.Exit(-1) } func main() {// yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。endpoint := "https://oss-cn-beijing.aliyuncs.com"// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。accessKeyId := "LTAI5tAnJCMgDdYKNPKK5AUP"accessKeySecret := "xxx"// yourBucketName填写存储空间名称。bucketName := "bucket-fileupload-test"// yourObjectName填写Object完整路径,完整路径不包含Bucket名称。objectName := "files/test.png"// yourLocalFileName填写本地文件的完整路径。localFileName := "D:\\GoLand\\workspaces\\shop-api\\temp\\oss\\yun.png"// 创建OSSClient实例。client, err := oss.New(endpoint, accessKeyId, accessKeySecret)if err != nil {handleError(err)}// 获取存储空间。bucket, err := client.Bucket(bucketName)if err != nil {handleError(err)}// 上传文件。err = bucket.PutObjectFromFile(objectName, localFileName)if err != nil {handleError(err)} }
- 那我们就可以这么用了吗?
- 什么问题呢?如果用户上传文件较大,gin 这里会成为瓶颈
- 怎么解决呢?阿里云提供了一种方案:客户端直传
- 但这样做又会有安全问题,浏览器拿着 secret 是件很危险的事,解决方案就是给用户一个签名
- 使用流程分两种,可以在我标出 4 的地方直接返回给客户端上传状态;如果想指定返回的信息,可以让 oss 回调我们服务器准备的函数,准备好返回给客户端的信息再给 oss,再执行第 6 步
- 快速入门,建议创建 RAM 子用户获取你的 KEY;子用户也能登陆页面,子用户的 key 也能用来编程访问
- 接下来看看具体怎么做
前端直传
- 官方给了详细步骤
- 下载源码,修改相关信息,启动 server
go run appserver.go 127.0.0.1 8888
- 访问 http://127.0.0.1:8888/ 会看到返回给前端的信息
{ accessid: "LTAI5tAnJCMgDdYKNPKK5AUP", host: "http://bucket-fileupload-test.oss-cn-hangzhou.aliyuncs.com", expire: 1677897001, signature: "P/C8MKC9vxoJcAOzzKDJVWv8Mf4=", policy: "eyJleHBpcmF0aW9uIjoiMjAyMy0wMy0wNFQwMjozMDowMVoiLCJjb25kaXRpb25zIjpbWyJzdGFydHMtd2l0aCIsIiRrZ", dir: "webfiles/", callback: "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly8xMjcuMC4wLjE6ODg4OCIsImNhbGxiYWNrQm9keSI6ImZpbGVuYW1lPSR7b2JqZWN0fVx1MDAyNnNpemU9JHtzaXplfVx1MDAyNm1pbWVUeXBlPSR7bWltZVR5cGV9XHUwMDI2aGVpZ2h0PSR7aW1hZ2VJbmZvLmhlaWdodH1cdTAwMjZ3aWR0aD0ke2ltYWdlSW5mby53aWR0aH0iLCJjYWxsYmFja0JvZHlUeXBlIjoiYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkIn0=" }
- 还没集成到我们的应用服务器,就先用本地回环测试
- 修改前端源码
- 修改
serverUrl
- 注释掉 callback,意味着 oss 直接传给服务器 status
new_multipart_params = {'key' : g_object_name,'policy': policyBase64,'OSSAccessKeyId': accessid, 'success_action_status' : '200', //让服务端返回200,不然,默认会返回204//'callback' : callbackbody,'signature': signature, };
- OK,现在可以直传文件了
- 修改
- 上述是不配置回调的情况,但如果我们想自定义返回给用户的信息
responseSuccess/responseFailed
,就需要再经过我们的服务器 - 但这里个问题,oss 服务器回调我们服务器的函数,也就是请求
callbackUrl
需要公网 IP,我们的服务器都在局域网上- 可以买个公网服务器,比如 ECS,通过它请求我们的内网 server
- 或者使用内网穿透服务,原理和自己买公网服务器是一样的
- 就不赘述了,咱们暂且不回调
- 将 oss 直传集成到 gin 微服务中
- 官方的代码是基于 http 写的,这里改写成 oss-web 微服务(gin)
- 结构和前面那些微服务一样,主要代码在 router 和 handler,官方提供的代码封装在 utils
- 记得修改 nacos 的本地和中心配置文件,确保服务正常注册
- 目前,还是现在 static/js/upload.js 设置不回调
- Q&A
- oss-web 不需要考虑分布式存储吗?(TODO)
- 共用阿里的服务器,但是分布在不同机器的多个 oss-web 微服务可能会上传同名文件
库存服务
- 接下来填另一个坑,商品库存服务
- 这个服务的作用和所处的位置如图,只在 srv 层
- 表设计
- 将库存单独做成微服务是很有必要的,不仅要对接订单服务和支付服务,商品库存和仓库之间的关系也比较复杂
- 大型的商家可能在不同地方有很多仓库,用户 A 下订单后,哪个仓库有货,安排哪里给用户发货等等都需要考虑;我们这里为了体现核心逻辑,先不扩展(TODO),只完成上图所示的功能;这部分的核心任务是:分布式事务
type Inventory struct{BaseModelGoods int32 `gorm:"type:int;index"` // 这个其实是商品id,这里直接用 goodsStocks int32 `gorm:"type:int"`Version int32 `gorm:"type:int"` //分布式锁的乐观锁 }
- 新建数据库,生成表
- 设计接口
- 就让 srv 层之间相互调用,当然,也有别的方式
- 首先肯定是设置库存
SetInv
,给商品服务用 - 查看库存
InvDetail
- 预扣减库存
Sell
,给订单服务用,也可以批量,因为用户一般会直接从购物车下单多件 - 归还库存
Reback
,要针对订单 ID 新设计表(归还这个订单下的所有商品),方便支持事务 - 关注点要聚焦在核心任务上,其他的扩展可以在复盘时逐一实现
- 实现接口
- 先将服务注册的逻辑拿过来
- 这里先实现基础逻辑,再考虑事务
- proto 文件提供了
UnimplementedInventoryServer
让我们在编写微服务时具有向前兼容的能力- 向前兼容,可以简单理解成你不实现这个接口也能启动服务
- 重点在实现预扣库存 Sell,使用 gorm 的手动事务
- 后续还会用分布式锁解决数据不一致问题(由于并发导致超卖)
- 还是用数据库本身的加锁功能;注:这和事务不是一回事,锁是保证事务操作无误的前提
- 确认扣减库存呢?支付服务中加个回调
- 库存归还,一般是订单超时归还(15分钟内没付钱),也可能是订单创建失败归还(预扣了)
- 测试
- 注:First finds the first record ordered by primary key,也就是说 First 查询是将查询条件和主键匹配的,但我们的 goodsId 不是主键,所以会造成重复添加的问题,需要用 Where 设置条件
- 为所有商品设置库存,为后续开发做准备
func main() {Init()var i int32// 商品表 id 从 421 到 840 for i = 421; i<=840; i++ {TestSetInv(i, 100)} }
数据不一致
- 前面预扣库存还有个数据不一致的问题
- 可以用代码模拟一下
func main() {Init()var wg sync.WaitGroupwg.Add(20)for i := 0; i < 20; i++ {go TestSell(&wg)}wg.Wait()conn.Close() }
- 为了给事务一个正确的起点,需要在查询库存前加锁,提交后释放锁
- 可以使用 go 自带的
sync.Mutex
里面的Lock
和unLock
方法 - 但这会带来严重的性能问题,因为这个锁并不针对数据库,而是这段代码,会锁住其他所有的这个函数的协程,但我们操作的不一定是同一个 goodsId,白白浪费性能;相当于表锁
- 同时,这个锁看起来没有问题,其实不然(什么问题?)
- 可以使用 go 自带的
- 那用什么锁呢?MySQL 提供了一种锁机制
for update
(InnoDB引擎)- 当查询的字段建立了索引,那就只会锁住满足条件的数据,也叫行锁
- 若查无此记录,无锁
- 当然,如果没有建立索引,怎么都是表锁
- gorm 也提供了接口
// 直接用 SQL 语句是这样的 // select * from table where xxx for update // mysql 默认自动提交,提交了会释放锁,所以事务在这里起到了两个作用,还有一个就是相当于关闭autocommit // 换句话说就是:在begin与commit之间才生效 tx := global.DB.Begin() tx.Clauses(clause.Locking{Strength: "UPDATE"}) // 后面再跟 Where 即可
- 也叫悲观锁,既然是锁,就会串行,肯定有性能问题
- 当查询的字段建立了索引,那就只会锁住满足条件的数据,也叫行锁
- 基于 MySQL 的乐观锁
- 乐观锁本质上不是一种锁,而是一种分布式情况下数据一致性的解决方案
- 需要我们加一个字段:
version
,更新条件中带上 version,如果和一开始查询到的 version 不一致,证明之前查到的数据已经别人被更新了,需要重新查询;如果更新成功则需把 version+1
- 在代码中使用乐观锁
for {// 这里有个坑,就是一定要用 Select,不然有零值问题,gorm 会忽略掉不更新,就是说不让你设置为0,库存虽然只剩一件,但是version没问题,全部都能扣减成功!库存始终是1if result := tx.Model(&model.Inventory{}).Select("Stocks", "Version").Where("goods = ? and version= ?", goodInfo.GoodsId, inv.Version).Updates(model.Inventory{Stocks: inv.Stocks, Version: inv.Version+1}); result.RowsAffected == 0 {zap.S().Info("库存扣减失败")}else{break} }
- OK,分布式锁的第一种方案就是:基于 MySQL 的乐观锁
redis 分布式锁
- 基于 Redis 实现分布式锁是常见方案,也是这里的第二种方案(推荐)
- 这里有实现此方案的开源项目,使用方法自己看
- 集成到代码
client := goredislib.NewClient(&goredislib.Options{Addr: fmt.Sprintf("%s:%d", global.ServerConfig.RedisInfo.Host, global.ServerConfig.RedisInfo.Port), }) pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...) rs := redsync.New(pool) // ... mutex := rs.NewMutex(fmt.Sprintf("goods_%d", goodInfo.GoodsId)) // 剩下的就和go自带的mutex类似
- 和 go 不同的是,Save 之后就可以释放锁
mutex.Unlock()
- 具体原理
- setnx:原子操作,设置key/value,这个 key 可以是我们的商品 id,value 是唯一的,保证不被其他用户删除
- 当时设置的 value 值是多少只有当时的 g 才能知道
- 删除时会取出 redis 中的值和当前自己保存下来的值对比一下
- 加入超时机制,避免死锁;也需要延时操作,避免活没干完锁丢了
- 产生死锁是因为 redis 服务可能挂掉了,为了获取锁而一直等待
- 使用 redlock 解决 redis 集群 master 可能宕机导致的数据不同步问题
- 这个问题源于下图这种模式:都从 master 获取锁
- 所有 redis 机器都视作相同服务器
- client 尝试按照顺序使用相同的 KV 获取所有 redis 的锁
- 机器个数一般为奇数个,获取锁较多的 client 最终获得锁
- 这里还有个时钟漂移问题,需要设置因子考虑漂移时间
- 这个问题源于下图这种模式:都从 master 获取锁
- 建议根据上述逻辑看一遍源码,并不复杂
- setnx:原子操作,设置key/value,这个 key 可以是我们的商品 id,value 是唯一的,保证不被其他用户删除
小结
- 商品微服务到这里基本梳理结束,还差最后一步,将 elasticsearch 集成进去,会在订单微服务完成后一并接入
- 关于多机部署问题会在所有微服务完成后统一梳理
- 各微服务之间代码结构大同小异,web 层在 api 定义主要逻辑,srv 层在 handler 定义主要逻辑
相关文章:

Go项目(商品微服务-2)
文章目录简介handler商品分类轮播图品牌和品牌与分类oss前端直传库存服务数据不一致redis 分布式锁小结简介 开发商品微服务 API 层类似的,将 user-web 目拷贝一份,全局替换掉 user-web修改 config 去掉不用的配置更新本地和远程 nacos 配置文件 把 pro…...

无头盔PICO-unity开发日记1(抓取、传送)
目录 可传送的地面 锚点传送 修改射线颜色(可交互/不可交互) 球、抓手组件 ||刚体(重力)组件 可传送的地面 1.地面添加组件 2.XR交互管理器添加传送提供者 3.地面设置传送提供者 4.XR交互管理器添加locomotion system 5.拖拽 完…...
Material3设计指南笔记
Material3设计指南笔记Table of Contents1. 颜色color1.1. 颜色分类1.2. 强调色accent color1.3. 中性色neutral color1.4. 辅助色additional color1.5. 调色盘tonal palettes1.6. 颜色规范2. z轴高度 elevation3. 图标 icon4. 动画 motion5. 形状 shape6. 字体1. 颜色color1.1…...

JavaWeb--会话技术
会话技术1 会话跟踪技术的概述2 Cookie2.1 Cookie的基本使用2.2 Cookie的原理分析2.3 Cookie的使用细节2.3.1 Cookie的存活时间2.3.2 Cookie存储中文3 Session3.1 Session的基本使用3.2 Session的原理分析3.3 Session的使用细节3.3.1 Session钝化与活化3.3.2 Session销毁目标 理…...

Git图解-为啥是Git?怎么装?
目录 零、学习目标 一、版本控制 1.1 团队开发问题 1.2 版本控制思想 1.2.1 版本工具 二、Git简介 2.1 简介 2.2 Git环境的搭建 三、转视频版 零、学习目标 掌握git的工作流程 熟悉git安装使用 掌握git的基本使用 掌握分支管理 掌握IDEA操作git 掌握使用git远程仓…...
HTML 框架
HTML 框架 <iframe>标签规定一个内联框架。 一个内联框架被用来在当前 HTML 文档中嵌入另一个文档。 通过使用框架,你可以在同一个浏览器窗口中显示不止一个页面。 iframe 语法: <iframe src"URL"></iframe> 该URL指向不同的…...
Rust特征(Trait)
特征(Trait) 特征(trait)是rust中的概念,类似于其他语言中的接口(interface)。在之前的代码中,我们也多次见过特征的使用,例如 #[derive(Debug)],它在我们定义的类型(struct)上自动…...

详解七大排序算法
对于排序算法,是我们在数据结构阶段,必须要牢牢掌握的一门知识体系,但是,对于排序算法,里面涉及到的思路,代码……各种时间复杂度等,都需要我们,记在脑袋瓜里面!…...

Vue+ECharts实现可视化大屏
由于项目需要一个数据大屏页面,所以今天学习了vue结合echarts的图标绘制 首先需要安装ECharts npm install echarts --save因为只是在数据大屏页面绘制图表,所以我们无需把它设置为全局变量。 可以直接在该页面引入echarts,就可以在数据大…...

百度Apollo规划算法——轨迹拼接
百度Apollo规划算法——轨迹拼接引言轨迹拼接1、什么是轨迹拼接?2、为什么要进行轨迹拼接?3、结合Apollo代码为例理解轨迹拼接的细节。参考引言 在apollo的规划算法中,在每一帧规划开始时会调用一个轨迹拼接函数,返回一段拼接轨迹…...

6. unity之脚本
1. 说明 当整个游戏运行起来之后,我们无法再借助鼠标来控制物体,此时可以使用脚本来更改物体的各种姿态,驱动游戏的整体运动逻辑。 2. 脚本添加 首先在Assets目录中,新创建一个Scripts文件夹,在该文件内右键鼠标选择…...
flink-note笔记:flink-state模块中broadcast state(广播状态)解析
github开源项目flink-note的笔记。本博客的实现代码都写在项目的flink-state/src/main/java/state/operator/BroadcastStateDemo.java文件中。 项目github地址: github 1. 广播状态是什么 网上关于flink广播变量、广播状态的讲解很杂。我翻了flink官网发现,实际上在1.15里面…...
vue——预览PDF
下载插件 npm install --save vue-pdf创建组件 <template><div class"ins-submit-docs-content ins-submit-docs-pdf"><div v-if"loading" style"position: absolute; top: 40%; width: 100%;text-align: center;"><el-l…...

数据库复习
什么是数据库系统 数据库系统是指在计算机系统中引入数据库后构成的系统,一般由数据库、数据库管理系统(及其开发工具)、应用系统、数据库管理员和用户构成 数据库系统的特点是什么? 数据结构化数据的共享性高,冗余度低且易扩充数据独立性高数…...

vscode插件推荐
文章目录前言一、vscode插件推荐?1、 Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code2、Auto Close Tag3、Auto Import3、Error Lens4、vscode-icons5、ES7 React/Redux/React-Native snippets6、GitLens — Git supercharged7、JavaScript…...
THUPC2023初赛总结
今天参加了THUPC2023初赛,感觉还行。 比赛本来是11:00-16:00,但出了点问题,比赛延迟了十分钟。 刚开始,我从第一题往后看,寻找简单的题。 过了一会儿,一看排行榜,怎么最后一题全是绿的&#…...

unity知识点小结02
虚拟轴 虚拟轴就是一个数值在-11内的轴,这个数轴上重要的数值就是-1,0和1。当使用按键模拟一个完整的虚拟轴时需要用到两个按键,即将按键1设置为负轴按键,按键2设置为正轴按键。在没有按下任何按键的时候,虚拟轴的数值为0…...

总线(四)Modbus总线 协议
文章目录Modbus技术背景Modbus OSI分布Moudbus分类通讯过程Moudbus协议通信过程以及报文解析RTU 与 ASCII 收发数据区别Modbus技术背景 Modbus是一种串行通信协议。 1971年,Modicon公司首次退出Modbus协议,ModbusRTU和Modbus ASCII诞生于此。 后来施耐德…...
Cadence Allegro 导出Component Report详解
⏪《上一篇》 🏡《总目录》 ⏩《下一篇》 目录 1,概述2,Component Report作用3,Component Report示例4,Component Report导出方法4.1,方法14,2,方法2B站关注“硬小二”浏览更多演示视频 1,...
程序猿成长之路之密码学篇-DES算法详解
DES的算法实现原理详情请见 https://blog.csdn.net/qq_31236027/article/details/128209185 DES算法密钥获取详情请见 https://blog.csdn.net/qq_31236027/article/details/129224730 编码工具类获取详见 https://blog.csdn.net/qq_31236027/article/details/128579451 DES算法…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...

Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...