Go语言RPC开发深度指南:net/rpc包的实战技巧和优化策略

news2024/12/26 23:07:08

Go语言RPC开发深度指南:net/rpc包的实战技巧和优化策略

    • 概览
    • 理解`net/rpc`的核心概念
      • RPC的基本原理
      • `net/rpc`的工作模式
      • 关键特性
    • 快速开始
      • 准备RPC服务和客户端的基础环境
      • 构建一个基础的RPC服务端
      • 构建一个基础的RPC客户端
    • 开发一个实际的RPC服务
      • 设计服务接口
      • 实现服务
      • 客户端的构建与功能实现
    • 客户端的构建与优化
      • 如何构建一个高效的客户端
        • 1. **利用连接池管理网络连接**
        • 2. **实现异步调用**
      • 错误处理和异常
        • 1. **处理网络异常和服务错误**
        • 2. **优雅地处理超时和取消**
    • 服务端的性能优化
      • 性能调优技巧
        • 1. **利用并发**
        • 2. **优化资源使用**
        • 3. **减少锁的使用**
      • 并发处理
      • 资源管理
    • `net/rpc`的高级用法
      • 使用编解码器自定义数据格式
        • 自定义JSON编解码器示例
      • 上下文管理和超时控制
      • 安全性考虑和TLS支持
    • 调试与测试RPC应用
      • 日志记录
      • 性能分析
      • 单元测试和集成测试
        • 单元测试
        • 集成测试
    • 实战案例分析
      • 案例一:分布式计算服务
        • 服务端设计
        • 客户端设计
      • 案例二:异步任务队列
        • 服务端设计
        • 客户端设计
      • 小结
    • 总结
      • 主要学到的知识点

在这里插入图片描述

概览

在现代软件开发中,网络编程扮演着至关重要的角色,而远程过程调用(Remote Procedure Call,RPC)技术是实现分布式系统间通信的一种常见方法。net/rpc包是Go语言标凈库中提供的一个RPC实现,它支持高效的网络通信,允许客户端像调用本地函数一样调用远程服务器上的函数。

本文旨在为中高级Go开发者提供一个详尽的net/rpc包教程,内容将全面覆盖从基础用法到高级应用技巧,特别强调实际开发中的应用场景和性能优化。通过本文的学习,读者可以掌握构建RPC服务的完整流程,包括服务端和客户端的开发、调试、测试以及性能调优。文章将结合实际代码示例,逐步引导读者深入理解并有效利用net/rpc的强大功能。

我们不会涉及Go语言的安装过程或其发展历史,而是专注于技术和实战内容,确保读者能够直接将学到的知识应用到实际开发工作中。

理解net/rpc的核心概念

net/rpc包提供了一个简单但强大的RPC实现,使得开发分布式应用变得更加直接和高效。要有效利用这一技术,首先需要理解几个核心概念。

RPC的基本原理

远程过程调用(RPC)是一种技术,它允许一个程序调用另一个地址空间(通常是远程服务器)上的程序或服务,而调用者无需关心底层网络技术的细节。RPC的目标是使远程服务调用尽可能地像本地调用一样简单。

net/rpc的工作模式

net/rpc包使得实现RPC变得非常简单。它默认使用Go的编码方式(gob编码),但也支持其他数据编码方式,如JSON。该包主要包括两部分:服务器端和客户端。服务器端负责注册对象(服务),这些对象拥有可被远程访问的方法;客户端则通过网络调用这些方法。

关键特性

  • 服务注册: 在net/rpc中,你需要注册一个对象,这个对象的方法才能被远程访问。只有符合特定签名的方法才能被注册:方法必须是可导出的、有两个参数,且第二个参数是指针类型,方法还必须有一个返回值error
  • 网络透明性: 调用者无需知道底层的网络实现细节,net/rpc封装了所有的网络交互操作。
  • 同步与异步调用: 默认情况下,RPC调用是同步的,即调用者发起调用后将阻塞,直到收到响应。但net/rpc也支持异步操作,允许调用者继续执行其他任务,直到RPC操作完成。

这些核心概念为理解如何有效地使用net/rpc奠定了基础。接下来的章节将通过具体的示例展示如何创建RPC服务和客户端,并探讨各种实战技巧。

快速开始

为了帮助读者快速掌握net/rpc的使用,我们将从搭建一个简单的RPC服务和客户端开始。这将为后续更复杂的应用打下坚实的基础。

准备RPC服务和客户端的基础环境

首先,确保你的开发环境中已安装Go语言。接下来,创建两个文件:server.go用于服务器端代码,client.go用于客户端代码。

构建一个基础的RPC服务端

  1. 定义服务和方法
    创建一个名为Arithmetic的服务,提供一个名为Multiply的方法,用于计算两个整数的乘积。

    package main
    
    import (
        "net"
        "net/rpc"
    )
    
    type Arithmetic int
    
    type Args struct {
        A, B int
    }
    
    func (t *Arithmetic) Multiply(args *Args, reply *int) error {
        *reply = args.A * args.B
        return nil
    }
    
    func main() {
        arith := new(Arithmetic)
        rpc.Register(arith)
        listener, err := net.Listen("tcp", ":1234")
        if err != nil {
            panic(err)
        }
        defer listener.Close()
        rpc.Accept(listener)
    }
    
  2. 注册服务
    使用rpc.Register函数注册Arithmetic服务,使其可以接收远程调用。

  3. 启动服务监听
    在TCP端口1234上监听来自客户端的连接请求。

构建一个基础的RPC客户端

  1. 连接到RPC服务器
    客户端通过rpc.Dial连接到服务器。

    package main
    
    import (
        "fmt"
        "log"
        "net/rpc"
    )
    
    func main() {
        client, err := rpc.Dial("tcp", "localhost:1234")
        if err != nil {
            log.Fatal("Dialing:", err)
        }
    
        args := &Args{7, 6}
        var reply int
        err = client.Call("Arithmetic.Multiply", args, &reply)
        if err != nil {
            log.Fatal("Arithmetic error:", err)
        }
        fmt.Printf("Result: %d\n", reply)
    }
    
  2. 进行RPC调用
    使用client.Call发起远程过程调用。在这里,我们调用了Arithmetic.Multiply方法,并等待它返回结果。

通过这个基本示例,您已经可以看到net/rpc的基础用法。在这个框架下,您可以继续添加更多的方法和功能,增强服务的能力。

开发一个实际的RPC服务

在快速开始指南中,我们通过一个基本的例子介绍了如何设置RPC服务器和客户端。接下来,我们将深入探讨如何开发一个具体的、功能更丰富的RPC服务。

设计服务接口

设计良好的服务接口是构建高效RPC服务的关键。接口应当简洁明了,同时满足功能需求。假设我们要开发一个订单管理系统,其中包括增加订单、查询订单等功能。

  1. 定义服务及其方法

    • AddOrder:添加新订单。
    • GetOrder:根据订单ID获取订单详情。
    type OrderService struct{}
    
    type Order struct {
        ID       string
        ItemName string
        Quantity int
        Price    float64
    }
    
    type OrderID struct {
        ID string
    }
    
    func (s *OrderService) AddOrder(order *Order, reply *OrderID) error {
        // 实现添加订单到数据库的逻辑
        *reply = OrderID{ID: "生成的订单ID"}
        return nil
    }
    
    func (s *OrderService) GetOrder(orderID *OrderID, reply *Order) error {
        // 实现根据订单ID查询订单的逻辑
        *reply = Order{ID: orderID.ID, ItemName: "示例商品", Quantity: 1, Price: 99.99}
        return nil
    }
    

实现服务

  1. 注册和监听服务

    • 将服务对象注册到RPC系统。
    • 配置服务器以监听特定端口。
    func main() {
        orderService := new(OrderService)
        rpc.Register(orderService)
        listener, err := net.Listen("tcp", ":1234")
        if err != nil {
            log.Fatal("监听端口失败:", err)
        }
        defer listener.Close()
        fmt.Println("服务已启动,监听端口1234")
        rpc.Accept(listener)
    }
    

客户端的构建与功能实现

  1. 实现客户端调用

    • 客户端需要能够创建订单并查询订单详情。
    func main() {
        client, err := rpc.Dial("tcp", "localhost:1234")
        if err != nil {
            log.Fatal("连接服务失败:", err)
        }
    
        // 添加订单
        newOrder := &Order{ItemName: "新书", Quantity: 1, Price: 45.50}
        orderID := new OrderID{}
        err = client.Call("OrderService.AddOrder", newOrder, &orderID)
        if err != nil {
            log.Fatal("添加订单失败:", err)
        }
        fmt.Printf("新订单ID: %s\n", orderID.ID)
    
        // 查询订单
        queryOrder := new(Order)
        err = client.Call("OrderService.GetOrder", orderID, queryOrder)
        if err != nil {
            log.Fatal("查询订单失败:", err)
        }
        fmt.Printf("订单详情: %+v\n", queryOrder)
    }
    

通过这些步骤,我们构建了一个功能更全面的RPC服务。这种方式展示了如何为实际的业务需求设计和实现RPC接口。

客户端的构建与优化

构建RPC客户端不仅仅是实现功能调用那么简单,还需要考虑效率、错误处理和用户体验。本节将探讨如何构建一个高效的客户端,实现异步调用,以及如何处理各种可能的错误。

如何构建一个高效的客户端

1. 利用连接池管理网络连接

在高并发环境下,频繁地打开和关闭网络连接会极大地影响性能。使用连接池可以重用现有的连接,减少开销。

type RPCClientPool struct {
    pool *sync.Pool
    network, address string
}

func NewRPCClientPool(network, address string) *RPCClientPool {
    return &RPCClientPool{
        network: network,
        address: address,
        pool: &sync.Pool{
            New: func() interface{} {
                client, err := rpc.Dial(network, address)
                if err != nil {
                    return nil
                }
                return client
            },
        },
    }
}

func (p *RPCClientPool) GetClient() *rpc.Client {
    return p.pool.Get().(*rpc.Client)
}

func (p *RPCClientPool) PutClient(client *rpc.Client) {
    p.pool.Put(client)
}
2. 实现异步调用

异步调用允许客户端在等待服务器响应的同时继续进行其他操作,提高了应用程序的响应性。

// 异步RPC调用示例
call := client.Go("OrderService.AddOrder", newOrder, &orderID, nil)
replyCall := <-call.Done
if replyCall.Error != nil {
    log.Fatal("添加订单失败:", replyCall.Error)
}
fmt.Printf("新订单ID: %s\n", orderID.ID)

错误处理和异常

1. 处理网络异常和服务错误

网络问题或服务端错误都可能导致RPC调用失败。合理处理这些错误对于构建可靠的应用至关重要。

if err := client.Call("Service.Method", args, &reply); err != nil {
    if err == rpc.ErrShutdown {
        // 处理服务关闭的情况
        fmt.Println("RPC服务已关闭")
    } else {
        // 记录错误并重试或返回错误
        log.Printf("RPC调用失败: %v", err)
    }
}
2. 优雅地处理超时和取消

控制调用超时对于维护良好的用户体验非常重要,尤其是在网络延迟或服务响应慢的情况下。

timeout := time.After(5 * time.Second)
done := make(chan bool)
go func() {
    err := client.Call("Service.Method", args, &reply)
    done <- (err == nil)
}()

select {
case success := <-done:
    if !success {
        fmt.Println("操作失败,请重试")
    }
case <-timeout:
    fmt.Println("操作超时")
}

这些技术和策略将帮助开发者构建出更稳定、高效和用户友好的RPC客户端。

服务端的性能优化

在RPC服务中,服务端的性能优化是确保快速响应和处理大量并发请求的关键。本节将探讨一些关于性能调优、并发处理和资源管理的实用技巧。

性能调优技巧

1. 利用并发

Go 语言天生支持并发,这使得在服务端实现高效的并发处理变得相对简单。利用Go的goroutine,可以轻松地为每个RPC请求分配一个单独的执行线程。

func main() {
    listener, err := net.Listen("tcp", ":1234")
    if err != nil {
        log.Fatal("监听端口失败:", err)
    }
    defer listener.Close()
    fmt.Println("服务已启动,监听端口1234")

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Print("接受连接失败:", err)
            continue
        }
        go rpc.ServeConn(conn)
    }
}
2. 优化资源使用

避免在处理RPC请求时进行昂贵的操作,如频繁的磁盘I/O或数据库访问。应当尽可能利用缓存和内存存储中间结果。

3. 减少锁的使用

在多线程环境中,锁的使用可能成为性能瓶颈。尽可能使用无锁设计或其他并发控制机制,比如channel,来减少锁的争用。

并发处理

处理并发不仅仅是启动多个goroutine那么简单。它还涉及合理地设计数据访问策略和资源共享机制,以避免竞态条件和数据不一致。

  • 使用channel进行通信:在Go中,推荐使用channel而不是共享内存来进行goroutine之间的通信。
  • 控制goroutine的数量:虽然goroutine非常轻量,但过多的goroutine仍然会消耗大量内存和调度资源。合理控制goroutine的数量是优化并发处理的关键。

资源管理

合理管理内存和连接资源对于提高服务稳定性和性能至关重要。

  • 使用对象池:对于频繁创建和销毁的对象,使用对象池可以减少GC压力并提高性能。
  • 连接复用:对于数据库连接或其他外部服务连接,使用连接池进行管理和复用,避免频繁建立和断开连接。

通过以上技巧,开发者可以显著提高RPC服务的处理能力和效率,满足更高的业务需求。

net/rpc的高级用法

在掌握了net/rpc基础用法之后,了解其高级功能可以帮助我们构建更复杂、更灵活的RPC应用。本节将介绍一些高级技术,如自定义编解码器、使用上下文管理和超时控制,以及增强安全性。

使用编解码器自定义数据格式

默认情况下,net/rpc使用Go的gob编解码器进行数据序列化和反序列化。然而,你可以通过实现rpc.ServerCodecrpc.ClientCodec接口来使用其他编解码器,比如JSON,以支持不同的数据交互需求。

自定义JSON编解码器示例
type JsonCodec struct {
    conn io.ReadWriteCloser
    dec  *json.Decoder
    enc  *json.Encoder
    encBuf *bufio.Writer
}

func NewJsonCodec(conn io.ReadWriteCloser) *JsonCodec {
    buf := bufio.NewWriter(conn)
    return &JsonCodec{
        conn: conn,
        dec:  json.NewDecoder(conn),
        enc:  json.NewEncoder(buf),
        encBuf: buf,
    }
}

func (c *JsonCodec) ReadRequestHeader(r *rpc.Request) error {
    return c.dec.Decode(r)
}

func (c *JsonCodec) ReadRequestBody(body interface{}) error {
    return c.dec.Decode(body)
}

func (c *JsonCodec) WriteResponse(r *rpc.Response, body interface{}) (err error) {
    if err = c.enc.Encode(r); err != nil {
        return
    }
    if err = c.enc.Encode(body); err != nil {
        return
    }
    return c.encBuf.Flush()
}

func (c *JsonCodec) Close() error {
    return c.conn.Close()
}

通过自定义编解码器,你可以轻松实现与其他语言编写的客户端或服务端的交互。

上下文管理和超时控制

net/rpc默认不支持传递上下文(context.Context),这在需要处理超时和取消操作时显得不够灵活。通过包装调用或使用中间件,我们可以增加这种支持。

func CallWithContext(ctx context.Context, client *rpc.Client, serviceMethod string, args, reply interface{}) error {
    done := client.Go(serviceMethod, args, reply, nil).Done
    select {
    case <-ctx.Done():
        return ctx.Err()
    case call := <-done:
        return call.Error
    }
}

安全性考虑和TLS支持

在进行RPC调用时,尤其是跨网络时,确保数据传输的安全是非常重要的。net/rpc可以结合crypto/tls包来实现基于TLS的连接加密。

config := &tls.Config{
    Certificates: []tls.Certificate{cert},
    // 更多的TLS配置...
}
conn, err := tls.Dial("tcp", "server:1234", config)
if err != nil {
    log.Fatal(err)
}
client := rpc.NewClient(conn)

这些高级技术使得net/rpc更加强大和灵活,能够满足更多复杂场景的需求。

调试与测试RPC应用

为确保RPC应用的稳定性和可靠性,有效的调试和测试策略是必不可少的。本节将介绍如何进行日志记录、性能分析以及如何实现RPC应用的单元测试和集成测试。

日志记录

日志是诊断问题的关键工具。在RPC服务中合理地记录日志可以帮助开发者追踪请求的流程,发现并解决问题。

func (t *Arithmetic) Multiply(args *Args, reply *int) error {
    log.Printf("Multiply called with: %d and %d", args.A, args.B)
    *reply = args.A * args.B
    log.Printf("Multiply result: %d", *reply)
    return nil
}

性能分析

Go提供了强大的内置工具来进行性能分析,包括CPU和内存的使用情况。使用这些工具,你可以发现程序的瓶颈并进行优化。

import "net/http"
import _ "net/http/pprof"

func main() {
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()
    // 其他服务代码
}

访问http://localhost:6060/debug/pprof,可以查看和下载性能分析数据。

单元测试和集成测试

单元测试

为RPC方法编写单元测试通常涉及到模拟网络请求和响应。可以使用Go的标准库net/rpc进行单元测试。

func TestArithmeticMultiply(t *testing.T) {
    arith := new(Arithmetic)
    args := Args{A: 7, B: 6}
    var reply int
    err := arith.Multiply(&args, &reply)
    if err != nil {
        t.Errorf("Multiply failed: %s", err)
    }
    if reply != 42 {
        t.Errorf("Multiply returned wrong result: got %v, want %v", reply, 42)
    }
}
集成测试

集成测试通常涉及到运行一个完整的RPC服务器和客户端,确保它们在真实环境下能正常工作。

func TestRPCIntegration(t *testing.T) {
    // 启动RPC服务
    go startRPCServer()
    time.Sleep(1 * time.Second) // 确保服务器启动

    client, err := rpc.Dial("tcp", "localhost:1234")
    if err != nil {
        t.Fatal("Dialing:", err)
    }

    // 测试RPC调用
    args := &Args{7, 6}
    var reply int
    err = client.Call("Arithmetic.Multiply", args, &reply)
    if err != nil {
        t.Fatal("Arithmetic error:", err)
    }
    if reply != 42 {
        t.Errorf("Wrong answer: %d != %d", reply, 42)
    }
}

通过这些调试和测试方法,你可以确保RPC应用的健売性和效率。

实战案例分析

在本节中,我们将通过一些具体的实战案例来展示net/rpc在现实世界中的应用。这些案例将帮助读者更深入地理解RPC技术的实际使用场景和解决方案。

案例一:分布式计算服务

假设我们需要开发一个分布式计算服务,该服务能够接收来自客户端的大量计算任务,分发到多个服务器节点进行处理,最后收集结果返回给客户端。

服务端设计
type ComputeService struct{}

type Task struct {
    Data  []int
    Op    string
    TaskID int
}

type Result struct {
    TaskID int
    Output int
}

func (c *ComputeService) Execute(task *Task, result *Result) error {
    output := 0
    switch task.Op {
    case "sum":
        for _, num := range task.Data {
            output += num
        }
    case "multiply":
        output = 1
        for _, num := range task.Data {
            output *= num
        }
    }
    result.TaskID = task.TaskID
    result.Output = output
    return nil
}

func startServer() {
    compute := new(ComputeService)
    rpc.Register(compute)
    listener, _ := net.Listen("tcp", ":1234")
    defer listener.Close()
    rpc.Accept(listener)
}
客户端设计
func sendTasks() {
    client, _ := rpc.Dial("tcp", "localhost:1234")
    tasks := []Task{
        {Data: []int{1, 2, 3, 4, 5}, Op: "sum", TaskID: 1},
        {Data: []int{1, 2, 3, 4, 5}, Op: "multiply", TaskID: 2},
    }
    for _, task := range tasks {
        result := new(Result)
        client.Call("ComputeService.Execute", &task, result)
        fmt.Printf("Task %d: Result = %d\n", result.TaskID, result.Output)
    }
}

案例二:异步任务队列

假设我们的服务端负责接收任务,将任务存入队列,并异步执行。完成后,通过RPC回调通知客户端。

服务端设计
  1. 任务定义和队列管理
    服务端维护一个任务队列,每接收到一个任务,就将其加入队列,并由工作线程异步处理。
type Task struct {
    ID      int
    Content string
}

type Result struct {
    ID     int
    Status string
}

type TaskQueue struct {
    tasks chan Task
}

func NewTaskQueue(maxQueueSize int) *TaskQueue {
    return &TaskQueue{
        tasks: make(chan Task, maxQueueSize),
    }
}

func (tq *TaskQueue) Enqueue(task Task) {
    tq.tasks <- task
}

func (tq *TaskQueue) Worker(resultChan chan Result) {
    for task := range tq.tasks {
        // 模拟任务处理
        fmt.Printf("Processing task %d\n", task.ID)
        time.Sleep(time.Duration(rand.Intn(5)) * time.Second) // Random sleep to simulate work
        resultChan <- Result{ID: task.ID, Status: "Completed"}
    }
}
  1. RPC服务注册和启动工作线程
    注册RPC服务并启动工作线程,监听队列并处理任务。
type AsyncTaskService struct {
    Queue     *TaskQueue
    ResultChan chan Result
}

func (s *AsyncTaskService) SubmitTask(task *Task, reply *Result) error {
    s.Queue.Enqueue(*task)
    *reply = Result{ID: task.ID, Status: "Queued"}
    return nil
}

func startServer() {
    taskQueue := NewTaskQueue(10)
    resultChan := make(chan Result)
    rpcServer := new(AsyncTaskService)
    rpcServer.Queue = taskQueue
    rpcServer.ResultChan = resultChan

    rpc.Register(rpcServer)
    listener, _ := net.Listen("tcp", ":1234")
    defer listener.Close()

    go taskQueue.Worker(resultChan)

    rpc.Accept(listener)
}
客户端设计

客户端发送任务到服务器,并可接收任务完成的通知。

func main() {
    client, _ := rpc.Dial("tcp", "localhost:1234")
    task := Task{ID: 1, Content: "Data processing"}
    result := Result{}

    err := client.Call("AsyncTaskService.SubmitTask", &task, &result)
    if err != nil {
        log.Fatal("Task submission failed:", err)
    }
    fmt.Printf("Task %d: %s\n", result.ID, result.Status)

    // 实现回调逻辑或其他形式的结果监听
}

小结

这个案例展示了如何使用net/rpc构建一个支持异步任务处理和结果通知的系统。服务端通过队列管理和工作线程来处理任务,客户端则通过RPC调用提交任务,并能获取任务状态。

总结

通过本文的学习,我们详细探讨了Go语言标准库中net/rpc包的使用,从基础概念到实际开发,再到性能优化和高级应用,最后通过具体案例展示了其在实战中的应用。这些内容不仅涵盖了RPC技术的基本操作,还包括了许多高级技巧,如自定义编解码器、上下文管理、异步处理以及安全性增强等。

主要学到的知识点

  1. net/rpc的基本使用:我们学习了如何设置RPC服务器和客户端,以及如何通过RPC进行基本的方法调用。
  2. 性能优化:介绍了服务端和客户端的性能优化技巧,包括并发处理、资源管理和连接复用等。
  3. 高级功能:探讨了使用自定义编解码器进行数据序列化,以及如何实现安全的RPC通信。
  4. 调试与测试:了解了如何使用日志、性能分析工具和测试方法来确保RPC应用的健壮性和有效性。
  5. 实战案例:通过实战案例,我们看到了net/rpc在处理分布式计算和异步任务队列中的实用性。

随着微服务和分布式系统的日益普及,对于高效、可靠的RPC框架的需求将持续增长。net/rpc虽然提供了基本的RPC功能,但在实际应用中,可能还需要结合其他技术和工具,如消息队列、服务发现和负载均衡等,来构建更复杂的服务架构。

我们希望本文能帮助读者在Go语言的RPC开发旅程中找到启发和帮助,并鼓励大家不断探索和实验,以解决更多复杂的技术挑战。

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

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

相关文章

pdf怎么压缩到2m以内或5m以内的方法

PDF作为一种广泛使用的文档格式&#xff0c;已经成为我们工作和生活中不可或缺的一部分。然而&#xff0c;有时候PDF文件内存会比较大&#xff0c;给我们的存储和传输带来了很大的不便。因此&#xff0c;学会压缩 PDF 文件是非常必要的。 打开"轻云处理pdf官网"&…

隐藏element的DateTimePicker组件自带的清空按钮

管理台页面使用到el-date-picker&#xff0c;type datetimerange 但是组件自带了清空按钮&#xff0c;实际上这个控件业务上代表开始时间和结束时间是一个必填选项&#xff0c;所有想要把清空按钮隐藏掉。 查看了文档https://element.eleme.io/#/zh-CN/component/datetime-p…

react 自定义鼠标右键点击事件

功能&#xff1a;鼠标右键点击节点时&#xff0c;出现“复制”功能&#xff0c;点击其他部位&#xff0c;隐藏“复制”&#xff1b;鼠标右键事件的文案&#xff0c;始终在鼠标点击位置的右下方&#xff1b;点击复制&#xff0c;提示复制成功 效果图&#xff1a; 代码&#xff1…

win11 之下载安装 allure

1. 下载 https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline/2.25.0/allure-commandline-2.25.0.zip 2. 配置系统变量 path 下添加解压后的bin目录 3. 验证是否安装成功 输入 allure

git使用摘樱桃的方式,实现特定需求进行提交合并

文章目录 先checkOut到主要的分支(需求提交到这) 然后双击点别的需求分支,对提交内容选定 进行摘樱桃操作 然后双击回到主要分支,会发现那2个提交内容代码已经在主要分支的本地里,选中其 右键选择Squash Commits进行合并 标注自己的需求标题提交名更改后, 最后进行push推送到…

利用AI云防护实现高效负载均衡

在当今高度数字化的世界里&#xff0c;保证网站和应用的高可用性和响应速度对企业的业务连续性和用户体验至关重要。传统的负载均衡技术虽然能够分发流量&#xff0c;但在面对突发流量、DDoS攻击或资源动态调整时往往力不从心。本文将探讨如何借助AI云防护服务&#xff0c;不仅…

问题解决:局域网下多台电脑实现共享打印机

看了很多篇解决措施&#xff0c;都没有解决&#xff0c;自己鼓弄了好久&#xff0c;终于解决了&#xff0c;如下步骤所示&#xff0c;实测好用。 首先先保证本电脑已打开网络共享 其次关闭防火墙&#xff08;有时会出现奇怪问题&#xff0c;最好关闭&#xff09; 接着访问IP…

Chatgpt教我打游戏攻略

宝可梦朱 我在玩宝可梦朱的时候&#xff0c;我的同行队伍里有黏美儿&#xff0c;等级为65&#xff0c;遇到了下雨天但是没有进化&#xff0c;为什么呢&#xff1f; 黏美儿&#xff08;Goomy&#xff09;要进化为黏美龙&#xff08;Goodra&#xff09;&#xff0c;需要满足以下…

【贪吃蛇】C语言教程

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

音频处理软件adobe audition使用教程

教程1笔记 基本操作 点击文件-》新建-》多轨会话&#xff1a; 编辑-》首选项&#xff0c;设置自动保存时间&#xff1a; 导入素材&#xff0c;文件-》导入素材&#xff0c;或者直接拖动进来文件&#xff01; 导出多轨混音&#xff1a; 更改为需要导出的格式wav,mp3等格式&am…

Netty中Reactor线程的运行逻辑

Netty中的Reactor线程主要干三件事情&#xff1a; 轮询注册在Reactor上的所有Channel感兴趣的IO就绪事件。 处理Channel上的IO就绪事件。 执行Netty中的异步任务。 正是这三个部分组成了Reactor的运行框架&#xff0c;那么我们现在来看下这个运行框架具体是怎么运转的~~ 这…

Windows 命令行界面常用命令

Windows 命令行界面常用命令 首先我们通过WIN R&#xff0c;输入cmd进入命令行界面。 1. dir - 列出当前目录下的文件和子目录 用法&#xff1a; dir2. cd - 切换目录 用法&#xff1a; cd 目录路径返回上一级目录&#xff1a; C:\Users\YourUsername\Documents> cd …

c语言中的字符函数

1.字符分类函数 c语言中有一系列函数是专门做字符分类的&#xff0c;也就是一个字符属于什么类型的字符。这些函数的使用需要包含一个头文件是ctype.h 可能你看这些感觉很懵&#xff0c;我以islower举例 #include<ctype.h> int main() {int retislower(A);printf("…

Android OTA 升级基础知识详解+源码分析

前言&#xff1a; 本文仅仅对OTA升级的几种方式的概念和运用进行总结&#xff0c;仅在使用层面对其解释。需要更详细的内容我推荐大神做的全网最详细的讲解&#xff1a; https://blog.csdn.net/guyongqiangx/article/details/129019303?spm1001.2014.3001.5502 三种升级方式…

编码器原理和功能介绍

目录 概述 1 编码综述 2 增量式旋转编码器 2.1 功能介绍 2.2 实现原理 3 绝对式编码器 3.1 实现原理 3.2 二进制编码器 3.3 格雷码编码器 4 旋转式编码器原理 5 编码器基本参数 5.1 分辨率 5.2 精度 5.3 最大响应频率 5.4 信号输出形式 6 自制简易编码器 概述 …

matlab入门基础笔记

1、绘制简单三角函数&#xff1a; 绘制正弦曲线和余弦曲线。x[0:0.5:360]*pi/180; plot(x,sin(x),x,cos(x)); &#xff08;1&#xff09;明确x轴与y轴变量&#xff1a; 要求为绘制三角函数&#xff1a; X轴&#xff1a;角度对应的弧度数组 Y轴&#xff1a;对应sin(x)的值 求…

【数据结构C++】表达式求值(多位数)课程设计

&#x1f4da;博客主页&#xff1a;Zhui_Yi_ &#x1f50d;&#xff1a;上期回顾&#xff1a;图 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f387;追当今朝天骄&#xff0c;忆顾往昔豪杰。 …

使用Ollama+OpenWebUI本地部署阿里通义千问Qwen2 AI大模型

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;AI大模型部署与应用专栏&#xff1a;点击&#xff01; &#x1f916;Ollama部署LLM专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年6月17日22点50分 &#x1f004;️文章质量&#xff…

函数(下) C语言

函数下 嵌套调用和链式访问1. 嵌套调用2. 链式访问 函数的声明和定义1. 单个文件2. 多个文件3. static 和 extern3.1 static 嵌套调用和链式访问 1. 嵌套调用 嵌套调用就是函数之间的互相调用&#xff0c;每个函数就像⼀个乐高零件&#xff0c;正是因为多个乐高的零件互相无缝…

MacOS - 启动台多了个『卸载 Adobe Photoshop』

问题描述 今天安装好了 Adobe Ps&#xff0c;但是发现启动台多了个『卸载 Adobe Photoshop』强迫症又犯了&#xff0c;想把它干掉&#xff01; 解决方案 打开访达 - 前往 - 资源库&#xff0c;搜索要卸载的名字就可以看到&#xff0c;然后移除到垃圾筐