Go实战训练之Web Server 与路由树

news2025/1/13 19:59:53

Server & 路由树

Server

Web 核心

对于一个 Web 框架,至少要提供三个抽象:

  • Server:代表服务器的抽象
  • Context:表示上下文的抽象
  • 路由树

Server

从特性上来说,至少要提供三部分功能:

  • 生命周期控制:即启动,关闭,生命周期的回调;
  • 路由注册接口:提供路由注册功能;
  • 作为 http 包到 Web 框架的桥梁

http.Handler接口

http 包暴露了一个接口 Handler。
它是我们引入自定义 Web 框架相关的连接点。

// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

接口的定义

版本一:只组合 http.Handler

//Server file
type Server interface {
	http.Handler
}
//Server_test file
func TestServer(t *testing.T) {
	var s Server
	http.ListenAndServe("localhost:8080", s)
}

优点:

  • 用户在使用的时候只需要调用 http.ListenAndServe 就可以
  • 和 HTTPS 协议完全无缝衔接
  • 极简设计

缺点:

  • 难以控制生命周期,并且在控制生命周期的时候增加回调支持
  • 缺乏控制力:如果将来希望支持优雅退出的功能,将难以支持

版本二:组合 http.Handler 并且增加 Start 方法。

//Server file
type Server interface {
	http.Handler
	Start(addr string) error
}
//Server_test file
func TestServer(t *testing.T) {
	var s Server
	http.ListenAndServe("localhost:8080", s)

	s.Start(":8080")
	//Start 方法可以不需要 addr 参数,那么在创建实现类的时候传入地址就可以。
}

优点:

  • Server 既可以当成普通的 http.Handler 来使用,又可以作为一个独立的实体,拥有自己的管理生命周期的能力
  • 完全的控制,可以为所欲为

缺点:

  • 如果用户不希望使用 ListenAndServeTLS,那么 Server 需要提供 HTTPS 的支持

版本一和版本二都直接耦合了 Go 自带的 http 包,如果我们希望切换为 fasthttp 或者类似的 http 包,则会非常困难。

HTTPServer 实现 Server

// Server_test file
type HTTPServer struct {}

var _Server = &HTTPServer{}

func (s *HTTPServer) ServerHTTP(writer http.ResponseWriter, request *http.Request)  {
	
}

func (s *HTTPServer) Start(addr string) error {
	return http.ListenAndServe(addr, s)
}

该实现直接使用 http.ListenAndServe 来启动,后续可以根据需要替换为:

  • 内部创建 http.Server 来启动
  • 使用 http.Serve 来启动,换取更大的灵活性,如将端口监听和服务器启动分离等

ServeHTTP 则是我们整个 Web 框架的核心入口。我们将在整个方法内部完成:

  • Context 构建
  • 路由匹配
  • 执行业务逻辑

路由树

实现路由树得步骤:

  • 全静态匹配
  • 支持通配符匹配
  • 支持参数路由

不同框架路由树的实现:

  • Beego:
    • ControllerRegister:类似于容器,放着所有的路由树
      • 路由树是按照 HTTP method 来组织的,例如 GET 方法会对应有一棵路由树
    • Tree:它代表的就是路由树,在 Beego 里
      面,一棵路由树被看做是由子树组成的
    • leafInfo:代表叶子节点
    • 树的设计并没有采用 children 式来定义,而是采用递归式的定义,即一棵树是由根节点 + 子树构成

  • Gin:
    • methodTrees:也就是路由树也是按照 HTTP 方法组织的,例如 GET 会有一棵路由树
    • methodTree:定义了单棵树。树在 Gin 里面采用的是 children 的定义方式,即树由节点构成(注意对比 Beego)
    • node:代表树上的一个节点,里面维持住了 children,即子节点。同时有 nodeType 和 wildChild 来标记一些特殊节点
    • gin是利用路由的公共前缀来构造路由树

路由树的设计总结:

归根结底就是设计一颗多叉树。

  • 同时我们按照 HTTP 方法来组织路由树,每个 HTTP 方法一棵树
  • 节点维持住自己的子节点

静态匹配

所谓的静态匹配,就是路径的每一段都必须严格相等。

接口设计

关键类型:

  • router:维持住了所有的路由树,它是整个路由注册和查找的总入口。router 里面维护了一个 map,是按照 HTTP 方法来组织路由树的
  • node:代表的是节点。它里面有一个 children 的 map 结构,使用 map 结构是为了快速查找到子节点
type router struct {
	// trees 是按照 HTTP 方法来组织的
	// 如 GET => *node
	trees map[string]*node
}

type node struct {
	path string
	//children 子节点
	//子节点的 path => *node
	children map[string]*node
	//handler 命中路由之后的逻辑
	handler HandlerFunc
}

func newRouter() router {
	return router{
		trees: map[string]*node{},
	}
}

用简化版的 TDD,即:

  1. 定义 API
  2. 定义测试
  3. 添加测试用例
  4. 实现,并且确保实现能够通过测试用例
  5. 重复 3-4 直到考虑了所有的场景
  6. 重复步骤 1-5

全局静态匹配

  • 方法路由设计
{
	method: http.MethodGet,
	path:   "/",
},
{
	method: http.MethodGet,
	path:   "/user",
},
{
	method: http.MethodGet,
	path:   "/user/home",
},
{
	method: http.MethodGet,
	path:   "/order/detail",
},
{
	method: http.MethodPost,
	path:   "/order/create",
},
{
	method: http.MethodPost,
	path:   "/login",
},
  • 非法用例进行判断
//非法样例
r = newRouter()

//空字符串
assert.PanicsWithValue(t, "web: 路由是空字符串", func() {
	r.addRoute(http.MethodGet, "", mockHandler)
})

//前导没有 /
assert.PanicsWithValue(t, "web: 路由必须以 / 开头", func() {
	r.addRoute(http.MethodGet, "a/b/c", mockHandler)
})
//后缀有 /
assert.PanicsWithValue(t, "web: 路由不能以 / 结尾", func() {
	r.addRoute(http.MethodGet, "/a/b/c/", mockHandler)
})

r.addRoute(http.MethodGet, "/", mockHandler)
// 根节点重复注册
assert.PanicsWithValue(t, "web: 路由重复注册", func() {
	r.addRoute(http.MethodGet, "/", mockHandler)
})
// 普通节点重复注册
r.addRoute(http.MethodGet, "/a/b/c", mockHandler)
assert.PanicsWithValue(t, http.MethodGet, func() {
	r.addRoute(http.MethodGet, "/a/b/c", mockHandler)
})

// 多个
assert.PanicsWithValue(t, "web: 非法路由。不允许使用 //a/b, /a//b 之类的路由, [/a//b]", func() {
	r.addRoute(http.MethodGet, "/a//b", mockHandler)
})
assert.PanicsWithValue(t, "web: 非法路由。不允许使用 //a/b, /a//b 之类的路由, [//a/b]", func() {
	r.addRoute(http.MethodGet, "//a/b", mockHandler)
})
  • 添加路由
// addRoute 注册路由
// method 是 HTTP 方法
//	path 必须以 / 开始并且结尾不能有 /,中间也不允许有连续的 /
func (r *router) addRoute(method string, path string, handler HandlerFunc) {
	// 空字符串判断
	if path == "" {
		panic("web: 路由是空字符串")
	}
	// 必须以 / 为开头判断
	if path[0] != '/' {
		panic("web: 路由必须以 / 开头")
	}
	// 结尾不能有 /
	if path != "/" && path[len(path)-1] == '/' {
		panic("web: 不能以 / 为结尾")
	}

	root, ok := r.trees[method]
	//这是一个全新的 HTTP 方法,我们必须创建根节点
	if !ok {
		root = &node{
			path: "/",
		}
		r.trees[method] = root
	}
	// 路径冲突
	if path == "/" {
		if root.handler != nil {
			panic("web: 路径冲突")
		}
		root.handler = handler
		return
	}

	// 对路由进行切割
	segs := strings.Split(path[1:], "/")

	//开始进行处理
	for _, s := range segs {
		// 对空路径进行判断
		if s == "" {
			panic(fmt.Sprintf("web: 非法路由。不允许使用 //a/b, /a//b 之类的路由, [%s]", path))
		}
		root = root.childrenOfCreate(s)
	}
	if root.handler != nil {
		panic(fmt.Sprintf("web: 路由冲突[%s]", path))
	}
}
  • 查找或者创建一个子节点
//子节点不存在则创建一个子节点
func (n *node) childrenOfCreate(path string) *node {
	if n.children == nil {
		n.children = make(map[string]*node)
	}
	child, ok := n.children[path]
	if !ok {
		child = &node{path: path}
		n.children[path] = child
	}
	return child
}
  • 对 method 进行校验

    定义为私有的 addRoute 即可实现:

    • 用户只能通过 Get 或者 Post 方法来注册,那么可以确保 method 参数永远都是对的
    • addRoute 在接口里面是私有的,限制了用户将无法实现 Server。实际上如果用户想要实现 Server,就约等于自己实现一个 Web 框架了。

path 之所以会有那么强的约束,是因为我希望用户写出来的代码风格要一致,就是注册的路由有些人喜欢加 /,有些人不喜欢加 /。

  • 路由查找
// test_file
// 待测试路由
testRoutes := []struct {
	method string
	path   string
}{
	{
		method: http.MethodGet,
		path:   "/",
	},
	{
		method: http.MethodGet,
			path:   "/user",
	},
	{
		method: http.MethodGet,
		path:   "/order/create",
	},
	{
		method: http.MethodGet,
		path:   "/user/*/home",
	},
	{
		method: http.MethodGet,
		path:   "/order/*",
	},
}
// 测试样例
testCase := []struct {
	name     string
	method   string
	path     string
	found    bool
	wantNode *node
}{
	{
		name:   "method not found",
		method: http.MethodHead,
	},
	{
		name:   "path not found",
		method: http.MethodGet,
		path:   "/abc",
	},
	{
		name:   "root",
		method: http.MethodGet,
		found:  true,
		path:   "/",
		wantNode: &node{
			path:    "/",
			handler: mockHandler,
		},
	},
	{
		name:   "user",
		method: http.MethodGet,
		found:  true,
		path:   "/user",
		wantNode: &node{
			path:    "user",
			handler: mockHandler,
		},
	},
	{
		name:   "no handler",
		method: http.MethodPost,
		found:  true,
		path:   "/order",
		wantNode: &node{
			path: "order",
		},
	},
	{
		name:   "two layer",
		method: http.MethodPost,
		found:  true,
		path:   "/order/create",
		wantNode: &node{
			path:    "create",
			handler: mockHandler,
		},
	},
}
  • 实现代码
// 路由查找实现
func (r *router) findRoute(method string, path string) (*node, bool) {
	root, ok := r.trees[method]
	if !ok {
		return nil, false
	}

	if path == "/" {
		return root, true
	}

	segs := strings.Split(strings.Trim(path, "/"), "/")
	for _, s := range segs {
		root, ok = root.childof(s)
		if !ok {
			return nil, false
		}
	}
	return root, true
}

//判断是否存在子节点
func (n *node) childof(path string) (*node, bool) {
	if n.children == nil {
		return nil, false
	}
	res, ok := n.children[path]
	return res, ok
}
  • Server 集成 router

这种情况下,用户只能使用 NewHTTPServer 来创建服务器实例。

如果考虑到用户可能自己 s := &HTTPServer 引起 panic,那么可以将 HTTPServer
做成私有的,即改名为 httpServer。

type HTTPServer struct{ router }

func NewHTTPServer() *HTTPServer {
	return &HTTPServer{
		router: newRouter(),
	}
}
func (s *HTTPServer) server(ctx *Context) {
	n, ok := s.findRoute(ctx.Req.Method, ctx.Req.URL.Path)
	if !ok || n.handler == nil {
		ctx.Resp.WriteHeader(404)
		ctx.Resp.Write([]byte("Not Found"))
		return
	}
	n.handler(ctx)
}
  • 通配符匹配

    所谓通配符匹配,是指用 * 号来表达匹配任何路径。要考虑几个问题:

    • 如果路径是 /a/b/c 能不能命中 /a/* 路由?
    • 如果注册了两个路由 /user/123/home/user/*/*。那么输入路径 /user/123/detail 能不能命中 /user/*/*?

    这两个都是理论上可以,但是不应该命中。

    • 从实现的角度来说,其实并不难。
    • 从用户的角度来说,他们不应该设计这种路由。给用户自由,但是也要限制不良实践。
    • 后者要求的是一种可回溯的路由匹配,即发现 /user/123/home 匹配不上之后要回溯回去 /user/* 进一步查找,典型的投入大产出低的特性。

对 node 进行修改:

// node 代表路由树的节点
// 路由树的匹配顺序为:
// 1、静态完全匹配
// 2、通配符匹配
// 这是不回溯匹配
type node struct {
	path string
	//children 子节点
	//子节点的 path => *node
	children map[string]*node
	//handler 命中路由之后的逻辑
	handler HandlerFunc

	//通配符 * 表示的节点,任意匹配
	starChild *node
}

对 childof 进行修改:

func (n *node) childof(path string) (*node, bool) {
	if n.children == nil {
		return n.starChild, n.starChild != nil
	}
	res, ok := n.children[path]
	if !ok {
		return n.starChild, n.starChild != nil
	}
	return res, ok
}

对 childOrCreate 进行修改:

func (n *node) childrenOfCreate(path string) *node {
	if path == "*" {
		if n.children == nil {
			n.starChild = &node{path: "*"}
		}
	}
	if n.children == nil {
		n.children = make(map[string]*node)
	}
	child, ok := n.children[path]
	if !ok {
		child = &node{path: path}
		n.children[path] = child
	}
	return child
}

增加 addRouter 测试用例

{
	method: http.MethodGet,
	path:   "/order/*",
},
{
	method: http.MethodGet,
	path:   "/*",
},
{
	method: http.MethodGet,
	path:   "/*/*",
},
{
	method: http.MethodGet,
	path:   "/*/abc",
},
{
	method: http.MethodGet,
	path:   "/*/abc/**",
},

增加 findRoute 测试用例

// 通配符匹配
{
  // 命中/order/*
  name:   "star match",
  method: http.MethodPost,
  path:   "/order/delete",
  found:  true,
  wantNode: &node{
	path:    "*",
	handler: mockHandler,
	},
},
{
	//命中中间的通配符
    // user/*/home
	name:   "start in middle",
	method: http.MethodGet,
	path:   "/user/Tom/home",
	wantNode: &node{
	  path:    "home",
	  handler: mockHandler,
		},
},
{
	// 比/order/* 多一段
	name:   "overflow",
	method: http.MethodPost,
	path:   "/order/delete/123",
},
  • 参数路径

    所谓参数路径,就是指在路径中带上参数,同时这些参数对应的值可以被业务取出来使用。

    例如:/user/:id ,如果输入路径 /user/123,那么会命中这个路由,并且 id = 123

    那么要考虑:

    • 允不允许同样的参数路径和通配符匹配一起注册?例如同时注册 /user/*/user/:id

    可以,但是没必要,用户也不应该设计这种路由。

对 node 进行修改

type HandlerFunc func(ctx *Context)

// node 代表路由树的节点
// 路由树的匹配顺序为:
// 1、静态完全匹配
// 2、通配符匹配
// 这是不回溯匹配
type node struct {
	path string
	//children 子节点
	//子节点的 path => *node
	children map[string]*node
	//handler 命中路由之后的逻辑
	handler HandlerFunc

	//通配符 * 表示的节点,任意匹配
	starChild *node
	//路径参数
	paramchild *node
}

对 childrenOfCreate 进行变更

func (n *node) childrenOfCreate(path string) *node {
	if path == "*" {
		if n.children == nil {
			n.starChild = &node{path: "*"}
		}
	}

	if path == "*" {
		if n.paramchild != nil {
			panic(fmt.Sprintf("web: 非法路由,已有路径参数路由。不允许同时注册通配符路由和参数路由 [%s]", path))
		}
		if n.starChild == nil {
			n.starChild = &node{path: path}
		}
		return n.starChild
	}

	// 以 : 开头,我们一般认为是参数路由
	if path[0] == ':' {
		if n.starChild != nil {
			panic(fmt.Sprintf("web: 非法路由,已有路径参数路由,不允许同时注册通配符路由和参数路由 [%s]", path))
		}
		if n.paramchild != nil {
			if n.paramchild.path != path {
				panic(fmt.Sprintf("web: 路由冲突, 参数路由冲突,已有 %s,新注册 %s", &n.paramchild.path, path))
			}
		} else {
			n.paramchild = &node{path: path}
		}
		return n.paramchild
	}
	if n.children == nil {
		n.children = make(map[string]*node)
	}
	child, ok := n.children[path]
	if !ok {
		child = &node{path: path}
		n.children[path] = child
	}
	return child
}

对 childof 进行修改

// childof 返回子节点
// 第一个返回值为 *node 是命中的节点
// 第二个返回值为 bool 代表是否命中参数值
// 第三个返回值为 bool 代表是否命中
func (n *node) childof(path string) (*node, bool, bool) {
	if n.children == nil {
		if n.paramchild != nil {
			return n.paramchild, true, true
		}
		return n.starChild, true, n.starChild != nil
	}
	res, ok := n.children[path]
	if !ok {
		if n.paramchild != nil {
			return n.paramchild, true, true
		}
		return n.starChild, false, n.starChild != nil
	}
	return res, true, true
}

获取路径参数

// 修改Context文件
type Context struct {
	Req        *http.Request
	Resp       http.ResponseWriter
	PathParams map[string]string
}
//新增 matchInfo 表示参数信息
type matchInfo struct {
	n          *node
	pathParams map[string]string
}
// 对 findRoute 进行修改
func (r *router) findRoute1(method string, path string) (*matchInfo, bool) {
	root, ok := r.trees[method]
	if !ok {
		return nil, false
	}

	if path == "/" {
		return &matchInfo{n: root}, true
	}

	segs := strings.Split(strings.Trim(path, "/"), "/")
	mi := &matchInfo{}
	for _, s := range segs {
		var matchParam bool
		root, matchParam, ok = root.childOf1(s)
		if !ok {
			return nil, false
		}
		if matchParam {
			mi.addValue(root.path[1:], s)
		}
	}
	mi.n = root
	return mi, true
}

/user/:id,输入路径 /user/123,那么就相当于把 id = 123 这样一个键值对放进去了 mi (matchInfo) 里面。

路由树总结

  • 已经注册了的路由,无法被覆盖,例如 /user/home 注册两次,会冲突
  • path 必须以 / 开始并且结尾不能有 /,中间也不允许有连续的 /
  • 不能在同一个位置注册不同的参数路由,例如 /user/:id/user/:name 冲突
  • 不能在同一个位置同时注册通配符路由和参数路由,例如 /user/:id/user/* 冲突
  • 同名路径参数,在路由匹配的时候,值会被覆盖,例如 /user/:id/abc/:id,那么 /user/123/abc/456 最终 id = 456

路由树疑问

Q: 为什么在注册路由用 panic?

A: 俗话说,遇事不决用 error。为什么注册路由的过程我们有一大堆 panic ?

这个地方确实可以考虑返回 error。例如 Get 方法,但是这要求用户必须处理返回的 error。

从另外一个角度来说,用户必须要注册完路由,才能启动 HTTPServer。那么我们就可以采用 panic,因为启动之前就代表应用还没运行。

Q:路由树是线程安全的吗?

A:显然不是线程安全的。

我们要求用户必须要注册完路由才能启动 HTTPServer。而正常的用法都是在启动之前依次注册路由,不存在并发场景。

至于运行期间动态注册路由,没必要支持。这是典型的为了解决 1% 的问题,引入 99% 的代码。

总结

为了尽最大可能方便各位同学能够电脑上进行调试和提交代码,我将我自己的写文章时的代码提交至 Github仓库当中。

如果大家对我所写代码有修改或者优化的话,欢迎大家提交 优化后的代码

最后欢迎大家 Follow Github 作者。

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

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

相关文章

基于SSM的宠物领养平台(有报告)。Javaee项目。ssm项目。

演示视频: 基于SSM的宠物领养平台(有报告)。Javaee项目。ssm项目。 项目介绍: 采用M(model)V(view)C(controller)三层体系结构,通过Spring Spri…

《自动机理论、语言和计算导论》阅读笔记:p215-p351

《自动机理论、语言和计算导论》学习第 11 天,p215-p351总结,总计 37 页。 一、技术总结 1.constrained problem 2.Fermat’s lats theorem Fermat’s Last Theorem states that no three positive integers a, b and c satisfy the equation a^n b…

【数据结构(邓俊辉)学习笔记】列表01——从向量到列表

文章目录 0.概述1. 从向量到列表1.1 从静态到动态1.2 从向量到列表1.3 从秩到位置1.4 列表 2. 接口2.1 列表节点2.1.1 ADT接口2.1.2 ListNode模板类 2.2 列表2.2.1 ADT接口2.2.2 List模板类 0.概述 学习了向量,再介绍下列表。先介绍下列表里的概念和语义&#xff0…

C++ | Leetcode C++题解之第66题加一

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> plusOne(vector<int>& digits) {int n digits.size();for (int i n - 1; i > 0; --i) {if (digits[i] ! 9) {digits[i];for (int j i 1; j < n; j) {digits[j] 0;}return …

平平科技工作室-Python-超级玛丽

一.准备图片 放在文件夹取名为images 二.准备一些音频和文字格式 放在文件夹media 三.编写代码 import sys, os sys.path.append(os.getcwd()) # coding:UTF-8 import pygame,sys import os from pygame.locals import* import time pygame.init() # 设置一个长为1250,宽为…

Python | Leetcode Python题解之第65题有效数字

题目&#xff1a; 题解&#xff1a; from enum import Enumclass Solution:def isNumber(self, s: str) -> bool:State Enum("State", ["STATE_INITIAL","STATE_INT_SIGN","STATE_INTEGER","STATE_POINT","STATE_…

Redis-三主三从集群搭建

正式搭建之前&#xff0c;注意事项&#xff08;坑&#xff09;提前放到最开始&#xff0c;也可以出问题回来看&#xff0c; &#xff08;1&#xff09;第二步中最好将配置文件中的logfile自定义一个目录&#xff0c;以便于在第五步中启动出错的时候迅速定位错误。 &#xff0…

DS高阶:图论算法经典应用

一、最小生成树&#xff08;无向图&#xff09; 在了解最小生成树算法之前&#xff0c;我们首先要先了解以下的准则&#xff1a; 连通图中的每一棵生成树&#xff0c;都是原图的一个极大无环子图&#xff0c;即&#xff1a;从其中删去任何一条边&#xff0c;生成树就不在连通&a…

如何低成本创建个人网站?

目录 前言 网站源代码 虚拟主机或服务器 域名注册或免费二级域名 域名解析 上传源代码压缩包 添加刚刚的域名 成功搭建 失败的解决方案 结语 前言 很多小白都非常想拥有自己的网站&#xff0c;但很多人虽然有了自己的源代码但苦于不知道怎么将其变成所有人都能够访…

全自动预混料饲料生产线,轻松生产发酵饲料

随着人们对健康饮食的日益重视&#xff0c;发酵饲料机作为一种新X的养殖设备&#xff0c;逐渐受到了广大养殖户的青睐。全自动预混料饲料生产线不仅提高了饲料的营养价值&#xff0c;还大大缩短了饲料的发酵时间&#xff0c;为养殖户带来了可观的经济效益。 发酵饲料加工机械…

通过符号程序搜索提升prompt工程

原文地址&#xff1a;supercharging-prompt-engineering-via-symbolic-program-search 通过自动探索​​大量提示变体来找到更好的提示 2024 年 4 月 22 日 众所周知&#xff0c;LLMs的成功在很大程度上仍然取决于我们用正确的指导和例子来提示他们的能力。随着新一代LLMs变得越…

「C++ STL篇 0-0」string类的使用

目录 〇、概念 1. string类是什么&#xff1f; 2. string类的官方文档 3. 导入string类 一、string类的构造函数 0. 全部构造函数 1. 常用的四个构造函数 2. 可能用到的构造函数 拓1&#xff1a;npos 二、赋值运算符重载 1. 三个赋值运算符重载函数 2. 使用赋值运算符重载函数…

最新SpringBoot项目地方废物回收机构管理系统

采用技术 最新SpringBoot项目地方废物回收机构管理系统的设计与实现~ 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBootMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 页面展示效果 登录页面 后端管理员 管理员首页 员工管理 设…

机器学习批量服务模式优化指南

原文地址&#xff1a;optimizing-machine-learning-a-practitioners-guide-to-effective-batch-serving-patterns 2024 年 4 月 15 日 简介 在机器学习和数据分析中&#xff0c;模型服务模式的战略实施对于在生产环境中部署和操作人工智能模型起着至关重要的作用。其中&…

软考之零碎片段记录(二十九)+复习巩固(十七、十八)

学习 1. 后缀式&#xff08;逆波兰式&#xff09; 2. c/c语言编译 类型检查是语义分析 词法分析。分析单词。如单词的字符拼写等语法分析。分析句子。如标点符号、括号位置等语言上的错误语义分析。分析运算符、运算对象类型是否合法 3. java语言特质 即时编译堆空间分配j…

Linux服务器常用命令总结

view查找日志关键词 注意日志级别&#xff0c;回车后等一会儿&#xff0c;因为文件可能比较大加载完需要时间 当内容显示出来后&#xff0c;使用“/关键词”搜索 回车就能搜到&#xff0c;n表示查找下一个&#xff0c;N表示查找上一个 find 查找 find Family -name book …

文本嵌入的隐私风险:从嵌入向量重建原始文本的探索

随着大型语言模型&#xff08;LLMs&#xff09;的广泛应用&#xff0c;文本嵌入技术在语义相似性编码、搜索、聚类和分类等方面发挥着重要作用。然而&#xff0c;文本嵌入所蕴含的隐私风险尚未得到充分探讨。研究提出了一种控制生成的方法&#xff0c;通过迭代修正和重新嵌入文…

jsp校园商城派送系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 校园商城派送系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统采用serlvetdaobean mvc 模式&#xff0c;系统主要采用B/S模式 开发。开发环境为TOMCAT7.0,Myeclipse8.…

第12章 消息服务 ❤❤❤❤

第12章 消息服务 12.1 JMS_ActiveMQ1. 简介2. ActiveMQ安装Linux安装命令问题1:网页访问不了问题2: 修改密码 3. 整合SpringBoot3.1 依赖3.2 配置3.3 JmsComponent 组件3.4 测试 12.2 AMQP_RabbitMQ1. 简介2. RabbitMQ2.1 Erlang环境安装(略)2.2 安装RabbitMQ(❤❤❤❤)2.3 启动…

FreeRTOS学习——FreeR TOS队列(下)

本篇文章记录我学习FreeRTOS的队列的相关知识&#xff0c;在此记录分享一下&#xff0c;希望我的分享对你有所帮助。 FreeRTOS学习——FreeRTOS队列&#xff08;上&#xff09;-CSDN博客 一、FreeRTOS队列的创建 &#xff08;一&#xff09;、函数原型 在使用队列之前必须先创…