当前位置: 首页 > article >正文

微服务商城-商品微服务

数据表

CREATE TABLE `product` (`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '商品id',`cateid` smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT '类别Id',`name` varchar(100) NOT NULL DEFAULT '' COMMENT '商品名称',`subtitle` varchar(200) NOT NULL DEFAULT '' COMMENT '商品副标题',`images` varchar(1024) NOT NULL DEFAULT '' COMMENT '图片地址,逗号分隔',`detail` varchar(1024) NOT NULL DEFAULT '' COMMENT '商品详情',`price` decimal(20,2) NOT NULL DEFAULT 0 COMMENT '价格,单位-元保留两位小数',`stock` int(11) NOT NULL DEFAULT 0 COMMENT '库存数量',`status` int(6) NOT NULL DEFAULT 1 COMMENT '商品状态.1-在售 2-下架 3-删除',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),KEY `ix_cateid` (`cateid`),KEY `ix_update_time` (`update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';CREATE TABLE `category` (`id` smallint(6) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '分类id',`parentid` smallint(6) NOT NULL DEFAULT 0 COMMENT '父类别id当id=0时说明是根节点,一级类别',`name` varchar(50) NOT NULL DEFAULT '' COMMENT '类别名称',`status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '类别状态1-正常,2-已废弃',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品类别表';CREATE TABLE `product_operation` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`product_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT '商品id',`status` int NOT NULL DEFAULT '1' COMMENT '运营商品状态 0-下线 1-上线',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),KEY `ix_update_time` (`update_time`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COMMENT='商品运营表';

商品微服务方法

获取商品缓存

func (l *ProductLogic) Product(in *product.ProductItemRequest) (*product.ProductItem, error) {v, err, _ := l.svcCtx.SingleGroup.Do(fmt.Sprintf("product:%d", in.ProductId), func() (interface{}, error) {return l.svcCtx.ProductModel.FindOne(l.ctx, in.ProductId)})if err != nil {return nil, err}p := v.(*model.Product)return &product.ProductItem{ProductId: p.Id,Name:      p.Name,Stock:     p.Stock,}, nil
}

查询缓存中的信息,如果缓存中不存在product:id, 从数据库中查询

获取多个商品

在这里插入图片描述

func (l *ProductsLogic) Products(in *product.ProductRequest) (*product.ProductResponse, error) {products := make(map[int64]*product.ProductItem)pdis := strings.Split(in.ProductIds, ",")ps, err := mr.MapReduce(func(source chan<- interface{}) {for _, pid := range pdis {source <- pid}}, func(item interface{}, writer mr.Writer, cancel func(error)) {pidStr := item.(string)pid, err := strconv.ParseInt(pidStr, 10, 64)if err != nil {return}p, err := l.svcCtx.ProductModel.FindOne(l.ctx, pid)if err != nil {return}writer.Write(p)}, func(pipe <-chan interface{}, writer mr.Writer, cancel func(error)) {var r []*model.Productfor p := range pipe {r = append(r, p.(*model.Product))}writer.Write(r)})if err != nil {return nil, err}for _, p := range ps.([]*model.Product) {products[p.Id] = &product.ProductItem{ProductId: p.Id,Name:      p.Name,}}return &product.ProductResponse{Products: products}, nil
}

map函数将所有的pid写入source
reduce函数取出pid 查询数据库得到商品信息
final函数将查询结果聚合到一个切片中

获取商品列表

根据类别id获取指定类别的商品列表
首先判断是否存在缓存,如果不存在则查询数据库,且写入缓存

获取上架商品的信息

获取商品操作表中的为上架状态的商品id,获取相应的商品列表

修改商品库存

直接扣减mysql库存数量

检查并修改商品库存

使用lua脚本先判断再扣减

检查商品库存是否足够扣减

查询mysql判断

回滚库存

直接操作数据库

扣减库存 分布式服务

参考:https://juejin.cn/post/7051205679217901599
果是在单体架构的业务当中,是不需要用到分布式事务的.单体架构中,涉及到需要保证多个事务同时成功的场景,只需要创建一个全局的事务对象 如:tx := db.Begin(),然后统一用这一个tx去管理接下来的业务逻辑即可。
绝大多数的订单系统的事务都会跨服务,因此都有更新数据一致性的需求,都可以通过 DTM 大幅简化架构,形成一个优雅的解决方案。
处理逻辑存在数据一致性问题,有可能订单创建成功了,但是在更新产品库存的时候可能会发生失败,这时候就会存在订单创建成功,产品库存没有减少的情况。
因为这里的产品库存更新是跨服务操作的,也没有办法使用本地事务来处理,所以我们需要使用分布式事务来处理它。这里我们需要借助 DTM 的 SAGA 协议来实现订单创建和产品库存更新的跨服务分布式事务操作。

  1. 将dtm注册到etcd中
# 微服务
MicroService:Driver: 'dtm-driver-gozero'           # 要处理注册/发现的驱动程序的名称Target: 'etcd://etcd:2379/dtmservice' # 注册 dtm 服务的 etcd 地址EndPoint: 'dtm:36790'
  1. 添加 dtm_barrier 数据表
  2. 我们需要为 product rpc 服务添加 DecrStock、DecrStockRevert 两个接口方法,分别用于产品库存更新 和 产品库存更新的补偿。
  3. 实现 DecrStock 接口方法
    在这里只有库存不足时,我们不需要再重试,直接回滚。
  4. 在 DecrStockRevert 接口方法中,产品库存是减去指定的数量,在这里我们把它给加回来。这样产品库存就回到在 DecrStock 接口方法减去之前的数量。
package logicimport ("context""mall/service/order/api/internal/svc""mall/service/order/api/internal/types""mall/service/order/rpc/types/order""mall/service/product/rpc/product""github.com/dtm-labs/dtmgrpc""github.com/zeromicro/go-zero/core/logx""google.golang.org/grpc/status"
)type CreateLogic struct {logx.Loggerctx    context.ContextsvcCtx *svc.ServiceContext
}func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateLogic {return &CreateLogic{Logger: logx.WithContext(ctx),ctx:    ctx,svcCtx: svcCtx,}
}func (l *CreateLogic) Create(req *types.CreateRequest) (resp *types.CreateResponse, err error) {// 获取 OrderRpc BuildTargetorderRpcBusiServer, err := l.svcCtx.Config.OrderRpc.BuildTarget()if err != nil {return nil, status.Error(100, "订单创建异常")}// 获取 ProductRpc BuildTargetproductRpcBusiServer, err := l.svcCtx.Config.ProductRpc.BuildTarget()if err != nil {return nil, status.Error(100, "订单创建异常")}// dtm 服务的 etcd 注册地址var dtmServer = "etcd://etcd:2379/dtmservice"// 创建一个gidgid := dtmgrpc.MustGenGid(dtmServer)// 创建一个saga协议的事务saga := dtmgrpc.NewSagaGrpc(dtmServer, gid).Add(orderRpcBusiServer+"/order.Order/Create", orderRpcBusiServer+"/order.Order/CreateRevert", &order.CreateRequest{Uid:    req.Uid,Pid:    req.Pid,Amount: req.Amount,Status: 0,}).Add(productRpcBusiServer+"/product.Product/DecrStock", productRpcBusiServer+"/product.Product/DecrStockRevert", &product.DecrStockRequest{Id:  req.Pid,Num: 1,})// 事务提交err = saga.Submit()if err != nil {return nil, status.Error(500, err.Error())}return &types.CreateResponse{}, nil
}

商品API

func (l *ProductDetailLogic) ProductDetail(req *types.ProductDetailRequest) (resp *types.ProductDetailResponse, err error) {var (p *product.ProductItemcs *reply.CommentsResponse)if err := mr.Finish(func() error {var err errorif p, err = l.svcCtx.ProductRPC.Product(l.ctx, &product.ProductItemRequest{ProductId: req.ProductID}); err != nil {return err}return nil}, func() error {var err errorif cs, err = l.svcCtx.ReplyRPC.Comments(l.ctx, &reply.CommentsRequest{TargetId: req.ProductID}); err != nil {logx.Errorf("get comments error: %v", err)}return nil}); err != nil {return nil, err}var comments []*types.Commentfor _, c := range cs.Comments {comments = append(comments, &types.Comment{ID: c.Id,Content:   c.Content,})}return &types.ProductDetailResponse{Product: &types.Product{ID:        p.ProductId,Name:      p.Name,},Comments: comments,}, nil
}

mr.Finsh 处理并发任务的结果
代码实现了两个远程调用的并发执行:一个获取产品信息,另一个获取评论。通过 mr.Finish 管理这两个操作,确保执行的顺序和错误处理。第一个操作如果失败会直接返回错误,而第二个操作即使失败也仅仅记录错误,不影响整体流程。

索引缓存

怎么在缓存中存储分类的商品呢?我们使用Sorted Set来存储,member为商品的id,即我们只在Sorted Set中存储缓存索引,查出缓存索引后,因为我们自动生成了以主键id索引为key的缓存,所以查出索引列表后我们再查询行记录缓存即可获取商品的详情,Sorted Set的score为商品的创建时间。

  1. 首先先从缓存中读取当前页的商品id索引,调用cacheProductList方法,注意,这里调用查询缓存方法忽略了error,为什么要忽略这个error呢,因为我们期望的是尽最大可能的给用户返回数据,也就是redis挂掉了的话那我们就会从数据库查询数据返回给用户,而不会因为redis挂掉而返回错误。
  2. 如果从缓存中查出的数据为0条,那么我们就从数据库中查询该分类下的数据,这里要注意从数据库查询数据的时候我们要限制查询的条数,我们默认一次查询300条,因为我们每页大小为10,300条可以让用户下翻30页,大多数情况下用户根本不会翻那么多页,所以我们不会全部加载以降低我们的缓存资源,当用户真的翻页超过30页后,我们再按需加载到缓存中
  3. 获取到当前页的数据后,我们还需要做去重,因为如果我们只以createTime作为游标的话,很可能数据会重复,所以我们还需要加上id作为去重条件,去重逻辑如下
  4. 如果没有命中缓存的话,我们需要把从数据库查出的数据写入缓存,这里需要注意的是如果数据已经到了末尾需要加上数据结束的标识符,即val为-1,score为0,这里我们异步的写会缓存,因为写缓存并不是主逻辑,不需要等待完成,写失败也没有影响呢,通过异步方式降低接口耗时
func (l *ProductListLogic) ProductList(in *product.ProductListRequest) (*product.ProductListResponse, error) {// 判断类别是否存在_, err := l.svcCtx.CategoryModel.FindOne(l.ctx, int64(in.CategoryId))if err == model.ErrNotFound {return nil, status.Error(codes.NotFound, "category not found")}// 设置游标为当前时间if in.Cursor == 0 {in.Cursor = time.Now().Unix()}// 如果页大小为0,则设置为默认的大小if in.Ps == 0 {in.Ps = defaultPageSize}var (isCache, isEnd   boollastID, lastTime int64firstPage        []*product.ProductItemproducts         []*model.Product)// 查询缓存中的数据pids, _ := l.cacheProductList(l.ctx, in.CategoryId, in.Cursor, int64(in.Ps))// 满一页if len(pids) == int(in.Ps) {isCache = true// 判断是否结束if pids[len(pids)-1] == -1 {isEnd = true}products, err := l.productsByIds(l.ctx, pids)if err != nil {return nil, err}// 商品数据for _, p := range products {firstPage = append(firstPage, &product.ProductItem{ProductId:  p.Id,Name:       p.Name,CreateTime: p.CreateTime.Unix(),})}} else {var (err   errorctime = time.Unix(in.Cursor, 0).Format("2006-01-02 15:04:05"))// 查询数据库products, err = l.svcCtx.ProductModel.CategoryProducts(l.ctx, ctime, int64(in.CategoryId), defaultLimit)if err != nil {return nil, err}var firstPageProducts []*model.Product// 分页if len(products) > int(in.Ps) {firstPageProducts = products[:int(in.Ps)]} else {firstPageProducts = productsisEnd = true}for _, p := range firstPageProducts {firstPage = append(firstPage, &product.ProductItem{ProductId:  p.Id,Name:       p.Name,CreateTime: p.CreateTime.Unix(),})}}if len(firstPage) > 0 {pageLast := firstPage[len(firstPage)-1]lastID = pageLast.ProductIdlastTime = pageLast.CreateTimeif lastTime < 0 {lastTime = 0}for k, p := range firstPage {if p.CreateTime == in.Cursor && p.ProductId == in.ProductId {firstPage = firstPage[k:]break}}}ret := &product.ProductListResponse{IsEnd:     isEnd,Timestamp: lastTime,ProductId: lastID,Products:  firstPage,}// 添加缓存if !isCache {threading.GoSafe(func() {if len(products) < defaultLimit && len(products) > 0 {endTime, _ := time.Parse("2006-01-02 15:04:05", "0000-00-00 00:00:00")products = append(products, &model.Product{Id: -1, CreateTime: endTime})}_ = l.addCacheProductList(context.Background(), products)})}return ret, nil
}
func (l *ProductListLogic) cacheProductList(ctx context.Context, cid int32, cursor, ps int64) ([]int64, error) {// 上下文、开始的时间游标、结束限、当前页码、页大小pairs, err := l.svcCtx.BizRedis.ZrevrangebyscoreWithScoresAndLimitCtx(ctx, categoryKey(cid), cursor, 0, 0, int(ps))if err != nil {return nil, err}var ids []int64for _, pair := range pairs {id, _ := strconv.ParseInt(pair.Key, 10, 64)ids = append(ids, id)}return ids, nil
}

相关文章:

微服务商城-商品微服务

数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验

系列回顾&#xff1a; 在上一篇中&#xff0c;我们成功地为应用集成了数据库&#xff0c;并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了&#xff01;但是&#xff0c;如果你仔细审视那些 API&#xff0c;会发现它们还很“粗糙”&#xff1a;有…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

【git】把本地更改提交远程新分支feature_g

创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

2025盘古石杯决赛【手机取证】

前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来&#xff0c;实在找不到&#xff0c;希望有大佬教一下我。 还有就会议时间&#xff0c;我感觉不是图片时间&#xff0c;因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

什么是EULA和DPA

文章目录 EULA&#xff08;End User License Agreement&#xff09;DPA&#xff08;Data Protection Agreement&#xff09;一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA&#xff08;End User License Agreement&#xff09; 定义&#xff1a; EULA即…...

【决胜公务员考试】求职OMG——见面课测验1

2025最新版&#xff01;&#xff01;&#xff01;6.8截至答题&#xff0c;大家注意呀&#xff01; 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:&#xff08; B &#xff09; A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

C++.OpenGL (10/64)基础光照(Basic Lighting)

基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...