使用rabbitmq 进行任务调度

news/2025/10/21 10:24:37/文章来源:https://www.cnblogs.com/chongyao/p/19154345
  • 重试策略既然使用了mq 就不要依赖代码进行重试应当使用mq的重试机制进行重试
  • 代码基于主流的mq 包github.com/rabbitmq/amqp091-go
  • 以下是对mq的client进行的封装
    package mqimport ("context""fmt""log""sync""sync/atomic""time"amqp "github.com/rabbitmq/amqp091-go"
    )type Client struct {url      stringisAliyun boolconn *amqp.Connectionch   *amqp.Channel // admin/publisher channel
    mu sync.Mutexexchanges []exchangeDeclconsumers []consumerDecl// reconnect 控制改用原子标志 + 通道用于通知reconnecting int32closed       chan struct{}wg           sync.WaitGroup
    }type exchangeDecl struct {Exchange   stringQueue      stringRoutingKey string
    }type consumerDecl struct {Queue   stringHandler func(amqp.Delivery)
    }// ------------------ 辅助函数 (用于 Ack/Nack 和 Panic 保护) ------------------// safeAck 尝试 Ack,如果失败,返回错误(指示 Channel 可能已关闭)
    func safeAck(msg amqp.Delivery) error {// 在某些情况下,msg.Ack 会在 channel 已关闭时返回一个错误if err := msg.Ack(false); err != nil {// 记录错误,但不paniclog.Printf("[RabbitMQ] safeAck 失败: %v", err)return err // 返回错误,让调用者决定是否退出
        }return nil
    }// safeNack 尝试 Nack,如果失败,返回错误(指示 Channel 可能已关闭)
    func safeNack(msg amqp.Delivery, requeue bool) error {// 在某些情况下,msg.Nack 会在 channel 已关闭时返回一个错误if err := msg.Nack(false, requeue); err != nil {log.Printf("[RabbitMQ] safeNack 失败: %v", err)return err // 返回错误,让调用者决定是否退出
        }return nil
    }// safeHandle 用于捕获 handler 的 panic 并把错误返回
    func safeHandle(handler func(amqp.Delivery), msg amqp.Delivery) (err error) {defer func() {if r := recover(); r != nil {err = fmt.Errorf("handler panic: %v", r)}}()handler(msg)return nil
    }// ------------------ 初始化 ------------------func New(url string, isAliyun bool) *Client {return &Client{url:       url,isAliyun:  isAliyun,closed:    make(chan struct{}),exchanges: make([]exchangeDecl, 0),consumers: make([]consumerDecl, 0),}
    }func (c *Client) Connect() error {c.mu.Lock()defer c.mu.Unlock()var conn *amqp.Connectionvar err error// **健壮性增强 1:配置心跳 (Heartbeat)**// 设置心跳为 20 秒,以确保在网络中间件的空闲超时(通常 > 60s)之前发送活动信号。//config := amqp.Config{//    Heartbeat: 20 * time.Second,//}// 尝试连接for i := 0; i < 5; i++ {// 使用 DialConfigconn, err = amqp.Dial(c.url)time.Sleep(2 * time.Second)if err == nil {break}log.Printf("[RabbitMQ] connect failed (%d/5): %v\n", i+1, err)time.Sleep(2 * time.Second)}if err != nil {return fmt.Errorf("RabbitMQ 连接失败: %v", err)}ch, err := conn.Channel()if err != nil {_ = conn.Close()return fmt.Errorf("RabbitMQ 打开通道失败: %v", err)}c.conn = connc.ch = chlog.Println("[RabbitMQ] 连接成功:", c.url)// 启动 watcher(只启动一次)go c.watchConnection()// 恢复之前声明(若有)for _, e := range c.exchanges {if err := c.declareExchangeQueueBind(e); err != nil {log.Printf("[RabbitMQ] 恢复 Exchange/Queue 失败: %v", err)}}// 恢复之前注册的消费者for _, cs := range c.consumers {go func(q string, h func(amqp.Delivery)) {if err := c.startConsume(q, h); err != nil {log.Printf("[RabbitMQ] 恢复 Consumer %s 初次失败: %v", q, err)}}(cs.Queue, cs.Handler)}return nil
    }// ------------------ Watcher / Reconnect ------------------// watchConnection 只监听连接级别关闭,触发全局重连流程。
    func (c *Client) watchConnection() {for {c.mu.Lock()conn := c.connch := c.chc.mu.Unlock()if conn == nil {select {case <-time.After(2 * time.Second):case <-c.closed:return}continue}connClose := conn.NotifyClose(make(chan *amqp.Error, 1))chClose := ch.NotifyClose(make(chan *amqp.Error, 1))select {case err := <-connClose:if err != nil {log.Printf("[RabbitMQ] 连接关闭: %v", err)c.handleReconnect()}case err := <-chClose:if err != nil {log.Printf("[RabbitMQ] admin 通道关闭: %v", err)c.handleReconnect()}case <-c.closed:return}}
    }func (c *Client) handleReconnect() {if !atomic.CompareAndSwapInt32(&c.reconnecting, 0, 1) {log.Println("[RabbitMQ] 重连已在进行中,忽略本次触发")return}go func() {defer atomic.StoreInt32(&c.reconnecting, 0)// 先安全关闭旧 conn/ch
            c.mu.Lock()if c.ch != nil {_ = c.ch.Close()c.ch = nil}if c.conn != nil {_ = c.conn.Close()c.conn = nil}c.mu.Unlock()// 指数退避重连backoff := time.Second * 2for {select {case <-c.closed:returndefault:}log.Println("[RabbitMQ] 尝试重新连接中...")if err := c.Connect(); err != nil {log.Printf("[RabbitMQ] 重连失败: %v,%s 后重试", err, backoff)time.Sleep(backoff)// 指数回退到最大 30sif backoff < 30*time.Second {backoff *= 2}continue}log.Println("[RabbitMQ] 重连成功并恢复所有消费者")return}}()
    }// ------------------ 声明 Exchange/Queue ------------------// Declare 会在 admin channel 上声明并保存声明记录(用于重连恢复)
    func (c *Client) Declare(exchange, queue, routingKey string) error {c.mu.Lock()defer c.mu.Unlock()if c.ch == nil || c.ch.IsClosed() {return fmt.Errorf("声明失败: admin channel 未打开")}kind := "x-delayed-message"args := amqp.Table{"x-delayed-type": "direct"}if c.isAliyun {kind = "direct"args = nil}if err := c.ch.ExchangeDeclare(exchange,kind,true, false, false, false, // durable: true
            args,); err != nil {return fmt.Errorf("声明 Exchange 失败: %v", err)}if _, err := c.ch.QueueDeclare(queue,true, false, false, false, // durable: true
            nil,); err != nil {return fmt.Errorf("声明 Queue 失败: %v", err)}if err := c.ch.QueueBind(queue, routingKey, exchange, false, nil); err != nil {return fmt.Errorf("绑定失败: %v", err)}// 保存声明用于重连恢复found := falsefor _, e := range c.exchanges {if e.Exchange == exchange && e.Queue == queue && e.RoutingKey == routingKey {found = truebreak}}if !found {c.exchanges = append(c.exchanges, exchangeDecl{Exchange:   exchange,Queue:      queue,RoutingKey: routingKey,})}return nil
    }func (c *Client) declareExchangeQueueBind(e exchangeDecl) error {c.mu.Lock()defer c.mu.Unlock()if c.ch == nil || c.ch.IsClosed() {return fmt.Errorf("admin channel 未打开")}kind := "x-delayed-message"args := amqp.Table{"x-delayed-type": "direct"}if c.isAliyun {kind = "direct"args = nil}if err := c.ch.ExchangeDeclare(e.Exchange, kind, true, false, false, false, args); err != nil {return err}if _, err := c.ch.QueueDeclare(e.Queue, true, false, false, false, nil); err != nil {return err}if err := c.ch.QueueBind(e.Queue, e.RoutingKey, e.Exchange, false, nil); err != nil {return err}return nil
    }// ------------------ 发布消息 ------------------func (c *Client) Publish(exchange, routingKey string, body []byte, correlationId string, delayMs int64) error {ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()headers := amqp.Table{}if delayMs > 0 {headers["x-delay"] = int32(delayMs)}c.mu.Lock()ch := c.chc.mu.Unlock()if ch == nil || ch.IsClosed() {return fmt.Errorf("Publish 失败: admin channel 已关闭")}// 生产健壮性:此处建议为 ch 开启 Publisher Confirms,并在 PublishWithContext 后等待确认。// 为了保持代码简洁,这里暂时沿用原逻辑。return ch.PublishWithContext(ctx,exchange,routingKey,false, false,amqp.Publishing{ContentType:   "application/json",Body:          body,Headers:       headers,CorrelationId: correlationId,DeliveryMode:  amqp.Persistent, // 建议设置为持久化
            },)
    }// ------------------ 消费者 ------------------// Consume 注册消费者(保存 handler),并尝试启动消费者。
    func (c *Client) Consume(queue string, handler func(amqp.Delivery)) error {// 保存 consumer 定义(避免重复)
        c.mu.Lock()found := falsefor _, cc := range c.consumers {if cc.Queue == queue {found = truebreak}}if !found {c.consumers = append(c.consumers, consumerDecl{Queue: queue, Handler: handler})}c.mu.Unlock()// 尝试启动消费者return c.startConsume(queue, handler)
    }func (c *Client) startConsume(queue string, handler func(amqp.Delivery)) error {// 创建独立 channel
        c.mu.Lock()conn := c.connc.mu.Unlock()if conn == nil {return fmt.Errorf("startConsume 失败: connection 未就绪")}ch, err := conn.Channel()if err != nil {return fmt.Errorf("创建 consumer channel 失败: %v", err)}// 设置 QoS,避免一次性推太多未 ack 消息(可根据业务调整)if err := ch.Qos(3, 0, false); err != nil {log.Printf("[RabbitMQ] 设置 Qos 失败: %v", err)}consumerTag := fmt.Sprintf("%s-%d", queue, time.Now().UnixNano())msgs, err := ch.Consume(queue, consumerTag, false, false, false, false, nil)if err != nil {_ = ch.Close()return fmt.Errorf("启动消费者失败: %v", err)}// 监听该 channel 的 NotifyClose,以便自动重连此消费者closeCh := ch.NotifyClose(make(chan *amqp.Error, 1))c.wg.Add(1)go func() {defer func() {// 一定关闭 channel 并释放 wg_ = ch.Close()c.wg.Done()log.Printf("[RabbitMQ] 消费者 %s 已退出 goroutine", queue) // 此日志是预期行为
            }()for {select {case msg, ok := <-msgs:if !ok {// msgs channel 被关闭,可能是由于底层 Channel 已经关闭log.Printf("[RabbitMQ] 消费者 %s: msgs channel closed, will attempt restart", queue)go c.retryStartConsume(queue, handler)return}// 标记是否需要主动退出shouldExit := false// 处理消息(包含 panic 保护)func(m amqp.Delivery) {// defer 用于捕获 panic 并尝试 Nackdefer func() {if r := recover(); r != nil {log.Printf("[RabbitMQ] Handler panic: %v, msg: %s", r, string(m.Body))// 尽量 Nack,如果 Nack 失败,则设置 shouldExitif safeNack(m, true) != nil {shouldExit = true}}}()if err := safeHandle(handler, m); err != nil {// Handler 返回错误var nackErr errorif m.Redelivered {nackErr = safeNack(m, false) // 拒绝,不重入 (可能进死信)} else {nackErr = safeNack(m, true) // Nack 并重入
                            }if nackErr != nil {// Nack 失败,说明 Channel 可能已关闭shouldExit = true}return}// 成功处理if err := safeAck(m); err != nil {// **健壮性增强 2:Ack 失败,主动退出**// Ack 失败,说明 Channel 可能已关闭shouldExit = true}}(msg)// **健壮性增强 3:如果 Ack/Nack 失败,立即退出 Goroutine**if shouldExit {log.Printf("[RabbitMQ] 消费者 %s: Ack/Nack 失败,主动触发重启", queue)go c.retryStartConsume(queue, handler)return}case err := <-closeCh:// Channel 收到关闭通知(err != nil 是外部故障)if err != nil {log.Printf("[RabbitMQ] consumer channel closed for queue %s: %v", queue, err)go c.retryStartConsume(queue, handler)return}// err == nil 是本地关闭,仍然触发重试/恢复log.Printf("[RabbitMQ] consumer channel closed (nil) for queue %s, attempting restart", queue)go c.retryStartConsume(queue, handler)returncase <-c.closed:return}}}()return nil
    }// retryStartConsume 会循环尝试重启特定 queue 的消费者,直到成功或 client 被关闭
    func (c *Client) retryStartConsume(queue string, handler func(amqp.Delivery)) {backoff := time.Second * 2for {select {case <-c.closed:returndefault:}// 如果全局在重连中,等待重连完成if atomic.LoadInt32(&c.reconnecting) == 1 {time.Sleep(2 * time.Second)continue}if err := c.startConsume(queue, handler); err != nil {log.Printf("[RabbitMQ] 重启消费者 %s 失败: %v, %s 后重试", queue, err, backoff)time.Sleep(backoff)if backoff < 30*time.Second {backoff *= 2}continue}log.Printf("[RabbitMQ] 消费者 %s 重启成功", queue)return}
    }// ------------------ 关闭 ------------------func (c *Client) Close() {close(c.closed)c.mu.Lock()if c.ch != nil {_ = c.ch.Close()c.ch = nil}if c.conn != nil {_ = c.conn.Close()c.conn = nil}c.mu.Unlock()// 等待所有消费者 goroutine 退出
        c.wg.Wait()log.Println("[RabbitMQ] 客户端已关闭")
    }

    注册流程:

    type MQBaseConfig struct {URL        stringUserName   stringPassword   stringIsAliyun   boolAk         stringSk         stringEndPoint   stringInstanceId stringVHost      string
    }var mqSetting *MQBaseConfigfunc init() {var ctx = gctx.New()mqSetting = &MQBaseConfig{URL:        g.Cfg().MustGet(ctx, "rabbitMq.url").String(),UserName:   g.Cfg().MustGet(ctx, "rabbitMq.userName").String(),Password:   g.Cfg().MustGet(ctx, "rabbitMq.password").String(),IsAliyun:   g.Cfg().MustGet(ctx, "rabbitMq.isAliyun").Bool(),Ak:         g.Cfg().MustGet(ctx, "rabbitMq.AK").String(),Sk:         g.Cfg().MustGet(ctx, "rabbitMq.SK").String(),EndPoint:   g.Cfg().MustGet(ctx, "rabbitMq.endpoint").String(),InstanceId: g.Cfg().MustGet(ctx, "rabbitMq.instanceId").String(),VHost:      g.Cfg().MustGet(ctx, "rabbitMq.vHost").String(),}connStr := buildConnURL(mqSetting)//client := New("amqp://user:pass@localhost:5672/", mqSetting.IsAliyun)
    client := mq.New(connStr, mqSetting.IsAliyun)if err := client.Connect(); err != nil {log.Fatalf("连接失败: %v", err)}// 声明 Exchange / Queue
        client.Declare(rabbitmqlib.AD_PLAN_EXCHANGE,rabbitmqlib.AD_PLAN_QUEUE,rabbitmqlib.AD_PLAN_KEY,)// ---------------------------//   创建 KS 任务延迟队列// ---------------------------
        client.Declare(rabbitmqlib.KS_TASK_EXCHANGE,rabbitmqlib.KS_TASK_QUEUE,rabbitmqlib.KS_TASK_KEY,)client.Declare(rabbitmqlib.KS_UPLOAD_EXCHANGE,rabbitmqlib.KS_UPLOAD_QUEUE,rabbitmqlib.KS_UPLOAD_KEY,)client.Declare(rabbitmqlib.KS_PLAN_EXCHANGE,rabbitmqlib.KS_PLAN_QUEUE,rabbitmqlib.KS_PLAN_KEY,)log.Println("[RabbitMQ] initialization success")service.RegisterRabbitMqClient(client)}func buildConnURL(cfg *MQBaseConfig) string {if cfg.IsAliyun {// 阿里云 RabbitMQ 使用 AK/SK 登录// 阿里云官方格式: amqp://<AccessKey>:<SecretKey>@<InstanceId>.mq-amqp.<region>.aliyuncs.com:5672/<VHost>return fmt.Sprintf("amqp://%s:%s@%s/%s",cfg.Ak, cfg.Sk, cfg.EndPoint, cfg.VHost,)}// 自建 RabbitMQreturn fmt.Sprintf("amqp://%s:%s@%s/%s",cfg.UserName, cfg.Password, cfg.URL, cfg.VHost,)
    }

    配置文件结构如下:

    rabbitMq:url: "1.1.1.1:5672"userName: "uname"password: "pwd"isAliyun: trueSK: "sk"AK: "uk"endpoint: "aliurl"instanceId: ""vHost: "vhost" 
  • 以下是代码发布流程
    func PublishUploadTask(ctx context.Context, req *model.KSAdUploadVideoTaskReq) (err error) {// 发送消息队列correlationId := libUtils.GenerateID()data, _ := json.Marshal(req)err = commonService.GetRabbitMqClient().Publish(rabbitmqlib.KS_UPLOAD_EXCHANGE, rabbitmqlib.KS_UPLOAD_KEY, data, correlationId, 5*1000)if err != nil {g.Log().Errorf(ctx, "PublishDelayMsg error: %v", err)}return
    }

     

  • 以下的代码是消费流程
    func init() {service.RegisterKsAdvertiserStrategyGenerate(New())commonService.GetRabbitMqClient().Consume(rabbitmqlib.KS_TASK_QUEUE, ConsumerAdPlan)
    }func ConsumerAdPlan(msg amqp.Delivery) {g.Log().Debugf(nil, "------开始执行ConsumerAdPlan -service Received CorrelationId:%s, body: %#v\n", msg.CorrelationId, string(msg.Body))withContext, cancel := context.WithCancel(context.Background())defer cancel()req := new(model.KSPlanTaskReq)err := json.Unmarshal(msg.Body, &req)if err != nil {g.Log().Errorf(withContext, "JSON解析失败:%v body:%s", err, string(msg.Body))msg.Ack(false)return}// 超过三次直接丢掉if req.RetryCount >= 3 {msg.Ack(false)return}err = service.KsAdvertiserStrategyGenerate().ExecuteTask3(withContext, req.TaskId)if err != nil {// 塞回队列req.RetryCount++correlationId := libUtils.GenerateID()data, _ := json.Marshal(req)err = commonService.GetRabbitMqClient().Publish(rabbitmqlib.KS_PLAN_EXCHANGE, rabbitmqlib.KS_PLAN_KEY, data, correlationId, 5*1000)}// 消费失败  ----------  执行过程中抛出异常,需要重试//delayNack(&msg)//continue// 消费成功 -------------msg.Ack(false)}

     

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/942027.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

byte[](字节数组)

byte[](字节数组)是编程中用于处理原始字节序列的数据结构。它在各种需要处理二进制数据的场景中都非常重要。为了让你快速抓住核心,我先用一个表格来汇总它的关键特性和常见操作,然后再详细展开。特性/操作类别 具…

2025 年火花机厂家最新推荐榜:涵盖新型 / 镜面 / 数控 / 五轴联动等多类型设备,优质厂家精选助力企业精准选购

引言 当前精密制造行业对火花机的需求日益多元化,从新型、镜面火花机到数控、五轴联动等专用设备,市场产品种类繁杂,而不同品牌在技术实力、加工精度、售后保障等方面差异显著。许多企业在选购时,常因缺乏专业参考…

2025年10月抗老面霜产品推荐榜:五强横评选购指南

入秋以后,昼夜温差拉大,办公室空调依旧强劲,很多25岁以上的职场人发现:早上粉底开始卡纹,午休后法令纹阴影加重,熬夜加班第二天苹果肌“垮”得特别明显。抗老面霜的搜索量随之在电商平台上升38%,小红书“30岁第…

2025 年工业连接器厂家最新推荐榜单:聚焦 M8/M12 / 防水 / 重载 / 以太网品类,精选优质国产企业助力高效采购

引言 随着工业自动化、新能源汽车、海洋工程等领域加速升级,连接器作为设备核心连接部件,其品质与适配性直接决定系统运行效率与安全。当前市场中,国产连接器品牌数量激增,但产品质量参差不齐,部分产品存在防护等…

Java设计模式之工厂模式 - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

【转】[C#] 要从接口取时间,单个订单查询和批量查询,写一个接口还是两个接口合适?

【转】[C#] 要从接口取时间,单个订单查询和批量查询,写一个接口还是两个接口合适?转自:豆包 建议分成两个独立接口,而非合并为一个。这种设计更符合接口的单一职责原则,能让接口语义更清晰、扩展性更强,同时避免…

2025 年药包材辅导公司最新推荐榜:GMP 验证 / 质量体系 / 实验室装修等服务优质机构权威评选

引言 药包材作为药品安全的 “第一道防线”,其质量直接关联用药安全与药品稳定性。随着《药品包装材料与药物相容性试验指导原则》等政策持续收紧,企业在 GMP 设备验证、洁净厂房设计、注册申报等环节面临严苛合规考…

2025年10月防脱生发产品推荐榜:十款临床验证口碑对比

脱发不再是“中年专属”,2025年国家卫健委发布的《中国脱发人群调查》显示,我国脱发人群已突破2.5亿,其中30岁以下占比高达60%。加班熬夜、高糖高油饮食、焦虑情绪让“发量焦虑”提前到来。很多人第一次走进皮肤科,…

2025 年国内优质不锈钢厂家最新推荐排行榜:含沈阳/东三省区域及水箱油罐等产品优质服务商楼梯/激光切割/桥梁杆/真空罐/扶手不锈钢厂家推荐

引言 当前国内不锈钢市场产品品类繁杂,从基础的板材、管材到定制化的水箱、油罐、桥梁杆等,覆盖建筑、化工、机械等多个领域,采购方常面临产品质量难甄别、供应商资质参差不齐的困境。部分供应商存在交货延迟、售后…

界面控件DevExpress WPF v25.2新功能预览 - 聚焦AI功能提升

界面控件DevExpress WPF v25.2新功能预览 - 聚焦AI功能提升DevExpress WPF 拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序…

CSS 预处理器:Sass的基本用法、核心特性 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025 顶管源头厂家最新推荐榜单:F 型混凝土 / 水泥 / 电力 / 矩形 / 市政排水大口径优质供应商精选

引言 当前城市地下管网、综合管廊等基础设施建设进入高峰期,顶管作为核心建材的市场需求持续激增。但行业中仍存在产能不足导致供货滞后、工艺不规范引发质量隐患、新品牌实力难辨等问题,采购方常因信息不对称选错供…

使用DMA和PWM驱动16组WS2812 LED的STM32实现

STM32使用DMA和PWM来驱动16组WS2812 LED灯带。WS2812是一种智能控制LED,每个LED都需要24位数据(8位绿色,8位红色,8位蓝色),并且对时序要求非常严格。 硬件设计考虑GPIO选择:选择16个可用的GPIO引脚,最好是同一…

2025年GEO品牌推荐榜:云视GEO以全栈技术引领行业变革

文章摘要 本文基于2025年GEO行业趋势,深度分析用户对品牌推荐和排行的需求,重点推荐四川云视有客科技有限公司的云视GEO服务。通过解读其GEO-AI搜索优化技术、线性规划博弈算法及定制化方案,揭示如何实现干预延迟≤…

【开题答辩实录分享】以《 Python基于大数据的四川旅游景点数据分析与可视化》为例进行答辩实录分享 - 实践

【开题答辩实录分享】以《 Python基于大数据的四川旅游景点数据分析与可视化》为例进行答辩实录分享 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display…

linux 程序 启动命令

#!/bin/sh # 获取当前日期current_date=$(date +%Y-%m-%d) # 0. 关闭程序kill -9 `ps -ef|grep java | grep leaf-business-zyyk-flow | awk {print \$2}` > /dev/null 2>&1 # 删除创建日期在10天前的日志文…

2025 年台车炉厂家最新推荐榜,技术实力与市场口碑深度解析,助力企业精准选型天然气/燃气/热处理/全纤维/翻转式台车炉厂家推荐

引言 在工业制造领域,台车炉作为热处理关键设备,其性能直接关系到工件质量、生产效率与企业能耗成本。当前市场上,台车炉品牌数量繁杂,部分产品存在控温精度不足、能耗偏高、售后响应滞后等问题,叠加环保政策趋严…

2025 年贵阳家居品牌最新推荐榜,技术实力与市场口碑深度解析贵阳家居实木家具/贵阳家居布艺沙发/贵阳家居多功能沙发家居公司推荐

引言 伴随家居消费升级与行业智能化转型,贵阳家居市场呈现 “新旧品牌同台竞技” 的格局:既有深耕本地的老牌企业,也有凭借创新快速崛起的新锐力量。但市场繁荣背后,材质以次充好、环保不达标、售后缺位等问题仍困…

ida pro 9.2 接入 ida-pro-mcp

ida-pro-mcp 官方文档 https://github.com/mrexodia/ida-pro-mcp 已在是我的安装过过程 1. 安装IDA pro 9.2 ,默认路径 C:\Program Files\IDA Professional 9.2 2. 下载安装python及pip,下载python3.11压缩包到ida安…

2025 年最新高低温试验箱厂家排行榜:精选优质供应商,专业推荐助您精准选购合适设备恒温恒湿试验箱/高低温试验箱厂家推荐

引言 在工业生产与科研实验中,高低温试验箱是保障产品质量、推动科研进展的关键设备。但当前市场品牌繁杂,部分小型品牌缺乏核心技术,设备在温度控制精度、稳定性等方面难以达标,且行业标准执行不到位、售后服务不…