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算法…...

maven生命周期、阶段与默认绑定插件梳理
maven生命周期、阶段与默认绑定插件梳理 CSDN博客 码云源码 1.maven生命周期、阶段与默认绑定插件 序号生命周期lifecycle阶段phase默认绑定插件(链接官网)默认绑定插件(链接maven库)说明1cleancleanmaven-clean-pluginmaven-clean-plugin清理2.1buildvalidate——验证2.2b…...

【数学基础】
文章目录『 第1讲 高等数学预备知识 』1.1 函数的概念与特性函数的四种特性【 重要结论 】1.2 函数的图像直角坐标系下的图像极坐标系下的图像参数方程1.3 常用基础知识【 情报#1 】『 第2讲 数列极限 』2.1 引言2.2 求数列极限【 情报#2 】『 第1讲 高等数学预备知识 』 1.1 …...

网上电子商城的设计与实现
技术:Java、JSP等摘要:21 世纪以来,人类经济高速发展,人们的生活发生了日新月异的变化,特别是计算机的应用及普及到经济和社会生活的各个领域。在消费领域,网上购物已经成为大众所接受的一种新型的消费方式…...

2023thupc总结
A 大富翁 很有意思的题 ∑x∈A∑y∈B[x支配y]−∑x∈A∑y∈B[y支配x]−∑x∈Awx\sum_{x\in A}\sum_{y\in B}[x支配y]-\sum_{x\in A}\sum_{y\in B}[y支配x]-\sum_{x\in A}w_x∑x∈A∑y∈B[x支配y]−∑x∈A∑y∈B[y支配x]−∑x∈Awx ∑x∈A∑y[x支配y]−∑x∈A∑y[y支…...

【数据库】MySQL数据库基础
目录 1.数据库: 2.数据库基本操作 2.1 MySQL的运行原理 2.2显示数据库: 2.3创建数据库 2.4使用数据库 2.5删除数据库 3.常见的数据类型 3.1数值类型: 3.2字符型类型 3.3日期类型 4.表的操作 4.1创建表 4.2查看表 4.3删除表 5.汇总…...

grid了解
结构 <div class"grid"><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div><div>8</div><div>9</div>&l…...

2023年全国最新工会考试精选真题及答案13
百分百题库提供工会考试试题、工会考试预测题、工会考试真题、工会证考试题库等,提供在线做题刷题,在线模拟考试,助你考试轻松过关。 81.女职工委员会在()下开展工作。 A.企业工会委员会领导 B.企业工会委员会指导 …...

初识HTML技术
文章目录一、为什么学习前端?二、第一个HTML文件VSCode三. HTML元素四. HTML页面一、为什么学习前端? 我们作为一个后端程序员,为什么还要学习前端,因为我们的终极目的是实现web开发,搭建网站,网站 前端 后端 比如我们随便…...

我们为什么要用消息队列?
消息队列是系统设计中存在时间最长的中间件之一,从系统有通信需求开始,就产生了消息队列。 消息队列的使用场景 在日常系统设计与实现的过程中,下面3种场景会涉及到消息队列: 异步处理流量控制服务解耦 异步处理 典型的应用场…...

Linux进程控制
进程控制fork函数进程终止退出码常见的退出方式进程等待什么是进程等待,为什么要进程等待阻塞与非阻塞进程替换替换原理替换函数执行系统命令执行自己写的程序模拟实现简易的shellfork函数 fork函数是创建一个子进程,之前用过。 #include <unistd.h…...