前言
基于 Web 的小说转换工具,支持将 txt 文件转换为 epub、mobi、azw3 等电子书格式
其实这个项目是今年八月做的,一直没有分享。
之前在找txt电子书转换EPUB工具的时候,很多都是有广告的,在GitHub上找到一个go开发的命令行工具:kaf-cli ,挺好用的,不过每次都要输入命令有点麻烦,所以我就用go-gin做成了网站,方便时候。
发在了读书群里,也有很多人在使用。
不过最近这个网站已经让我的服务器不堪重负,只能被迫暂停了。
本文简单分享一下这个网站,作为2025年的收尾吧~
这个项目还催生了 Alpine.js 实现国际化的功能。
本项目是从 ystyle/kaf-cli fork 而来,在保留原有命令行功能的基础上,新增了 Web 可视化界面,提供更便捷的使用体验。
项目地址: https://github.com/Deali-Axy/ebook-generator
主要特性
🌐 Web 功能
- 📁 文件上传:支持 txt 文件上传,最大 50MB
- 🔄 格式转换:支持转换为 epub、mobi、azw3 格式
- 📊 实时进度:通过 SSE 实时查看转换进度
- 📥 文件下载:转换完成后可下载电子书文件
- 🗑️ 自动清理:支持手动清理临时文件
- 📖 API 文档:集成 Swagger UI,方便调试
- 🎨 可视化界面:简洁易用的 HTML 界面
📚 转换功能
- 自动识别书名和章节
- 自动识别字符编码(解决中文乱码)
- 自定义章节标题识别规则
- 自定义卷的标题识别规则
- 自动给章节正文生成加粗居中的标题
- 段落自动识别和缩进
- 支持生成 Orly 风格的书籍封面
- 知轩藏书格式文件名自动提取书名和作者
- 超快速转换(epub 格式生成 300 章/s 以上速度)
截图
主页面

转换进度和下载

web代码
简单贴一些代码吧,其实我还做了登录注册功能的,不过有bug,前端就没搭配加上去了~
// main 启动Web服务
func main() {
// 设置Gin模式
if os.Getenv("GIN_MODE") == "" {
gin.SetMode(gin.DebugMode)
}
// 初始化服务管理器
serviceManager, err := initServiceManager()
if err != nil {
log.Fatal("初始化服务管理器失败:", err)
}
// 启动所有服务
if err := serviceManager.Start(); err != nil {
log.Fatal("启动服务失败:", err)
}
// 设置优雅关闭
defer func() {
if err := serviceManager.Stop(); err != nil {
log.Printf("停止服务时出错: %v", err)
}
}()
// 初始化Web服务相关组件
initWebServices(serviceManager)
// 创建Gin引擎
r := gin.Default()
// 初始化SEO服务
seoService := initSEOService()
// 添加中间件
r.Use(middleware.CORS())
r.Use(middleware.Logger())
r.Use(middleware.Recovery())
r.Use(middleware.AddSecurityHeaders())
r.Use(middleware.AddCacheHeaders())
r.Use(middleware.SEOMiddleware(seoService))
// 设置文件上传大小限制 (50MB)
r.MaxMultipartMemory = 50 << 20
// API路由组
api := r.Group("/api")
{
// 基础转换功能
api.POST("/upload", handlers.UploadFile)
api.POST("/convert", handlers.ConvertBook)
api.GET("/status/:taskId", handlers.GetTaskStatus)
api.GET("/download/:fileId", handlers.DownloadFile)
api.DELETE("/cleanup/:taskId", handlers.CleanupTask)
api.GET("/events/:taskId", handlers.GetTaskEvents)
// 用户认证相关路由
auth := api.Group("/auth")
{
auth.POST("/register", handlers.Register)
auth.POST("/login", handlers.Login)
auth.GET("/profile", handlers.AuthMiddleware(), handlers.GetProfile)
auth.PUT("/profile", handlers.AuthMiddleware(), handlers.UpdateProfile)
auth.POST("/logout", handlers.AuthMiddleware(), handlers.Logout)
auth.POST("/refresh", handlers.AuthMiddleware(), handlers.RefreshToken)
auth.PUT("/password", handlers.AuthMiddleware(), handlers.ChangePassword)
}
// 转换历史相关路由(需要认证)
history := api.Group("/history")
history.Use(handlers.AuthMiddleware())
{
history.GET("", handlers.GetHistories)
history.GET("/stats", handlers.GetHistoryStats)
history.DELETE("/:id", handlers.DeleteHistory)
}
// 转换预设相关路由(需要认证)
presets := api.Group("/presets")
presets.Use(handlers.AuthMiddleware())
{
presets.POST("", handlers.CreatePreset)
presets.GET("", handlers.GetPresets)
presets.GET("/:id", handlers.GetPreset)
presets.PUT("/:id", handlers.UpdatePreset)
presets.DELETE("/:id", handlers.DeletePreset)
}
// 批量转换相关路由(需要认证)
batch := api.Group("/batch")
batch.Use(handlers.AuthMiddleware())
{
batch.POST("/convert", handlers.BatchConvert)
}
}
// 集成Swagger文档
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
// 静态文件服务
r.Static("/static", "./web/static")
r.StaticFile("/", "./web/static/index.html")
r.StaticFile("/demo", "./web/static/index.html")
// SEO相关文件 - 动态生成
r.GET("/sitemap.xml", handlers.GenerateSitemap)
r.GET("/robots.txt", handlers.GenerateRobotsTxt)
// SEO状态监控
seo := r.Group("/seo")
{
seo.GET("/status", handlers.GetSEOStatus)
}
// 健康检查接口
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
})
// 启动服务
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("服务启动在端口: %s", port)
log.Printf("Swagger文档地址: http://localhost:%s/swagger/index.html", port)
if err := r.Run(":" + port); err != nil {
log.Fatal("启动服务失败:", err)
}
}
小结
就这样吧,时间不早了,祝大家新年快乐吧~
程序设计实验室
微信公众号