AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝
在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。
2.embed介绍 🎯
Go 1.16 引入了革命性的 embed
包,彻底改变了静态资源管理的模式。下面简单介绍一下embed包的用法,后面出文章详细介绍。
2.1. 嵌入单个文件
package mainimport (_ "embed""fmt"
)//go:embed version.txt
var version string // 自动推断为字符串类型func main() {fmt.Println("App Version:", version)
}
2.2. 嵌入二进制文件
//go:embed logo.png
var logoBytes []byte // 适用于二进制文件func saveLogo() error {return os.WriteFile("logo_copy.png", logoBytes, 0644)
}
2.3. 嵌入文件集合
//go:embed templates/*.html
var templateFS embed.FS // 文件系统接口func loadTemplates() {// 遍历嵌入的HTML模板dirEntries, _ := templateFS.ReadDir("templates")for _, entry := range dirEntries {if !entry.IsDir() {data, _ := templateFS.ReadFile("templates/" + entry.Name())fmt.Printf("Loaded template: %s (%d bytes)\n", entry.Name(), len(data))}}
}
3.embed改造 🛠️
3.1创建resources文件夹
在根目录下创建resources
文件夹,然后创建static
文件夹,用于存放前端文件。我们将resources
整体作为文件系统嵌入,后续有其它需要嵌入的资源可以统一在resources
处理。
resources和staic按自己喜好起,能区分即可。
需要运行npm build
将dist文件夹内文件复制到static目录中
3.2 创建assets.go
创建assets.go
,用于全局存储嵌入的资源文件
// assets/assets.gopackage assetsimport "embed"var Resources embed.FS
3.2 嵌入resources资源文件
修改main.go
// main.gopackage mainimport ("embed""github.com/ciclebyte/aibookmark/assets""github.com/ciclebyte/aibookmark/cmd"
)//go:embed resources
var resources embed.FSfunc main() {assets.Resources = resourcescmd.Execute()
}
- 需要引入embed包
- 将resources交由assets管理,否则后面使用容易出现循环引用。
3.2 修改gin前端资源处理方式
func NewServer(db *gorm.DB) *Server {server := &Server{db: db}router := gin.New()// 配置中间件router.Use(gin.Logger())router.Use(gin.Recovery())// 禁用重定向router.RedirectTrailingSlash = falserouter.RedirectFixedPath = falserouter.HandleMethodNotAllowed = true// 打印嵌入资源文件结构fmt.Println("=== 嵌入资源文件结构 ===")fs.WalkDir(assets.Resources, ".", func(path string, d fs.DirEntry, err error) error {if err != nil {fmt.Printf("访问路径 %s 时出错: %v\n", path, err)return err}if d.IsDir() {fmt.Printf("目录: %s\n", path)} else {fmt.Printf("文件: %s\n", path)}return nil})fmt.Println("=====================")// 配置CORSrouter.Use(func(c *gin.Context) {c.Writer.Header().Set("Access-Control-Allow-Origin", "*")c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")if c.Request.Method == "OPTIONS" {c.AbortWithStatus(204)return}c.Next()})// 添加请求日志中间件router.Use(func(c *gin.Context) {// 开始时间start := time.Now()path := c.Request.URL.Pathmethod := c.Request.Method// 处理请求c.Next()// 结束时间end := time.Now()latency := end.Sub(start)// 获取状态码statusCode := c.Writer.Status()// 打印请求日志fmt.Printf("[%s] %s %s %d %v\n", method, path, c.ClientIP(), statusCode, latency)})staticFS, _ := fs.Sub(assets.Resources, "resources/static")fmt.Println("=== 静态资源文件结构 ===")fs.WalkDir(staticFS, ".", func(path string, d fs.DirEntry, err error) error {if err != nil {fmt.Printf("访问路径 %s 时出错: %v\n", path, err)return err}if d.IsDir() {fmt.Printf("目录: %s\n", path)} else {fmt.Printf("文件: %s\n", path)}return nil})fmt.Println("=====================")// 设置静态文件服务router.StaticFS("/assets", http.FS(staticFS))// 添加一个处理 favicon 的路由router.GET("/favicon.ico", func(c *gin.Context) {c.FileFromFS("/favicon.ico", http.FS(staticFS))})// 读取 index.html 内容indexContent, err := fs.ReadFile(staticFS, "index.html")if err != nil {fmt.Printf("读取 index.html 失败: %v\n", err)} else {fmt.Printf("成功读取 index.html,长度: %d\n", len(indexContent))}// 添加根路径处理router.GET("/", func(c *gin.Context) {fmt.Printf("处理根路径请求: %s\n", c.Request.URL.Path)fmt.Printf("请求头: %v\n", c.Request.Header)// 设置响应头c.Header("Content-Type", "text/html; charset=utf-8")c.Header("Cache-Control", "no-cache, no-store, must-revalidate")c.Header("Pragma", "no-cache")c.Header("Expires", "0")// 直接写入响应c.Writer.WriteHeader(200)c.Writer.Write(indexContent)})// API路由分组api := router.Group("/api"){category := api.Group("/categories"){category.POST("", server.createCategory)category.GET("", server.listCategories)category.GET("/all", server.getAllCategories)category.GET("/:id", server.getCategory)category.PUT("/:id", server.updateCategory)category.DELETE("/:id", server.deleteCategory)}bookmark := api.Group("/bookmarks"){bookmark.POST("", server.createBookmark)bookmark.GET("", server.listBookmarks)bookmark.GET("/:id", server.getBookmark)bookmark.PUT("/:id", server.updateBookmark)bookmark.DELETE("/:id", server.deleteBookmark)bookmark.POST("/ai", server.createAIBookmark)bookmark.POST("/import", server.importBookmarks)bookmark.POST("/export", server.exportBookmarks)}}// 添加Swagger路由router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))// SPA 前端路由兜底router.NoRoute(func(c *gin.Context) {// 只对 GET 请求兜底if c.Request.Method == "GET" {fmt.Printf("处理前端路由请求: %s\n", c.Request.URL.Path)// 检查是否是静态资源请求if strings.HasPrefix(c.Request.URL.Path, "/assets/") {// 获取文件扩展名ext := filepath.Ext(c.Request.URL.Path)contentType := "application/octet-stream"// 根据扩展名设置 Content-Typeswitch ext {case ".js":contentType = "application/javascript"case ".css":contentType = "text/css"case ".html":contentType = "text/html; charset=utf-8"case ".json":contentType = "application/json"case ".png":contentType = "image/png"case ".jpg", ".jpeg":contentType = "image/jpeg"case ".svg":contentType = "image/svg+xml"case ".ico":contentType = "image/x-icon"}c.Header("Content-Type", contentType)c.FileFromFS(c.Request.URL.Path[1:], http.FS(staticFS))return}// 其他请求返回 index.htmlc.Header("Content-Type", "text/html; charset=utf-8")c.Header("Cache-Control", "no-cache, no-store, must-revalidate")c.Header("Pragma", "no-cache")c.Header("Expires", "0")c.Writer.WriteHeader(200)c.Writer.Write(indexContent)} else {fmt.Printf("404 Not Found: %s %s\n", c.Request.Method, c.Request.URL.Path)c.Status(404)}})server.router = routerreturn server
}
4.启动
我们还是先启动后端服务
go run main.go serve
但是这次我们无需再启动前端服务,可以直接通过后端服务进行访问
往期系列
- Ai书签管理工具开发全记录(一):项目总览与技术蓝图
- Ai书签管理工具开发全记录(二):项目基础框架搭建
- AI书签管理工具开发全记录(三):配置及数据系统设计
- AI书签管理工具开发全记录(四):日志系统设计与实现
- AI书签管理工具开发全记录(五):后端服务搭建与API实现
- AI书签管理工具开发全记录(六):前端管理基础框框搭建 Vue3+Element Plus
- AI书签管理工具开发全记录(七):页面编写与接口对接
- AI书签管理工具开发全记录(八):Ai创建书签功能实现
- AI书签管理工具开发全记录(九):用户端页面集成与展示
- AI书签管理工具开发全记录(十):命令行中结合ai高效添加书签
- AI书签管理工具开发全记录(十一):MCP集成
- AI书签管理工具开发全记录(十二):MCP集成查询
- AI书签管理工具开发全记录(十三):TUI基本框架搭建
- AI书签管理工具开发全记录(十四):TUI基本界面完善
- AI书签管理工具开发全记录(十五):TUI基本逻辑实现与数据展示
- AI书签管理工具开发全记录(十六):Sun-Panel接口分析
- AI书签管理工具开发全记录(十七):Sun-Panel书签同步实现
- AI书签管理工具开发全记录(十八):书签导入导出
相关文章:

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...

企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...

均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...

Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...

python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...