国家建设材料检测网站厦门专业网站
- 作者: 五速梦信息网
- 时间: 2026年04月20日 11:04
当前位置: 首页 > news >正文
国家建设材料检测网站,厦门专业网站,wordpress微信缩略图,wordpress官网模板文章目录简介handler商品分类轮播图品牌和品牌与分类oss前端直传库存服务数据不一致redis 分布式锁小结简介
开发商品微服务 API 层类似的#xff0c;将 user-web 目拷贝一份#xff0c;全局替换掉 user-web修改 config 去掉不用的配置更新本地和远程 nacos 配置文件 把 pro…
文章目录简介handler商品分类轮播图品牌和品牌与分类oss前端直传库存服务数据不一致redis 分布式锁小结简介
开发商品微服务 API 层类似的将 user-web 目拷贝一份全局替换掉 user-web修改 config 去掉不用的配置更新本地和远程 nacos 配置文件 把 proto 文件拷过来再生成一下全部拷过来也行 Qgrpc 和 HTTP 调用的优缺点是什么Q如何将我们的服务改为 HTTP 调用 更新 initialize/router ApiGroup : Router.Group(/g/v1)router 目录是单独设置的方便管理也需要更新通过 package 区分各接口也就是需要在 api 下新建目录goods/brand/category/banners 修改 global 文件修改 initialize/srv_conn 文件服务发现并拨号连接 goods_srvmiddlewares 部分是各微服务的共用代码可以抽出来共用 但要有专人维护并做好版本管理避免因为改动影响所有微服务或者就放在各微服务代码虽多但是隔离性好 启动项目测试能否注册并发现 goods_srv 在 router 中临时配置一个接口使用 yapi 配置商品服务的测试环境请求我们的接口配置环境别忘了带 token这个是各服务都要验证的可以先请求密码登录接口获取一个过期时间设置长一些
handler
开始实现各接口新建 test 目录为每个接口编写 UT 测试主要是看响应先不必 assert商品列表 List 获取商品列表需要一些请求参数前端页面和后端API严格遵守文档在yapi定义 API 这里获取页面请求参数ctx.DefaultQuery()按照 proto 的定义拼装好向 srv 的请求参数注意检查用户输入避免后端处理时异常除了在文档中定义请求参数还要定义返回值 注yapi 是在前端页面和 API 层之间的一个东西定义的请求参数和返回值都有两用 注册 注册中心可能换成别的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 接口理清请求参数和响应值是什么 商品库存涉及到跨节点分布式事务重难点后续补充TODODone 在分布式应用中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 (fmtgithub.com/aliyun/aliyun-oss-go-sdk/ossos
)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 : LTAI5tAnJCMgDdYKNPKK5AUPaccessKeySecret : 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 步 接下来看看具体怎么做
前端直传
官方给了详细步骤下载源码修改相关信息启动 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 直接传给服务器 statusnew_multipart_params {key : g_object_name,policy: policyBase64,OSSAccessKeyId: accessid, success_actionstatus : 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 设置不回调 QA oss-web 不需要考虑分布式存储吗TODO共用阿里的服务器但是分布在不同机器的多个 oss-web 微服务可能会上传同名文件
库存服务
接下来填另一个坑商品库存服务这个服务的作用和所处的位置如图只在 srv 层 表设计 将库存单独做成微服务是很有必要的不仅要对接订单服务和支付服务商品库存和仓库之间的关系也比较复杂大型的商家可能在不同地方有很多仓库用户 A 下订单后哪个仓库有货安排哪里给用户发货等等都需要考虑我们这里为了体现核心逻辑先不扩展TODO只完成上图所示的功能这部分的核心任务是分布式事务type Inventory struct{BaseModelGoods int32 gorm:type:int;index // 这个其实是商品id这里直接用 goodsStocks int32 gorm:type:intVersion 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; i840; 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白白浪费性能相当于表锁同时这个锁看起来没有问题其实不然什么问题 那用什么锁呢MySQL 提供了一种锁机制 for updateInnoDB引擎 当查询的字段建立了索引那就只会锁住满足条件的数据也叫行锁 若查无此记录无锁 当然如果没有建立索引怎么都是表锁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 不一致证明之前查到的数据已经别人被更新了需要重新查询如果更新成功则需把 version1 在代码中使用乐观锁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.Version1}); 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 可以是我们的商品 idvalue 是唯一的保证不被其他用户删除 当时设置的 value 值是多少只有当时的 g 才能知道删除时会取出 redis 中的值和当前自己保存下来的值对比一下 加入超时机制避免死锁也需要延时操作避免活没干完锁丢了 产生死锁是因为 redis 服务可能挂掉了为了获取锁而一直等待 使用 redlock 解决 redis 集群 master 可能宕机导致的数据不同步问题 这个问题源于下图这种模式都从 master 获取锁 所有 redis 机器都视作相同服务器client 尝试按照顺序使用相同的 KV 获取所有 redis 的锁机器个数一般为奇数个获取锁较多的 client 最终获得锁这里还有个时钟漂移问题需要设置因子考虑漂移时间 建议根据上述逻辑看一遍源码并不复杂
小结
商品微服务到这里基本梳理结束还差最后一步将 elasticsearch 集成进去会在订单微服务完成后一并接入关于多机部署问题会在所有微服务完成后统一梳理各微服务之间代码结构大同小异web 层在 api 定义主要逻辑srv 层在 handler 定义主要逻辑
- 上一篇: 国家建设部官方网站投诉wordpress弹窗评论
- 下一篇: 国家建设材料检测网站郑州房产信息网官网
相关文章
-
国家建设部官方网站投诉wordpress弹窗评论
国家建设部官方网站投诉wordpress弹窗评论
- 技术栈
- 2026年04月20日
-
国家建设部防化工程师网站官网辽宁建设工程信息网业绩录入
国家建设部防化工程师网站官网辽宁建设工程信息网业绩录入
- 技术栈
- 2026年04月20日
-
国家和住房城乡建设部网站首页易语言如何做浏网站
国家和住房城乡建设部网站首页易语言如何做浏网站
- 技术栈
- 2026年04月20日
-
国家建设材料检测网站郑州房产信息网官网
国家建设材料检测网站郑州房产信息网官网
- 技术栈
- 2026年04月20日
-
国家建设执业注册中心网站河北抖音seo系统
国家建设执业注册中心网站河北抖音seo系统
- 技术栈
- 2026年04月20日
-
国家建筑信息管理平台seo定义
国家建筑信息管理平台seo定义
- 技术栈
- 2026年04月20日
