Go语言服务端应用骨架实现方案

Go语言服务端应用骨架实现方案 我的新项目面向所有想要编写HTML5网络应用程序的开发者。

GitHub图标 GitHub

头像

geosoft1/ssas

Go语言的服务器端应用程序骨架。通过在GitHub上创建账户来为geosoft1/ssas开发做出贡献。

基础服务器端网络应用程序具有以下功能:

  • HTTP/HTTPS服务器
  • 中间件上的路由和认证(使用gorilla mux
  • 会话管理(使用gorilla sessions
  • 用户管理(注册、修改和删除用户、通过邮件恢复密码)
  • 网络故障时的离线回退页面
  • 模块化设计,易于扩展
  • JSON格式的配置文件
  • 从文件或模型生成数据库(使用MySQL Workbench
  • 支持移动访问的响应式界面,禁用浏览器后退按钮

关于这个项目的DEV.to文章在这里

尽情使用。


8 回复

太棒的想法!

根据您实际需要的模板代码量,融入一些最佳实践可能会很有用,例如在注册时使用类似 bcrypt 的库来实现开箱即用的密码加盐和哈希处理。


我同意这个观点,这东西居然用明文存储未经哈希和加盐处理的密码。我也希望能有更好的配置格式。

说实话,我不太喜欢它:功能太少而且做得也不好。不过我觉得作为博客文章里的示例应用还行,所以可能只是个"宣传"手段而已。

BB-8:

灌输一些最佳实践可能会很有用,例如开箱即用的密码加盐和哈希处理

我会在下一个版本中考虑这一点 🙂

iegomez:

说实话,我不太喜欢它:它做得太少而且不够好。不过我认为作为博客文章中的示例应用程序还可以,所以也许这只是一个"宣传"手段。

这并非宣传,如您所见许可证是GPL。我只是认为这个基础程序很有用,所以分享了这个想法和代码,也许能像帮助我一样帮助到其他人。

并非所有Web应用程序都是互联网站点,其中很多是公司内部应用程序等。想要用Go编写这类应用程序的人可以跳过大量"做得太少"的工作,专注于实际任务。如果需要更好的配置,也可以自行改进这项免费工作。

并非要批评,但我确实想质疑这样一种假设:仅仅因为应用程序是"内部"的,我们就可以绕过一些基本的安全要求(如盐值、哈希、强健的身份验证等)。

物理边界固然重要,但可以被绕过,而且这种情况经常发生。心怀不满的员工是任何公司面临的最大风险之一,他们就在这个边界之内。此外,一些"内部"应用程序可能会被员工在可疑网络环境下从外部设施访问。VPN 会有所帮助,但无人看管的未锁定笔记本电脑仍然是个问题。在我看来,无论部署点在哪里,安全基础都是必不可少的。培训有助于预防某些风险,精心设计的软件也是如此。

欢迎提出不同意见,再次强调我并非要批评您的工作。我当然无法做得更好,而且无论如何我都不是该领域的专家或专业人士。我只是一个学生,努力提升自己的知识和技能。感谢您的贡献,希望您也能继续进步!

geosoft1: 没有广告,正如您所见许可证是GPL。我只是觉得这个基础程序很有用,所以分享了可能对他人有帮助的想法和代码,就像它曾帮助过我一样。

您误会了。我并不是指您通过这个程序进行自我宣传。

我的意思是,虽然我不喜欢把它作为鼓励人们使用的方案(基于我初次接触时的感受,或者说它被"宣传"给我的方式),但对于展示如何使用Go快速构建Web应用程序基础的博客文章来说,这或许还算合适。

关于"功能太少",再次说明,我并非批评其功能数量,而是强调如果它只实现少量功能(这完全没问题),就应该把这些功能做完善(比如使用明文密码就是不合适的,即便是企业内部应用也是如此)。

文字交流容易丢失语气信息,我无法判断我的意见是否让您感到不快,但希望这些观点无论如何能带来一些帮助。

如果我的理解正确,到目前为止提出的主要问题是密码以明文(未哈希)形式存储在数据库中。我同意这一点可以解决,可能会在下一个版本中实现。

对于内部应用程序以及其他情况,事情实际上并没有那么糟糕。在VPN和一些常规安全解决方案的保护下,不同公司的员工访问他们的内部资源(如共享文件或其他内容),因此在这种情况下,黑客攻击Linux机器以查看数据库中的某些密码并不是一件简单的事情。出于这个原因,我目前没有过分强调安全性,而是更注重功能性,随后再解决这些安全问题。

无论如何,HTTPS仍然可以通过证书使用,所以在有人入侵你的数据库之前,我认为使用这个并没有太大危险。但我愿意接受建设性的建议来改进这个想法。

看,我本不打算回应,但试图为此辩解只会让情况更糟。数据库是用户抵御恶意攻击的最后防线,因此以明文存储密码就是错误的,没有任何借口。"情况没那么糟"或"我觉得这样用风险不大"之类的说辞实在天真、危险且错误。

这可能是主要问题,但并非唯一问题。例如,在您的主函数中看到这个引起了我的注意:

// 注意:这将避免处理器过载
runtime.GOMAXPROCS(1)

您认为强制用户接受这样的设置合理吗?您对他们的机器了解多少,竟要硬编码这样的限制?

您的主文件还包含UserDatabaseSMTP结构体,而"添加简单应用"部分并未提及该应用的模型或结构体,这很可能意味着您期望用户在主文件中创建数据、配置和其他结构体:这完全违背了模块化设计的理念。

请不要误解,我并非要攻击您或您的代码,只是指出您选择公开的这个包存在的一些问题。如我之前所说,作为博客速成示例这基本可以接受,但请勿在明显未达标时声称"项目已可投入运行"。

以下是针对 Go 语言服务端应用骨架实现方案的专业回答,基于你提供的项目描述和功能需求:

实现方案概述

你的项目 ssas 提供了一个基于 Go 语言的服务器端应用骨架,适用于构建 HTML5 网络应用程序。它集成了 HTTP/HTTPS 服务器、路由、会话管理、用户管理等功能,采用模块化设计,便于扩展。以下是一个示例实现,展示如何构建一个基础服务器骨架,涵盖你提到的关键功能。

示例代码:基础服务器骨架

这个示例使用 gorilla/mux 处理路由和中间件,gorilla/sessions 管理会话,并模拟用户注册和认证流程。假设使用 JSON 配置文件(如 config.json)来设置服务器参数。

  1. 项目结构和配置文件 创建一个 config.json 文件来存储服务器配置:

    {
      "server": {
        "host": "localhost",
        "port": 8080,
        "use_https": false
      },
      "database": {
        "driver": "mysql",
        "dsn": "user:password@tcp(localhost:3306)/appdb"
      },
      "session": {
        "secret_key": "your-secret-key"
      }
    }
    
  2. 主文件 (main.go) 初始化服务器、加载配置、设置路由和中间件:

    package main
    
    import (
        "encoding/json"
        "log"
        "net/http"
        "os"
    
        "github.com/gorilla/mux"
        "github.com/gorilla/sessions"
    )
    
    // Config 结构体定义配置文件格式
    type Config struct {
        Server struct {
            Host     string `json:"host"`
            Port     int    `json:"port"`
            UseHTTPS bool   `json:"use_https"`
        } `json:"server"`
        Session struct {
            SecretKey string `json:"secret_key"`
        } `json:"session"`
    }
    
    var store = sessions.NewCookieStore([]byte("your-secret-key")) // 从配置中加载
    
    func main() {
        // 加载配置文件
        config := loadConfig("config.json")
        store = sessions.NewCookieStore([]byte(config.Session.SecretKey))
    
        // 初始化路由器
        r := mux.NewRouter()
    
        // 添加中间件:例如日志记录
        r.Use(loggingMiddleware)
    
        // 设置路由
        r.HandleFunc("/", homeHandler).Methods("GET")
        r.HandleFunc("/register", registerHandler).Methods("POST")
        r.HandleFunc("/login", loginHandler).Methods("POST")
        r.HandleFunc("/logout", logoutHandler).Methods("POST")
    
        // 启动 HTTP/HTTPS 服务器
        addr := fmt.Sprintf("%s:%d", config.Server.Host, config.Server.Port)
        if config.Server.UseHTTPS {
            log.Printf("Starting HTTPS server on %s", addr)
            log.Fatal(http.ListenAndServeTLS(addr, "cert.pem", "key.pem", r))
        } else {
            log.Printf("Starting HTTP server on %s", addr)
            log.Fatal(http.ListenAndServe(addr, r))
        }
    }
    
    func loadConfig(filename string) Config {
        file, err := os.Open(filename)
        if err != nil {
            log.Fatal("Failed to load config:", err)
        }
        defer file.Close()
    
        var config Config
        decoder := json.NewDecoder(file)
        err = decoder.Decode(&config)
        if err != nil {
            log.Fatal("Failed to decode config:", err)
        }
        return config
    }
    
    func loggingMiddleware(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            log.Printf("%s %s", r.Method, r.URL.Path)
            next.ServeHTTP(w, r)
        })
    }
    
    func homeHandler(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Welcome to the Go server skeleton!"))
    }
    
    func registerHandler(w http.ResponseWriter, r *http.Request) {
        // 示例:处理用户注册(实际中应验证输入并存储到数据库)
        email := r.FormValue("email")
        password := r.FormValue("password")
        log.Printf("Registering user: %s", email)
        w.Write([]byte("User registered successfully"))
    }
    
    func loginHandler(w http.ResponseWriter, r *http.Request) {
        session, _ := store.Get(r, "user-session")
        // 示例认证逻辑(实际中应检查数据库)
        session.Values["authenticated"] = true
        session.Save(r, w)
        w.Write([]byte("Logged in successfully"))
    }
    
    func logoutHandler(w http.ResponseWriter, r *http.Request) {
        session, _ := store.Get(r, "user-session")
        session.Values["authenticated"] = false
        session.Save(r, w)
        w.Write([]byte("Logged out successfully"))
    }
    
  3. 用户管理和会话处理 使用 gorilla/sessions 管理用户会话。在实际应用中,你需要集成数据库(如 MySQL)来存储用户数据,并实现密码恢复等功能。以下是一个扩展示例,添加用户模型和数据库操作(使用 database/sql 和 MySQL 驱动):

    import (
        "database/sql"
        _ "github.com/go-sql-driver/mysql"
    )
    
    // User 结构体定义用户模型
    type User struct {
        ID       int
        Email    string
        Password string // 实际中应使用哈希存储
    }
    
    func initDB(dsn string) *sql.DB {
        db, err := sql.Open("mysql", dsn)
        if err != nil {
            log.Fatal("Failed to connect to database:", err)
        }
        return db
    }
    
    // 在 registerHandler 中保存用户到数据库
    func registerHandler(w http.ResponseWriter, r *http.Request) {
        email := r.FormValue("email")
        password := r.FormValue("password") // 实际中应使用 bcrypt 等哈希
        db := initDB("user:password@tcp(localhost:3306)/appdb")
        defer db.Close()
    
        _, err := db.Exec("INSERT INTO users (email, password) VALUES (?, ?)", email, password)
        if err != nil {
            http.Error(w, "Registration failed", http.StatusInternalServerError)
            return
        }
        w.Write([]byte("User registered successfully"))
    }
    
  4. 离线回退和响应式界面 对于离线回退,你可以添加一个服务工作者(Service Worker)来缓存资源,但这是在客户端实现的。在服务器端,确保路由处理静态文件(如 HTML、CSS、JS)。使用 http.FileServer 提供静态文件:

    r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
    

总结

这个示例展示了如何用 Go 语言构建一个服务器端应用骨架,覆盖了 HTTP/HTTPS 服务器、路由、中间件、会话管理和基础用户功能。通过模块化设计,你可以轻松扩展其他功能,如数据库集成或移动优化界面。根据你的项目需求,进一步集成 MySQL Workbench 生成的模型或添加认证中间件来增强安全性。

回到顶部