Gin 源码概览 - 路由
本文基于gin 1.1 源码解读
https://github.com/gin-gonic/gin/archive/refs/tags/v1.1.zip
1. 注册路由
我们先来看一段gin代码,来看看最终得到的一颗路由树长啥样
func TestGinDocExp(t *testing.T) {engine := gin.Default()engine.GET("/api/user", func(context *gin.Context) {fmt.Println("api user")})engine.GET("/api/user/info/a", func(context *gin.Context) {fmt.Println("api user info a")})engine.GET("/api/user/information", func(context *gin.Context) {fmt.Println("api user information")})engine.Run()
}
看起来像是一颗前缀树,我们后面再仔细深入源码
1.1 gin.Default
gin.Default()
返回了一个Engine
结构体指针,同时添加了2个函数,Logger
和 Recovery
。
func Default() *Engine {engine := New()engine.Use(Logger(), Recovery())return engine
}
New
方法中初始化了 Engine
,同时还初始化了一个RouterGroup
结构体,并将Engine
赋值给RouterGroup
,相当于互相套用了
func New() *Engine {engine := &Engine{RouterGroup: RouterGroup{Handlers: nil, // 业务handle,也可以是中间件basePath: "/", // 根地址root: true, // 根路由},RedirectTrailingSlash: true,RedirectFixedPath: false,HandleMethodNotAllowed: false,ForwardedByClientIP: true,trees: make(methodTrees, 0, 9), // 路由树}engine.RouterGroup.engine = engine// 这里使用pool来池化Context,主要目的是减少频繁创建和销毁对象带来的内存分配和垃圾回收的开销engine.pool.New = func() interface{} { return engine.allocateContext()}return engine
}
在执行完gin.Defualt后,gin的内容,里面已经默认初始化了2个handles
1.2 Engine.Get
在Get方法内部,最终都是调用到了group.handle
方法,包括其他的POST,DELETE等
- group.handle
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {// 获取绝对路径, 将group中的地址和当前地址进行组合absolutePath := group.calculateAbsolutePath(relativePath)// 将group中的handles(Logger和Recovery)和当前的handles合并handlers = group.combineHandlers(handlers) // 核心在这里,将handles添加到路由树中group.engine.addRoute(httpMethod, absolutePath, handlers)return group.returnObj()
}
- group.engine.addRoute
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {// 通过遍历trees中的内容,判断是否在这之前,同一个http方法下已经添加过路由// trees 是一个[]methodTree 切片,有2个字段,method 表示方法,root 表示当前节点,是一个node结构体root := engine.trees.get(method) // 如果这是第一个路由则创建一个新的节点if root == nil {root = new(node)engine.trees = append(engine.trees, methodTree{method: method, root: root})}root.addRoute(path, handlers)
}
- root.addRoute(path, handlers)
这里的代码比较多,其实大家可以简单的认为就是将handle
和path
进行判断,注意这里的path
不是一个完整的注册api,而是去掉了公共前缀后的那部分字符串。
func (n *node) addRoute(path string, handlers HandlersChain) {fullPath := path // 存储当前节点的完整路径n.priority++ // 优先级自增1numParams := countParams(path) // 统计当前节点的动态参数个数// non-empty tree,非空路由树if len(n.path) > 0 || len(n.children) > 0 {walk:for {// Update maxParams of the current node// 统计当前节点及子节点中最大数量的参数个数// maxParams 可以快速判断当前节点及其子树是否能匹配包含一定数量参数的路径,从而加速匹配过程。(GPT)if numParams > n.maxParams {n.maxParams = numParams}// Find the longest common prefix.// This also implies that the common prefix contains no ':' or '*'// since the existing key can't contain those chars.// 计算最长公共前缀i := 0max := min(len(path), len(n.path))for i < max && path[i] == n.path[i] {i++}// Split edge,当前路径和节点路径只有部分重叠,且存在分歧if i < len(n.path) {// 将后缀部分创建一个新的节点,作为当前节点的子节点。child := node{path: n.path[i:],wildChild: n.wildChild,indices: n.indices,children: n.children,handlers: n.handlers,priority: n.priority - 1,}// Update maxParams (max of all children)// 路径分裂时,确保当前节点及子节点中是有最大的maxParamsfor i := range child.children {if child.children[i].maxParams > child.maxParams {child.maxParams = child.children[i].maxParams}}n.children = []*node{&child}// []byte for proper unicode char conversion, see #65n.indices = string([]byte{n.path[i]})n.path = path[:i]n.handlers = niln.wildChild = false}// Make new node a child of this nodeif i < len(path) {path = path[i:] // 获取当前节点中非公共前缀的部分// 当前节点是一个动态路径// TODO 还需要好好研究一下if n.wildChild {n = n.children[0]n.priority++// Update maxParams of the child nodeif numParams > n.maxParams {n.maxParams = numParams}numParams--// Check if the wildcard matches// 确保新路径的动态部分与已有动态路径不会冲突。if len(path) >= len(n.path) && n.path == path[:len(n.path)] {// check for longer wildcard, e.g. :name and :namesif len(n.path) >= len(path) || path[len(n.path)] == '/' {continue walk}}panic("path segment '" + path +"' conflicts with existing wildcard '" + n.path +"' in path '" + fullPath + "'")}// 获取非公共前缀部分的第一个字符c := path[0]// slash after param// 当前节点是动态参数节点,且最后一个字符时/,同时当前节点还只有一个字节if n.nType == param && c == '/' && len(n.children) == 1 {n = n.children[0]n.priority++continue walk}// Check if a child with the next path byte existsfor i := 0; i < len(n.indices); i++ {if c == n.indices[i] {i = n.incrementChildPrio(i)n = n.children[i]continue walk}}// Otherwise insert itif c != ':' && c != '*' {// []byte for proper unicode char conversion, see #65n.indices += string([]byte{c})child := &node{maxParams: numParams,}n.children = append(n.children, child)// 增加当前子节点的优先级n.incrementChildPrio(len(n.indices) - 1)n = child}n.insertChild(numParams, path, fullPath, handlers)return} else if i == len(path) { // Make node a (in-path) leafif n.handlers != nil {panic("handlers are already registered for path ''" + fullPath + "'")}n.handlers = handlers}return}} else { // Empty treen.insertChild(numParams, path, fullPath, handlers)n.nType = root}
}
1.3 Engine.Run
func (engine *Engine) Run(addr ...string) (err error) {defer func() { debugPrintError(err) }()// 处理一下web的监听地址address := resolveAddress(addr)debugPrint("Listening and serving HTTP on %s\n", address)// 最终还是使用http来启动了一个web服务err = http.ListenAndServe(address, engine)return
}
2. 路由查找
在上一篇文章中介绍了,http 的web部分的实现,http.ListenAndServe(address, engine)
在接收到请求后,最终会调用engine
的ServeHTTP
方法
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {// 从context池中获取一个Contextc := engine.pool.Get().(*Context) // 对Context进行一些初始值操作,比如赋值w和reqc.writermem.reset(w)c.Request = reqc.reset()// 最终进入这个方法来处理请求engine.handleHTTPRequest(c)// 处理结束后将Conetxt放回池中,供下一次使用engine.pool.Put(c)
}
2.1 engine.handleHTTPRequest()
func (engine *Engine) handleHTTPRequest(context *Context) {httpMethod := context.Request.Method // 当前客户端请求的http 方法path := context.Request.URL.Path // 查询客户端请求的完整请求地址// Find root of the tree for the given HTTP methodt := engine.treesfor i, tl := 0, len(t); i < tl; i++ {if t[i].method == httpMethod {root := t[i].root// Find route in treehandlers, params, tsr := root.getValue(path, context.Params)if handlers != nil {context.handlers = handlers // 所有的handles 请求对象context.Params = params // 路径参数,例如/api/user/:id , 此时id就是一个路径参数context.Next() // 执行所有的handles方法context.writermem.WriteHeaderNow()return}}}// 这里客户端请求的地址没有匹配上,同时检测请求的方法有没有注册,若没有注册过则提供请求方法错误if engine.HandleMethodNotAllowed {for _, tree := range engine.trees {if tree.method != httpMethod {if handlers, _, _ := tree.root.getValue(path, nil); handlers != nil {context.handlers = engine.allNoMethodserveError(context, 405, default405Body)return}}}}// 路由地址没有找到context.handlers = engine.allNoRouteserveError(context, 404, default404Body)
}
2.2 context.Next()
这个方法在注册中间件的使用会使用的较为频繁
func (c *Context) Next() {// 初始化的时候index 是 -1c.index++s := int8(len(c.handlers))for ; c.index < s; c.index++ {c.handlers[c.index](c) // 依次执行注册的handles}
}
我们来看一段gin 是执行中间件的流程
func TestGinMdls(t *testing.T) {engine := gin.Default()engine.Use(func(ctx *gin.Context) {fmt.Println("请求过来了")// 这里可以做一些横向操作,比如处理用户身份,cors等ctx.Next()fmt.Println("返回响应")})engine.GET("/index", func(context *gin.Context) {fmt.Println("index")})engine.Run()
}
curl http://127.0.0.1:8080/index
通过响应结果我们可以分析出,请求过来时,先执行了Use中注册的中间件,然后用户调用ctx.Next() 可以执行下一个handle,也就是用户注册的/index方法的handle
相关文章:
Gin 源码概览 - 路由
本文基于gin 1.1 源码解读 https://github.com/gin-gonic/gin/archive/refs/tags/v1.1.zip 1. 注册路由 我们先来看一段gin代码,来看看最终得到的一颗路由树长啥样 func TestGinDocExp(t *testing.T) {engine : gin.Default()engine.GET("/api/user", f…...
【计算机网络】传输层协议TCP与UDP
传输层 传输层位于OSI七层网络模型的第四层,主要负责端到端通信,可靠性保障(TCP),流量控制(TCP),拥塞控制(TCP),数据分段与分组,多路复用与解复用等,通过TCP与UDP协议实现…...
iOS UIScrollView的一个特性
1如果UIScrollView 的contentSize.height > scrollView.bounds.size.height - scrollView.contentInset.top - scrollView.contentInset.bottom , 则scrollView就可以滚动,否则无法滚动 并且最大的滚动范围就是 contentSize.height - ( s…...
Docker 实现MySQL 主从复制
一、拉取镜像 docker pull mysql:5.7相关命令: 查看镜像:docker images 二、启动镜像 启动mysql01、02容器: docker run -d -p 3310:3306 -v /root/mysql/node-1/config:/etc/mysql/ -v /root/mysql/node-1/data:/var/lib/mysql -e MYS…...
python爬虫入门(实践)
python爬虫入门(实践) 一、对目标网站进行分析 二、博客爬取 获取博客所有h2标题的路由 确定目标,查看源码 代码实现 """ 获取博客所有h2标题的路由 """url "http://www.crazyant.net"import re…...
通过Ukey或者OTP动态口令实现windows安全登录
通过 安当SLA(System Login Agent)实现Windows安全登录认证,是一种基于双因素认证(2FA)的解决方案,旨在提升 Windows 系统的登录安全性。以下是详细的实现方法和步骤: 1. 安当SLA的核心功能 安…...
C 语言雏启:擘画代码乾坤,谛观编程奥宇之初瞰
大家好啊,我是小象٩(๑ω๑)۶ 我的博客:Xiao Xiangζั͡ޓއއ 很高兴见到大家,希望能够和大家一起交流学习,共同进步。* 这一课主要是让大家初步了解C语言,了解我们的开发环境,main函数,库…...
【Linux系统编程】—— 进程替换及其在操作系统中的应用与实现
文章目录 什么是进程替换?进程替换当中的接口单进程替换多进程的替换详解exec接口execlexeclpexecv 前言: 本篇博客将深入探讨进程替换的概念及其在操作系统中的作用。我们将介绍进程替换的基本原理,探讨操作系统如何通过进程的切换来实现任务…...
“裸奔”时代下该如何保护网络隐私
网络隐私的保护之道 引言 在这个信息爆炸的时代,网络已经深入到我们生活的每一个角落。你是否曾想过,在享受这些便利时,你的个人隐私正面临着严峻的挑战?网络隐私的现状警示着我们,信息泄露的事件屡见不鲜࿰…...
分类问题(二元,多元逻辑回归,费歇尔判别分析)spss实操
分类模型: 二分类和多分类: 对于二分类模型 ,我们将介绍逻辑回归和Fisher线性判别分析两种分类算法; 对于多分类模型,我们将简单介绍Spss中的多分类线性判别分析和多分类逻辑回归的操作步骤 二分类: 基于广义线性模型&#x…...
推荐一个开源的轻量级任务调度器!TaskScheduler!
大家好,我是麦鸽。 这次推荐一款轻量级的嵌入式任务调度器,目前已经有1.4K的star,这个项目比较轻量化,只有5个源文件,可以作为学习的一个开源项目。 核心文件 项目概述: 这是一个轻量级的协作式多任务处理&…...
Spring 核心技术解析【纯干货版】- IV:Spring 切面编程模块 Spring-Aop 模块精讲
随着软件开发技术的不断进步,面向切面编程(AOP)作为一种重要的编程思想,已经在现代开发中占据了重要地位。它通过将横切逻辑从业务逻辑中分离出来,使得代码更加清晰、易于维护。Spring AOP 作为 Spring 框架的核心模块…...
STM32之FreeRTOS开发介绍(十九)
STM32F407 系列文章 - freertos(十九) 目录 前言 一、简述 二、开源网址 三、原理及功能特性 1.原理简介 2.功能介绍 1.任务调度 2.任务管理 3.中断管理 4.消息队列 3.特点说明 4.优缺点 四、参考书籍 五、实现方式 总结 前言 FreeRTOS是…...
2024年美赛C题评委文章及O奖论文解读 | AI工具如何影响数学建模?从评委和O奖论文出发-O奖论文做对了什么?
模型假设仅仅是简单陈述吗?允许AI的使用是否降低了比赛难度?还在依赖机器学习的模型吗?处理题目的方法有哪些?O奖论文的优点在哪里? 本文调研了当年赛题的评委文章和O奖论文,这些问题都会在文章中一一解答…...
第14篇:从入门到精通:掌握python上下文管理器
第14篇:上下文管理器 内容简介 本篇文章将深入探讨Python中的上下文管理器(Context Manager)。您将了解上下文管理器的概念与用途,学习如何实现自定义的上下文管理器,以及如何使用contextlib模块来简化上下文管理器的…...
cuda从零开始手搓PB神经网络
cuda实现PB神经网络 基于上一篇的矩阵点乘,实现了矩阵的加减乘除、函数调用等。并且复用之前元编程里面写的梯度下降、Adam、NAdam优化方法。实现PB神经网络如下: #ifndef __BP_NETWORK_HPP__ #define __BP_NETWORK_HPP__ #include "matrix.hpp&quo…...
Java 大视界 -- Java 大数据物联网应用:数据处理与设备管理(八)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...
大模型之三十三- 开源Melo 语音合成
大模型之三十三- 开源Melo 语音合成 文本到语音(TTS)系统从基于基础音素的模型演变成复杂的端到端神经方法,这种方法可以直接将文本转换为语音。这一变革得益于深度学习的进步和计算能力的提升,已经在语音的自然度、韵律控制和跨语言能力方面取得了重大进展 。现代TTS系统…...
全同态加密理论、生态现状与未来展望(上)
《全同态加密理论、生态现状与未来展望》系列由lynndell2010gmail.com和mutourend2010gmail.com整理原创发布,分为上中下三个系列: 全同态加密理论、生态现状与未来展望(上):专注于介绍全同态加密理论知识。全同态加密…...
cursor重构谷粒商城02——30分钟构建图书管理系统【cursor使用教程番外篇】
前言:这个系列将使用最前沿的cursor作为辅助编程工具,来快速开发一些基础的编程项目。目的是为了在真实项目中,帮助初级程序员快速进阶,以最快的速度,效率,快速进阶到中高阶程序员。 本项目将基于谷粒商城…...
提升大语言模型的三大策略
1.概述 随着大语言模型(LLMs)在技术和应用上的不断发展,它们已经深刻地改变了我们与计算机的互动方式。从文本生成到语言理解,LLMs的应用几乎涵盖了各个行业。然而,尽管这些模型已展现出令人印象深刻的能力,…...
Ubuntu 24.04 LTS 安装 Docker Desktop
Docker 简介 Docker 简介和安装Ubuntu上学习使用Docker的详细入门教程Docker 快速入门Ubuntu版(1h速通) Docker 安装 参考 How to Install Docker on Ubuntu 24.04: Step-by-Step Guide。 更新系统和安装依赖 在终端中运行以下命令以确保系统更新并…...
mysql查看binlog日志
mysql 配置、查看binlog日志: 示例为MySQL8.0 1、 检查binlog开启状态 SHOW VARIABLES LIKE ‘log_bin’; 如果未开启,修改配置my.ini 开启日志 安装目录配置my.ini(mysql8在data目录) log-binmysql-bin(开启日志并指定日志前缀ÿ…...
2. Flink分区策略
一. Flink分区策略概述 Flink任务在执行过程中,一个流(stream)包含一个或多个分区(Stream partition),TaskManager中的一个slot的SubTask就是一个stream partition(流分区)。 Flink分区之间进行数据传递模式有两种。 1. one-to-one模式 数据不需要重新…...
Qt 5.14.2 学习记录 —— 십칠 窗口和菜单
文章目录 1、Qt窗口2、菜单栏设置快捷键添加子菜单添加分割线和菜单图标 3、工具栏 QToolBar4、状态栏 QStatusBar5、浮动窗口 QDockWidget 1、Qt窗口 QWidget,即控件,是窗口的一部分。在界面中创建控件组成界面时,Qt自动生成了窗口…...
微信小程序中实现背景图片完全覆盖显示,可以通过设置CSS样式来实现
wxml页面代码 <view class"beijing"></view>wxss样式代码 /* pages/beiJing/beiJing.wxss */ .beijing {background-image: url("https://www.qipa250.com/qipa.jpg");/* 定位:绝对定位 */position: absolute;/* 上下左右都定位到…...
亲测有效!如何快速实现 PostgreSQL 数据迁移到 时序数据库TDengine
小T导读:本篇文章是“2024,我想和 TDengine 谈谈”征文活动的优秀投稿之一,作者从数据库运维的角度出发,分享了利用 TDengine Cloud 提供的迁移工具,从 PostgreSQL 数据库到 TDengine 进行数据迁移的完整实践过程。文章…...
中国综合算力指数(2024年)报告汇总PDF洞察(附原数据表)
原文链接: https://tecdat.cn/?p39061 在全球算力因数字化技术发展而竞争加剧,我国积极推进算力发展并将综合算力作为数字经济核心驱动力的背景下,该报告对我国综合算力进行研究。 中国算力大会发布的《中国综合算力指数(2024年…...
51c~ONNX~合集1
我自己的原文哦~ https://blog.51cto.com/whaosoft/11608027 一、使用Pytorch进行简单的自定义图像分类 ~ONNX 推理 图像分类是计算机视觉中的一项基本任务,涉及训练模型将图像分类为预定义类别。本文中,我们将探讨如何使用 PyTorch 构建一个简单的自定…...
线下陪玩系统架构与功能分析
2015工作至今,10年资深全栈工程师,CTO,擅长带团队、攻克各种技术难题、研发各类软件产品,我的代码态度:代码虐我千百遍,我待代码如初恋,我的工作态度:极致,责任ÿ…...
海康工业相机的应用部署不是简简单单!?
作者:SkyXZ CSDN:SkyXZ~-CSDN博客 博客园:SkyXZ - 博客园 笔者使用的设备及环境:WSL2-Ubuntu22.04MV-CS016-10UC 不会吧?不会吧?不会还有人拿到海康工业相机还是一脸懵叭?不会还有人…...
SAP POC 项目完工进度 - 收入确认方式【工程制造行业】【新准则下工程项目收入确认】
1. SAP POC收入确认基础概念 1.1 定义与原则 SAP POC(Percentage of Completion)收入确认方式是一种基于项目完工进度来确认收入的方法。其核心原则是根据项目实际完成的工作量或成本投入占预计总工作量或总成本的比例,来确定当期应确认的收…...
【Elasticsearch 】 聚合分析:聚合概述
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编…...
【算法】二分
二分 1.二分查找1.在排序数组中查找元素的第一个和最后一个位置2.牛可乐和魔法封印3.A-B 数对4.烦恼的高考志愿 2.二分答案1.木材加工2.砍树3.跳石头 1.二分查找 当我们的解具有二段性(根据最终答案所在的位置判断是否具有二段性)时,就可以使…...
如何将自己本地项目开源到github上?
环境: LLMB项目 问题描述: 如何将自己本地项目开源到github上? 解决方案: 步骤 1: 准备本地项目 确保项目整洁 确认所有的文件都在合适的位置,并且项目的 README.md 文件已经完善。检查是否有敏感信息࿰…...
编辑器Vim基本模式和指令 --【Linux基础开发工具】
文章目录 一、编辑器Vim 键盘布局二、Linux编辑器-vim使用三、vim的基本概念正常/普通/命令模式(Normal mode)插入模式(Insert mode)末行模式(last line mode) 四、vim的基本操作五、vim正常模式命令集插入模式从插入模式切换为命令模式移动光标删除文字复制替换撤销上一次操作…...
Scade 表达式 - 使用索引的迭代器
Scade 表达式中的 map, fold, mapfold,会对输入数组参数中的元素逐个作处理,不需要数组元素的索引信息。若在处理数组元素时,需要数组元素相应的索引信息,则可使用迭代器算子 mapi, foldi, mapfoldi。 mapi 算子 mapi 算子的行为…...
K8s学习
Kubernetes 1. Kubernetes介绍 1.1 应用部署方式演变 在部署应用程序的方式上,主要经历了三个时代: 传统部署:互联网早期,会直接将应用程序部署在物理机上 优点:简单,不需要其它技术的参与 缺点…...
面试--你的数据库中密码是如何存储的?
文章目录 三种分类使用 MD5 加密存储加盐存储Base64 编码:常见的对称加密算法常见的非对称加密算法https 传输加密 在开发中需要存储用户的密码,这个密码一定是加密存储的,如果是明文存储那么如果数据库被攻击了,密码就泄露了。 我们要对数据…...
微服务学习-快速搭建
1. 速通版 1.1. git clone 拉取项目代码,导入 idea 中 git clone icoolkj-microservices-code: 致力于搭建微服务架构平台 1.2. git checkout v1.0.1版本 链接地址:icoolkj-microservices-code 标签 - Gitee.com 2. 项目服务结构 3. 实现重点步骤 …...
兼职全职招聘系统架构与功能分析
2015工作至今,10年资深全栈工程师,CTO,擅长带团队、攻克各种技术难题、研发各类软件产品,我的代码态度:代码虐我千百遍,我待代码如初恋,我的工作态度:极致,责任ÿ…...
【云岚到家】-day03-门户缓存实现实战
【云岚到家】-day03-门户缓存实现实战 1.定时任务更新缓存 1.1 搭建XXL-JOB环境 1.1.1 分布式调度平台XXL-JOB介绍 对于开通区域列表的缓存数据需要由定时任务每天凌晨更新缓存,如何实现定时任务呢? 1.使用jdk提供的Timer定时器 示例代码如下…...
Ubuntu 24.04 LTS 开启 SMB 服务,并通过 windows 访问
Ubuntu 24.04 LTS 背景资料 Ubuntu服务器折腾集Ubuntu linux 文件权限Ubuntu 空闲硬盘挂载到 文件管理器的 other locations Ubuntu开启samba和window共享文件 Ubuntu 配置 SMB 服务 安装 Samba 确保 Samba 已安装。如果未安装,运行以下命令进行安装ÿ…...
“AI人工智能内容辅助创作平台:让创意不再“卡壳”
在如今这个信息爆炸的时代,内容创作成了每个人的“必修课”。无论是自媒体大V、文案策划,还是普通学生写作文,大家都会遇到一个让人抓狂的问题——“创意枯竭”。有时候,脑袋里空空如也,一个字都写不出来,那…...
mac 安装 node
brew versions node // 安装 node brew versions node14 // 安装指定版本 卸载node: sudo npm uninstall npm -g sudo rm -rf /usr/local/lib/node /usr/local/lib/node_modules /var/db/receipts/org.nodejs.* sudo rm -rf /usr/local/include/node /Users/$USER/.npm su…...
VUE之Router使用及工作模式
1、路由的使用 【两个注意点】 1)路由组件通常放在pages 或 views文件夹,一般组件通常放在components文件夹。 2)通过点击导航,视觉效果上"消失"了的路由组件,默认是被"卸载"掉的,需要的时候再去挂载。 // 创建一个路由器,并暴露出去// 第一步:…...
day25_HTML
今日内容 零、 复习昨日 一、HTML 零、 复习昨日 一、Web开发 前端 HTML ,页面展现CSS , 样式JS (JavaScript) , 动起来 二、HTML 2.1 HTML概念 网页,是网站中的一个页面,通常是网页是构成网站的基本元素,是承载各种网站应用的平台。通俗…...
(开源)基于Django+Yolov8+Tensorflow的智能鸟类识别平台
1 项目简介(开源地址在文章结尾) 系统旨在为了帮助鸟类爱好者、学者、动物保护协会等群体更好的了解和保护鸟类动物。用户群体可以通过平台采集野外鸟类的保护动物照片和视频,甄别分类、实况分析鸟类保护动物,与全世界各地的用户&…...
【AI日记】25.01.20
【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】 AI kaggle 比赛:Forecasting Sticker Sales 读书 书名:自由宪章阅读原因:作者哈耶克,诺贝尔经济学奖得主,之前读过他的 《通往奴役…...
基于机器学习的用户健康风险分类及预测分析
完整源码项目包获取→点击文章末尾名片! 背景描述 在这个日益注重健康与体能的时代,健身已成为许多人追求健康生活的重要组成部分。 本数据集包含若干健身房会员的详细信息,包括年龄、性别、体重、身高、心率、锻炼类型、身体脂肪比例等多项关…...