GO实现Redis:GO实现Redis协议解析器(2)
- 本文实现Redis的协议层,协议层负责解析指令,然后将指令交给核心database执行
- echo database用来测试协议层的代码
- https://github.com/csgopher/go-redis
RESP协议
RESP是客户端与服务端通信的协议,格式有五种:
正常回复:以“+”开头,以“\r\n”结尾的字符串形式
错误回复:以“-”开头,以“\r\n”结尾的字符串形式
整数:以“:”开头,以“\r\n”结尾的字符串形式
多行字符串:以“$”开头,后跟实际发送字节数,再以“\r\n”开头和结尾
$3\r\nabc\r\n
数组:以“*”开头,后跟成员个数
SET key value
*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
客户端和服务器发送的命令或数据一律以 \r\n (CRLF)作为换行符。
当我们输入*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n这样一串命令,服务端接收到的是如下的命令:
*3\r\n
$3\r\n
SET\r\n
$3\r\n
key\r\n
$5\r\n
value\r\n
interface/resp/conn.go
type Connection interface {Write([]byte) errorGetDBIndex() intSelectDB(int)
}interface/resp/reply.go
type Reply interface {ToBytes() []byte
}
Connection接口:Redis客户端的一个连接
Write:给客户端回复消息
GetDBIndex:Redis有16个DB
Reply接口:响应接口
resp/reply/consts.go
type PongReply struct{}var pongBytes = []byte("+PONG\r\n")func (r *PongReply) ToBytes() []byte {return pongBytes
}var thePongReply = new(PongReply)func MakePongReply() *PongReply {return thePongReply
}type OkReply struct{}var okBytes = []byte("+OK\r\n")func (r *OkReply) ToBytes() []byte {return okBytes
}var theOkReply = new(OkReply)func MakeOkReply() *OkReply {return theOkReply
}var nullBulkBytes = []byte("$-1\r\n")type NullBulkReply struct{}func (r *NullBulkReply) ToBytes() []byte {return nullBulkBytes
}func MakeNullBulkReply() *NullBulkReply {return &NullBulkReply{}
}var emptyMultiBulkBytes = []byte("*0\r\n")type EmptyMultiBulkReply struct{}func (r *EmptyMultiBulkReply) ToBytes() []byte {return emptyMultiBulkBytes
}type NoReply struct{}var noBytes = []byte("")func (r *NoReply) ToBytes() []byte {return noBytes
}
定义五种回复:回复pong,ok,null,空数组,空
resp/reply/reply.go
type ErrorReply interface {Error() stringToBytes() []byte
}
ErrorReply:定义错误接口
resp/reply/errors.go
type UnknownErrReply struct{}var unknownErrBytes = []byte("-Err unknown\r\n")func (r *UnknownErrReply) ToBytes() []byte {return unknownErrBytes
}func (r *UnknownErrReply) Error() string {return "Err unknown"
}type ArgNumErrReply struct {Cmd string
}func (r *ArgNumErrReply) ToBytes() []byte {return []byte("-ERR wrong number of arguments for '" + r.Cmd + "' command\r\n")
}func (r *ArgNumErrReply) Error() string {return "ERR wrong number of arguments for '" + r.Cmd + "' command"
}func MakeArgNumErrReply(cmd string) *ArgNumErrReply {return &ArgNumErrReply{Cmd: cmd,}
}type SyntaxErrReply struct{}var syntaxErrBytes = []byte("-Err syntax error\r\n")
var theSyntaxErrReply = &SyntaxErrReply{}func MakeSyntaxErrReply() *SyntaxErrReply {return theSyntaxErrReply
}func (r *SyntaxErrReply) ToBytes() []byte {return syntaxErrBytes
}func (r *SyntaxErrReply) Error() string {return "Err syntax error"
}type WrongTypeErrReply struct{}var wrongTypeErrBytes = []byte("-WRONGTYPE Operation against a key holding the wrong kind of value\r\n")func (r *WrongTypeErrReply) ToBytes() []byte {return wrongTypeErrBytes
}func (r *WrongTypeErrReply) Error() string {return "WRONGTYPE Operation against a key holding the wrong kind of value"
}type ProtocolErrReply struct {Msg string
}func (r *ProtocolErrReply) ToBytes() []byte {return []byte("-ERR Protocol error: '" + r.Msg + "'\r\n")
}func (r *ProtocolErrReply) Error() string {return "ERR Protocol error: '" + r.Msg
}
errors定义5种错误:UnknownErrReply 未知错误,ArgNumErrReply 参数个数错误,SyntaxErrReply 语法错误,WrongTypeErrReply 数据类型错误,ProtocolErrReply 协议错误
resp/reply/reply.go
var (nullBulkReplyBytes = []byte("$-1")// 协议的结尾CRLF = "\r\n"
)type BulkReply struct {Arg []byte
}func MakeBulkReply(arg []byte) *BulkReply {return &BulkReply{Arg: arg,}
}func (r *BulkReply) ToBytes() []byte {if len(r.Arg) == 0 {return nullBulkReplyBytes}return []byte("$" + strconv.Itoa(len(r.Arg)) + CRLF + string(r.Arg) + CRLF)
}type MultiBulkReply struct {Args [][]byte
}func (r *MultiBulkReply) ToBytes() []byte {argLen := len(r.Args)var buf bytes.Bufferbuf.WriteString("*" + strconv.Itoa(argLen) + CRLF)for _, arg := range r.Args {if arg == nil {buf.WriteString("$-1" + CRLF)} else {buf.WriteString("$" + strconv.Itoa(len(arg)) + CRLF + string(arg) + CRLF)}}return buf.Bytes()
}func MakeMultiBulkReply(args [][]byte) *MultiBulkReply {return &MultiBulkReply{Args: args,}
}type StatusReply struct {Status string
}func MakeStatusReply(status string) *StatusReply {return &StatusReply{Status: status,}
}func (r *StatusReply) ToBytes() []byte {return []byte("+" + r.Status + CRLF)
}type IntReply struct {Code int64
}func MakeIntReply(code int64) *IntReply {return &IntReply{Code: code,}
}func (r *IntReply) ToBytes() []byte {return []byte(":" + strconv.FormatInt(r.Code, 10) + CRLF)
}type StandardErrReply struct {Status string
}func (r *StandardErrReply) ToBytes() []byte {return []byte("-" + r.Status + CRLF)
}func (r *StandardErrReply) Error() string {return r.Status
}func MakeErrReply(status string) *StandardErrReply {return &StandardErrReply{Status: status,}
}func IsErrorReply(reply resp.Reply) bool {return reply.ToBytes()[0] == '-'
}
BulkReply:回复一个字符串
MultiBulkReply:回复字符串数组
StatusReply:状态回复
IntReply:数字回复
StandardErrReply:标准错误回复
IsErrorReply:判断是否为错误回复
ToBytes:将字符串转成RESP协议规定的格式
resp/parser/parser.go
type Payload struct {Data resp.ReplyErr error
}type readState struct {readingMultiLine bool expectedArgsCount int msgType byte args [][]byte bulkLen int64
}func (s *readState) finished() bool {return s.expectedArgsCount > 0 && len(s.args) == s.expectedArgsCount
}func ParseStream(reader io.Reader) <-chan *Payload {ch := make(chan *Payload)go parse0(reader, ch)return ch
}func parse0(reader io.Reader, ch chan<- *Payload) {......
}
Payload结构体:客服端给我们发的数据
- Reply:客户端与服务端互相发的数据都称为Reply
readState结构体:
- readingMultiLine:解析单行还是多行数据
- expectedArgsCount:应该读取的参数个数
- msgType:消息类型
- args:消息内容
- bulkLen:数据长度
finished方法:判断解析是否完成
ParseStream方法:异步解析数据后放入管道,返回管道数据
func readLine(bufReader *bufio.Reader, state *readState) ([]byte, bool, error) {var msg []bytevar err errorif state.bulkLen == 0 {msg, err = bufReader.ReadBytes('\n')if err != nil {return nil, true, err}if len(msg) == 0 || msg[len(msg)-2] != '\r' {return nil, false, errors.New("protocol error: " + string(msg))}} else {msg = make([]byte, state.bulkLen+2)_, err = io.ReadFull(bufReader, msg)if err != nil {return nil, true, err}if len(msg) == 0 || msg[len(msg)-2] != '\r' || msg[len(msg)-1] != '\n' {return nil, false, errors.New("protocol error: " + string(msg))}state.bulkLen = 0}return msg, false, nil
}
readLine:一行一行的读取。读正常的行,以\n分隔。读正文中包含\r\n字符的行时,state.bulkLen加上换行符\r\n(state.bulkLen+2)
func parseMultiBulkHeader(msg []byte, state *readState) error {var err errorvar expectedLine uint64expectedLine, err = strconv.ParseUint(string(msg[1:len(msg)-2]), 10, 32)if err != nil {return errors.New("protocol error: " + string(msg))}if expectedLine == 0 {state.expectedArgsCount = 0return nil} else if expectedLine > 0 {state.msgType = msg[0]state.readingMultiLine = truestate.expectedArgsCount = int(expectedLine)state.args = make([][]byte, 0, expectedLine)return nil} else {return errors.New("protocol error: " + string(msg))}
}func parseBulkHeader(msg []byte, state *readState) error {var err errorstate.bulkLen, err = strconv.ParseInt(string(msg[1:len(msg)-2]), 10, 64)if err != nil {return errors.New("protocol error: " + string(msg))}if state.bulkLen == -1 { // null bulkreturn nil} else if state.bulkLen > 0 {state.msgType = msg[0]state.readingMultiLine = truestate.expectedArgsCount = 1state.args = make([][]byte, 0, 1)return nil} else {return errors.New("protocol error: " + string(msg))}
}
parseMultiBulkHeader:解析数组的头部,设置期望的行数和相关参数。
parseBulkHeader:解析多行字符串的头部。
func parseSingleLineReply(msg []byte) (resp.Reply, error) {str := strings.TrimSuffix(string(msg), "\r\n")var result resp.Replyswitch msg[0] {case '+': // status replyresult = reply.MakeStatusReply(str[1:])case '-': // err replyresult = reply.MakeErrReply(str[1:])case ':': // int replyval, err := strconv.ParseInt(str[1:], 10, 64)if err != nil {return nil, errors.New("protocol error: " + string(msg))}result = reply.MakeIntReply(val)}return result, nil
}func readBody(msg []byte, state *readState) error {line := msg[0 : len(msg)-2]var err errorif line[0] == '$' {// bulk replystate.bulkLen, err = strconv.ParseInt(string(line[1:]), 10, 64)if err != nil {return errors.New("protocol error: " + string(msg))}if state.bulkLen <= 0 { // null bulk in multi bulksstate.args = append(state.args, []byte{})state.bulkLen = 0}} else {state.args = append(state.args, line)}return nil
}
parseSingleLineReply:解析单行命令
readBody:读取多行的命令,如果是开头,设置bulkLen,读取下一行时根据这个+2,不是开头,设置bulkLen,读取下一行时根据这个+2,不是开头,设置bulkLen,读取下一行时根据这个+2,不是开头则直接添加到args
func parse0(reader io.Reader, ch chan<- *Payload) {defer func() {if err := recover(); err != nil {logger.Error(string(debug.Stack()))}}()bufReader := bufio.NewReader(reader)var state readStatevar err errorvar msg []bytefor {var ioErr boolmsg, ioErr, err = readLine(bufReader, &state)if err != nil {if ioErr {ch <- &Payload{Err: err,}close(ch)return}ch <- &Payload{Err: err,}state = readState{}continue}if !state.readingMultiLine {if msg[0] == '*' {// multi bulk replyerr = parseMultiBulkHeader(msg, &state)if err != nil {ch <- &Payload{Err: errors.New("protocol error: " + string(msg)),}state = readState{}continue}if state.expectedArgsCount == 0 {ch <- &Payload{Data: &reply.EmptyMultiBulkReply{},}state = readState{}continue}} else if msg[0] == '$' { // bulk replyerr = parseBulkHeader(msg, &state)if err != nil {ch <- &Payload{Err: errors.New("protocol error: " + string(msg)),}state = readState{} // reset statecontinue}if state.bulkLen == -1 { // null bulk replych <- &Payload{Data: &reply.NullBulkReply{},}state = readState{} // reset statecontinue}} else {// single line replyresult, err := parseSingleLineReply(msg)ch <- &Payload{Data: result,Err: err,}state = readState{} // reset statecontinue}} else {// read bulk replyerr = readBody(msg, &state)if err != nil {ch <- &Payload{Err: errors.New("protocol error: " + string(msg)),}state = readState{} // reset statecontinue}// if sending finishedif state.finished() {var result resp.Replyif state.msgType == '*' {result = reply.MakeMultiBulkReply(state.args)} else if state.msgType == '$' {result = reply.MakeBulkReply(state.args[0])}ch <- &Payload{Data: result,Err: err,}state = readState{}}}}
}
parse0:解析指令,解析完成后通过channel发出去
resp/connection/conn.go
type Connection struct {conn net.ConnwaitingReply wait.Waitmu sync.Mutex // 避免多个协程往客户端中写selectedDB int
}func NewConn(conn net.Conn) *Connection {return &Connection{conn: conn,}
}func (c *Connection) RemoteAddr() net.Addr {return c.conn.RemoteAddr()
}func (c *Connection) Close() error {c.waitingReply.WaitWithTimeout(10 * time.Second)_ = c.conn.Close()return nil
}func (c *Connection) Write(b []byte) error {if len(b) == 0 {return nil}c.mu.Lock()c.waitingReply.Add(1)defer func() {c.waitingReply.Done()c.mu.Unlock()}()_, err := c.conn.Write(b)return err
}func (c *Connection) GetDBIndex() int {return c.selectedDB
}func (c *Connection) SelectDB(dbNum int) {c.selectedDB = dbNum
}
之前写的EchoHandler是用户传过来什么,我们传回去什么。现在要写一个RespHandler来代替EchoHandler,让解析器来解析。RespHandler中要有一个管理客户端连接的结构体Connection。
Connection:客户端连接,在协议层的handler中会用到
resp/handler/handler.go
var (unknownErrReplyBytes = []byte("-ERR unknown\r\n")
)type RespHandler struct {activeConn sync.Mapdb databaseface.Databaseclosing atomic.Boolean
}func MakeHandler() *RespHandler {var db databaseface.Databasedb = database.NewEchoDatabase()return &RespHandler{db: db,}
}func (h *RespHandler) closeClient(client *connection.Connection) {_ = client.Close()h.db.AfterClientClose(client)h.activeConn.Delete(client)
}func (h *RespHandler) Handle(ctx context.Context, conn net.Conn) {if h.closing.Get() {// closing handler refuse new connection_ = conn.Close()}client := connection.NewConn(conn)h.activeConn.Store(client, 1)ch := parser.ParseStream(conn)for payload := range ch {if payload.Err != nil {if payload.Err == io.EOF ||payload.Err == io.ErrUnexpectedEOF ||strings.Contains(payload.Err.Error(), "use of closed network connection") {// connection closedh.closeClient(client)logger.Info("connection closed: " + client.RemoteAddr().String())return}// protocol errerrReply := reply.MakeErrReply(payload.Err.Error())err := client.Write(errReply.ToBytes())if err != nil {h.closeClient(client)logger.Info("connection closed: " + client.RemoteAddr().String())return}continue}if payload.Data == nil {logger.Error("empty payload")continue}r, ok := payload.Data.(*reply.MultiBulkReply)if !ok {logger.Error("require multi bulk reply")continue}result := h.db.Exec(client, r.Args)if result != nil {_ = client.Write(result.ToBytes())} else {_ = client.Write(unknownErrReplyBytes)}}
}func (h *RespHandler) Close() error {logger.Info("handler shutting down...")h.closing.Set(true)// TODO: concurrent waith.activeConn.Range(func(key interface{}, val interface{}) bool {client := key.(*connection.Connection)_ = client.Close()return true})h.db.Close()return nil
}
RespHandler:和之前的echo类似,加了核心层的db.exec执行解析的指令
interface/database/database.go
type CmdLine = [][]bytetype Database interface {Exec(client resp.Connection, args [][]byte) resp.ReplyAfterClientClose(c resp.Connection)Close()
}type DataEntity struct {Data interface{}
}
Exec:核心层的执行
AfterClientClose:关闭之后的善后方法
CmdLine:二维字节数组的指令别名
DataEntity:表示Redis的数据,包括string, list, set等等
database/echo_database.go
type EchoDatabase struct {
}func NewEchoDatabase() *EchoDatabase {return &EchoDatabase{}
}func (e EchoDatabase) Exec(client resp.Connection, args [][]byte) resp.Reply {return reply.MakeMultiBulkReply(args)
}func (e EchoDatabase) AfterClientClose(c resp.Connection) {logger.Info("EchoDatabase AfterClientClose")
}func (e EchoDatabase) Close() {logger.Info("EchoDatabase Close")
}
echo_database:测试协议层
Exec:指令解析后,再使用MakeMultiBulkReply包装一下返回去
main.go
err := tcp.ListenAndServeWithSignal(&tcp.Config{Address: fmt.Sprintf("%s:%d",config.Properties.Bind,config.Properties.Port),},handler.MakeHandler())
if err != nil {logger.Error(err)
}
main改成刚才写的:handler.MakeHandler()
相关文章:
GO实现Redis:GO实现Redis协议解析器(2)
本文实现Redis的协议层,协议层负责解析指令,然后将指令交给核心database执行echo database用来测试协议层的代码https://github.com/csgopher/go-redis RESP协议 RESP是客户端与服务端通信的协议,格式有五种:正常回复࿱…...
Geoserver 发布wmts服务,以及cesium加载发布的wmts服务
WMTS提供了一种采用预定义图块方法发布数字地图服务的标准化解决方案。WMTS弥补了WMS不能提供分块地图的不足。WMS针对提供可定制地图的服务,是一个动态数据或用户定制地图(需结合SLD标准)的理想解决办法。WMTS牺牲了提供定制地图的灵活性&am…...
【微信小程序】selectComponent(#id)失败得到是null分析
小程序中无法像网页中轻易的获取DOM元素,需要依靠 this.selectComponent(#id)this.selectAllComponents(#id) 本文主要针对 this.selectComponent 获取DOM元素失败的原因 下面开始正文 上图为我的业务代码,由图可知,通过for循环遍历渲染ca…...
JVM中引用计数法与可达性分析
目录 概要 如何判断对象已死? 引用计数算法 优点 缺点 举例说明 可达性分析 图例说明 GC Roots的对象包括以下几种 可达性分析回收过程 四大引用 回收方法区 方法区的垃圾收集主要回收两部分内容: 1. 废弃的常量 2. 不再使用的类型。 JVM是…...
JS-对象篇
内容 简单介绍 重点介绍三个 Array,String和JSON 后面这两个不是重点 BOM-浏览器对象模型 DOM-文档对象模式(JS中每个HTML标签都封装成一个DOM对象) Array 和java不同 方式一 JS中是var 变量 new Array()(这个变量名后面没有[]这个标记&…...
【Unity】创建一个自己的AR安卓程序
目录1 环境配置2 下载官方提供的AR Starter工程3 AR Starter工程中的包以及打包设置3.1 Package Manager3.2 Player Settings4 创建一个新的AR场景5 AR场景中的物体6 在unity中运行AR场景7 在AR场景的基础上添加自己的想法7.1 修改Cube的旋转速度/方向7.2 将Cube替换为其他物体…...
游戏平台商店化的功能特点
帮助用户高效的获取游戏以及游戏相关内容是游戏平台的核心,基于这个需求在平台功能的设计上与其他类型产品也有着类似的思路。商店模式的特点诸如百货商店、超市、书店以及其他类型的商店,都会根据推荐、分类两个特点提供商品。 如果把游戏比作书籍&…...
最新前端面试知识点总结-2023(3w+字,长篇幅)
2023-前端面试知识点总结面试题总览javascript相关一、js 代码的常用优化手段二、es5 构造函数与继承三、new 一个对象的过程四、防抖与节流五、promise/A规范概述六、实现一个柯里函数封装七、事件队列八、微任务是哪些宏任务是哪些九、执行js代码时,同步任务、微任…...
离线安装ffmpeg
linux离线安装ffmpeg 获取安装包:[ffmpeg-release](Index of /releases (ffmpeg.org)) 下载最新版本,ffmpeg-4.4.tar.gz 然后传送到服务器上,解压安装 # 解压 tar -zxvf ffmpeg-4.4.tar.gz# 安装 cd ffmpeg-4.4 ./configure --enable-sha…...
位置编码Positional Encoding
位置编码Positional Encoding1.Transformers中的PE2.什么是Transformer位置编码2.1.表格型2.2.相对位置的关系-函数型3.为什么可以表示相对距离?4.其他参考内容全来自于网络总结。 其他参考1其他参考2 1.Transformers中的PE 摘抄自这里。 公式是初中生都看的懂, …...
Java异步注解@Async详解
一、Async注解 Async的作用就是异步处理任务。 在方法上添加Async,表示此方法是异步方法;在类上添加Async,表示类中的所有方法都是异步方法;使用此注解的类,必须是Spring管理的类;需要在启动类或配置类中…...
macOS Big Sur 11.7.5 (20G1225) 正式版 ISO、PKG、DMG、IPSW 下载
本站提供的 macOS Big Sur 软件包,既可以拖拽到 Applications(应用程序)下直接安装,也可以制作启动 U 盘安装,或者在虚拟机中启动安装。 2023 年 3 月 27 日 (北京时间 28 日凌晨),…...
硬件语言Verilog HDL牛客刷题day02 组合逻辑部分
1.VL11 4位数值比较器电路 1.题目: 某4位数值比较器的功能表如下。请用Verilog语言采用门级描述方式,实现此4位数值比较器。 2.解题代码: timescale 1ns/1nsmodule comparator_4(input [3:0] A ,input [3:0] B ,output …...
【LM401】ADC采集代码解读
本文主要实现基于LM401模组,,测试ADC低功耗采集,详细解析代码基于计算方式 对于小白理解ADC有更详细的理解 【LM401】ADC采集代码解读1. 单片机ADC与DAC简单理解2. 模组ADC通道介绍3. ADC初始化4. 采集值的计算5.测试结果硬件基于易智联的LM401的LoRa模组…...
CSDN 编程竞赛四十期题解
竞赛总览 CSDN 编程竞赛四十期:比赛详情 (csdn.net) 竞赛题解 题目1、小鱼的航程 有一只小鱼,它上午游泳150公里,下午游泳100公里,晚上和周末都休息(实行双休日)。假设从周x(1<x<7)开…...
【TypeScript学习之路】泛型
【TypeScript学习之路】泛型 文章目录【TypeScript学习之路】泛型写在前面前言一、认识泛型1.1 什么是泛型1.2 泛型函数的使用二、泛型接口与泛型类2.1 泛型接口2.2 泛型类三、泛型约束写在前面 🤗这里是前端程序员小张! 🌻人海茫茫ÿ…...
数据分析学习项目:东京奥运会跳水评论分析
“中国跳水梦之队” ————有关东京奥运会跳水评论分析 导语 第32届夏季奥林匹克运动会于2021年07月23日-2021年08月08日在日本东京举办。 四年一届的奥运会可以说是世界瞩目的盛会,奥运健儿们在赛场上的精神风貌不只是代表了他们自身的运动精神,更昭…...
Winform/Csharp中使用Linq的Where条件筛选、Select字段映射(左外连接并设置无匹配时默认值)、OrderBy(排序并自定义排序规则)
场景 Java8新特性-Stream对集合进行操作的常用API: Java8新特性-Stream对集合进行操作的常用API_streamapi操作集合_霸道流氓气质的博客-CSDN博客 上面讲的是在Java中使用Stream中对集合的常用操作。 在C#中Linq是有对应的类似的api。 完整和详细的用法可自行查…...
Linux-常用的Shell命令
文章目录前言常用的Shell命令文件和目录管理查看文件、目录信息查看文件内容查看文件类型查找文件查找内容查看目录大小创建文件删除文件拷贝文件移动文件创建目录删除目录拷贝目录压缩文件解压文件路径相关操作目录切换显示当前路径用户、用户组管理创建用户删除用户创建用户组…...
Go语言基础:数组定义及循环遍历
前言 大家好,我是沐风晓月,本文go语言入门-掌握go语言函数收录于《go语言学习专栏》专栏,此专栏带你从零开始学习go语言,持续更新中,欢迎点赞收藏。 🏠个人主页:我是沐风晓月 🧑个人…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...
