构建CMS:goPress

Go是Google开发的一种以网络为中心的编程语言,可以轻松编写与网络相关的程序。 有许多不错的库可供选择,运行Web应用程序非常容易。

在本教程中,我将使用Go和一些帮助程序库创建一个内容管理系统(CMS)。 该CMS将使用第一个教程“ 构建CMS:结构和样式”中列出的站点数据结构。

Go开发设置

在Mac上安装go编程语言的最简单方法是使用Homebrew 。 如果尚未安装Homebrew,教程Homebrew Demystified:OS X的Ultimate Package Manager将向您展示如何。 对于其他平台,只需按照Go下载页面上的说明进行操作。

在终端中,键入:

brew install go

在您的主目录中,创建目录go 。 Go语言将在其中存储所有下载的库。 将以下行添加到您的.bashrc文件和/或.zshrc文件中:

export GOPATH="/Users/<your user name>/go"

如果您使用fish ,请将其添加到您的config.fish文件中:

set -xg GOPATH "/Users/<your user name>/go"

接下来,您需要安装库。 goWeb库提供了Web服务器框架, 琥珀色库提供了与Jade等效HTML预处理器, BlackFriday将Markdown转换为正确HTML。 另外,我使用Handlebars库进行模板制作。 要安装这些库,您需要在项目目录中键入以下内容:

go get github.com/hoisie/web
go get github.com/eknkc/amber
go get github.com/russross/blackfriday
go get github.com/murz/go-handlebars/handlebars

现在,Go和所需的库已在您的系统上,下一步是开始编码。 goPress库包含五个文件,而主程序是一个用于调用库函数的文件。

goPress.go库文件

我希望服务器尽可能快。 为此,任何可重用的内容都在内存中。 因此,全局变量保留所有资产和主页。 您可以设计具有所有资产在内存中的服务器,但这会导致大型站点上的内存膨胀。 由于主页应该是最频繁加载的页面,因此它也保留在内存中。

使用上一个教程中设置的文件结构,在src目录中创建一个名为goPress的新目录。 这将是放置所有goPress库文件的位置。 第一个文件是goPress.go 。 创建文件并开始输入以下代码。

package goPress

//
// Package:          goPress
//
// Description:        This package is for the goPress CMS
//                     written in the go programming
//                    language made by Google. This package
//                     defines everything for a full
//                    CMS.
//

//
// Import the libraries we use for this program.
//
import (
    "encoding/json"
    "github.com/hoisie/web"
    "io/ioutil"
    "log"
    "os"
    "strings"
)

//
// Define a structure to contain all the information
// important to the CMS. Capticalized
// variables within the structure is imported 
// and exported.
//
type goPressData struct {
    CurrentLayout  string
    CurrentStyling string
    ServerAddress  string
    SiteTitle      string
    Sitebase       string
    TemplatBase    string
    CapatchaWidth  int
    CapatchaHeight int
    Cache          bool
    MainBase       string
    content        map[string]string
    layoutBase     string
    mainpg         string
    postbase       string
    scripts        string
    stylesheet     string
    stylingBase    string
    template       string
}

var SiteData = new(goPressData)
var ServerParamFile string = "server.json"

顶部的package语句告诉编译器此文件是软件包库的一部分,并提供库的名称。 该目录中的每个文件都必须在其顶部具有此名称才能成为文件的一部分。

接下来,导入此文件中引用的所有库。 如果您列出了一个库而不使用它,则编译器会抱怨。 这有助于保持代码整洁。

加载库后,我定义了CMS将使用的不同数据结构和全局变量。 这些全局变量是图书馆全局变量。 在库外部,如果库名限定了范围,则可以引用以大写字母开头的变量。

接下来,将此功能添加到同一文件:

//
// Function:        GetGlobals
//
// Description:        This function is used to create the
//                     global variables initialize the
//                    global variables.
//
// Inputs:
//
func GetGlobals() {
    //
    // Load the Server Parameters from a file.
    //
    LoadServerParameters()

    //
    // Setup the basic paths to everything.
    //
    SiteData.layoutBase = SiteData.TemplatBase + "layouts/"
    SiteData.stylingBase = SiteData.TemplatBase + "styling/"
    SiteData.postbase = SiteData.Sitebase + "posts/"

    //
    // Create the content array that will hold the site 
    // fragments. Set the title now.
    //
    SiteData.content = make(map[string]string)
    SiteData.content["title"] = SiteData.SiteTitle

    //
    // Log that the data is being loaded.
    //
    log.Println("Loading data for site: " + SiteData.SiteTitle)

    //
    // Get all the basic information that is generic and 
    // in the styles and layout directories.
    // These will then be over written if a new default 
    // in the site area is found. This gives
    // the flexibility to load defaults from a directory 
    // without having to make sure that all
    // the necessary ones are loaded.
    //

    //
    // Get the 404 page contents
    //
    SiteData.content["404"] = GetPageContents(SiteData.stylingBase + SiteData.CurrentStyling + "/404")

    //
    // Get the sidebar contents
    //
    SiteData.content["sidebar"] = GetPageContents(SiteData.stylingBase + SiteData.CurrentStyling + "/sidebar")

    //
    // Get the footer contents
    //
    SiteData.content["footer"] = GetPageContents(SiteData.stylingBase + SiteData.CurrentStyling + "/footer")

    //
    // Get the template contents
    //
    SiteData.template = GetPageContents(SiteData.layoutBase + SiteData.CurrentLayout + "/template")

    //
    // Get the header contents
    //
    SiteData.content["header"] = GetPageContents(SiteData.stylingBase + SiteData.CurrentStyling + "/header")

    //
    // Get the main page contents
    //
    SiteData.mainpg = GetPageContents(SiteData.Sitebase + "pages/" + "main")

    //
    // The following will load page parts from the 
    // "parts" directory for the site. These might
    // overload those already defined or add new stuff 
    // that the users site templates
    // will need.
    //
    partsdir := SiteData.Sitebase + "parts/"

    //
    // Read the directory.
    //
    fileList, err := ioutil.ReadDir(partsdir)
    if err != nil {
        //
        // Error reading the directory.
        //
        log.Printf("Error reading directory: %s\n", partsdir)
    } else {
        //
        // Get the number of items in the directory list.
        //
        count := len(fileList)

        //
        // Loop through each directory element.
        //
        for i := 0; i < count; i++ {
            if !fileList[i].IsDir() {
                //
                // It is a file. Read it and add to the 
                // scripts variable.
                //
                filename := fileList[i].Name()
                parts := strings.Split(filename, ".")
                if filename != ".DS_Store" {
                    SiteData.content[parts[0]] = LoadFile(partsdir + filename)
                }
            }
        }
    }

    //
    // Clear out the global variables not set.
    //
    SiteData.scripts = ""
    SiteData.stylesheet = ""
}

GetGlobals函数加载该站点的所有全局存储的信息。 基于文件名的哈希映射(不带扩展名)存储服务器文件,布局目录和样式目录中的数据。 然后,将site/parts目录中的所有内容放入相同的结构中。 这样,如果站点只想要主题中给定的默认值,则用户不必将其文件放在site/parts目录中。

在同一文件中,添加以下功能:

//
// Function:        SaveServerParameters
//
// Description:        This function is for saving the 
//                     authorization secret for DropBox.
//
// Inputs:
//
func SaveServerParameters() {
    if wfile, err := os.Create(ServerParamFile); err == nil {
        enc := json.NewEncoder(wfile)
        enc.Encode(&SiteData)
        wfile.Close()
    } else {
        log.Println("Writing Server file denied.")
    }
}

//
// Function:        LoadServerParameters
//
// Description:        This function is used to load the 
//                    parameters for this server.
//
// Inputs:
//
func LoadServerParameters() {
    if wfile, err := os.Open(ServerParamFile); err == nil {
        enc := json.NewDecoder(wfile)
        enc.Decode(&SiteData)
        wfile.Close()
        log.Println("Read the " + ServerParamFile + " server parameter file. Site Title is: " + SiteData.SiteTitle)
    } else {
        log.Println("No Server File found.")
    }
}

这些是帮助函数SaveServerParameters()LoadServerParameters() 。 这些函数将不同的服务器设置保存并加载到server.json文件中。

接下来的功能是创建路线和我们的默认路线。 将这些功能添加到同一文件中:

//
// Function:        DefaultRoutes
//
// Description:        This function sets the default 
//                    routes for a CMS.
//
// Inputs:
//
func DefaultRoutes() {
    SetGetRoute("/", Mainpage)
    SetGetRoute("/sitemap.xml", SiteMap)
    SetGetRoute("/stylesheets.css", GetStylesheets)
    SetGetRoute("/scripts.js", GetScripts)
    SetGetRoute("/theme/images/(.*)", LoadThemeImage)
    SetGetRoute("/(favicon.ico)", ImagesLoad)
    SetGetRoute("/images/(.*)", ImagesLoad)
    SetGetRoute("/posts/([a-zA-Z0-9]*)/([a-zA-Z0-9]*)", PostIndex)
    SetGetRoute("/posts/([a-zA-Z0-9]*)/([a-zA-Z0-9]*)/(.*)", PostPages)
    SetGetRoute("/(.*)", TopPages)
}

//
// Function:        SetGetRoute
//
// Description:        This function gives an easy access 
//                    to the web variable setup in this 
//                    library.
//
// Inputs:
//         route         Route to setup
//        handler        Function to run that route.
//
func SetGetRoute(route string, handler interface{}) {
    web.Get(route, handler)
}

//
// Function:        StartServer
//
// Description:        This function is for starting the web 
//                    server using the SiteData 
//                    configuration.
//
// Inputs:
//
func StartServer(serverAddress string) {
    web.Run(serverAddress)
}

DefaultRoutes()函数创建要在我们的CMS中使用的默认路由。 这些路由的功能在其他库文件中。 SetGetRoute()创建每个路由。 这只是goWeb库函数的包装,该函数使用正则表达式定义路由的格式,并使用在该表达式为true时执行的函数。 如果您曾经将Sinatra框架用于Ruby或将Express框架用于Node.js,那么您将熟悉此设置。

路线的创建顺序很重要。 如果第一个路由包含匹配所有内容的正则表达式,则其余路由将无法访问。 第一条路线将全部抓住。 因此,我首先定义了最具体的路由,最后定义了更通用的路由。

StartServer()函数启动Web服务器。 它调用goWeb函数Run()来获取服务器的地址。

在整个代码中,我充分利用了log.PrintLn()函数。 这会将带有日期和时间戳记的消息打印到控制台。 这对于调试非常有用,但也可用于流量分析。

PagesPosts.go库文件

接下来,在同一目录中创建PagesPosts.go文件。 该文件将包含用于处理页面和帖子类型的所有代码。 页面只是一个网页。 帖子是随时间推移创建的所有内容:新闻帖子,博客帖子,教程等。在此文件中,添加以下代码:

package goPress

import (
    "bytes"
    "encoding/json"
    "github.com/eknkc/amber"
    "github.com/hoisie/web"
    "github.com/murz/go-handlebars/handlebars"
    "github.com/russross/blackfriday"
    "io/ioutil"
    "log"
    "os"
    "strings"
    "time"
)

goPress.go文件中一样,它以package声明和要导入的库列表开头。 该文件将利用我们下载了(我)每个库。

//
// Function:         Mainpage
//
// Description:     This function is used to generate 
//                     and display the main page for the
//                     web site. This function will guide 
//                     the user to setup the DropBox
//                     account if this is the first time 
//                     being ran or the dropbox 
//                     authorization secret gets zeroed.
//
// Inputs:
//                    ctx     Contents from the request
//
func Mainpage(ctx *web.Context) string {
    //
    // Render the main page.
    //
    page := RenderPageContents(ctx, SiteData.mainpg, SiteData.Sitebase+"pages/main")

    return page
}

Mainpage()函数显示站点的首页。 它只是RenderPageContents()函数的包装,用于指定要呈现的主索引页。 RenderPageContents()完成所有实际工作。

//
// Function:         SiteMap
//
// Description:     This function is to give a site map 
//                    to requesters.
//
// Inputs:
//                    ctx     Contents from the request
//
func SiteMap(ctx *web.Context) string {
    var contents string

    wfile, err := os.Open(SiteData.Sitebase + "sitemap.xml")
    if err == nil {
        bcontents, _ := ioutil.ReadAll(wfile)
        contents = string(bcontents)
        wfile.Close()
    }
    return contents
}

SiteMap()函数将站点地图提供给请求者。 它从站点目录顶部的sitemap.xml中提取信息。

//
// Function:         PostPages
//
// Description:     This function generates the needed 
//                    post page.
//
// Inputs:
//                    ctx        What the browser sends
//                    posttype   The type of post
//                    postname   The name of the post 
//                                    type instance
//                    val        The name of the post 
//                                    page to display
//
func PostPages(ctx *web.Context, posttype string, postname string, val string) string {
    //
    // Get the page contents and process it.
    //
    pgloc := SiteData.postbase + posttype + "/" + postname + "/" + val
    return RenderPageContents(ctx, GetPageContents(pgloc), pgloc)
}

PostPages()函数显示所需的正确帖子。 再一次,这只是设置了对RenderPageContents()函数的调用,该函数完成了所有主要工作。

//
// Function:          PostIndex
//
// Description:       This function generates the needed post index.
//
// Inputs:
//                    ctx       What the browser sends
//                    posttype  The type of post
//                    postname  The name of the post type instance
//
func PostIndex(ctx *web.Context, posttype string, postname string) string {
    //
    // Get the page contents and process it.
    //
    pgloc := SiteData.postbase + posttype + "/" + postname + "/index"
    return RenderPageContents(ctx, GetPageContents(pgloc), pgloc)
}

PostIndex()函数将信息汇总到一起,并提供给RenderPageContents()函数。

//
// Function:         topPages
//
// Description:     This function will generate a 
//                    "static" top level page that is not
//                     a post page.
//
// Inputs:
//                    val         The name of the top level page
//
func TopPages(ctx *web.Context, val string) string {
    //
    // Look for the markdown of the page.
    //
    pgloc := SiteData.Sitebase + "pages/" + val
    return RenderPageContents(ctx, GetPageContents(pgloc), pgloc)
}

topPages()函数为标准页面设置RenderPageContents()函数。 所有页面都在pages/目录中。

//
// Function:         GetPageContents
//
// Description:     This function is used to retrieve 
//                    the page contents. It will first look 
//                    for a markdown page, then for a html 
//                    page, and then it looks for an amber
//                    page.
//
// Inputs:
//                    filename         The name of the file
//
func GetPageContents(filename string) string {
    //
    // Assume the page can not be found.
    //
    contents := SiteData.content["404"]

    //
    // Let's look for a markdown version first.
    //
    wfile, err := os.Open(filename + ".md")
    if err == nil {
        bcontents, _ := ioutil.ReadAll(wfile)
        wfile.Close()
        contents = string(blackfriday.MarkdownCommon(bcontents))
        //
        // Double quotes were turned into &ldquo; and
        // &rdquo;. Turn them back. Without this, and 
        // Handlebar macros will be broken.
        //
        contents = strings.Replace(contents, "&ldquo;", "\"", -1)
        contents = strings.Replace(contents, "&rdquo;", "\"", -1)
    } else {
        //
        // It must be an html. Look for that.
        //
        wfile, err = os.Open(filename + ".html")
        if err == nil {
            bcontents, _ := ioutil.ReadAll(wfile)
            contents = string(bcontents)
            wfile.Close()
        } else {
            //
            // It must be an amber. Look for that.
            //
            wfile, err = os.Open(filename + ".amber")
            if err == nil {
                wfile.Close()
                template, err2 := amber.CompileFile(filename+".amber", amber.Options{true, false})
                if err2 != nil {
                    //
                    // Bad amber file.

                    log.Println("Amber file bad: " + filename)
                } else {
                    //
                    // Put the default site info.
                    //
                    pgData := SiteData.content

                    //
                    // read in that pages specific data 
                    // to be added to the rest
                    // of the data. It is stored at the 
                    // same place, but in a json
                    // file.
                    //
                    if wfile, err := os.Open(filename + ".json"); err == nil {
                        //
                        // Load the json file of extra 
                        // data for this page. This could 
                        // override the standard data as 
                        // well.
                        //
                        enc := json.NewDecoder(wfile)
                        enc.Decode(&pgData)
                        wfile.Close()
                    } else {
                        log.Println("The page: " + filename + " did not have a json file.")
                    }

                    pgData["PageName"] = filename

                    //
                    // The amber source compiles okay. 
                    // Run the template and return
                    // the results.
                    //
                    var b bytes.Buffer
                    template.Execute(&b, pgData)
                    contents = b.String()
                }
            } else {
                //
                // A file could not be found.
                //
                log.Println("Could not find file:  " + filename)
            }
        }
    }

    //
    // Return the file contains obtained.
    //
    return contents
}

GetPageContents()函数加载所有页面/帖子的内容。 它首先从全局数据结构中加载404未找到的页面内容。 然后,该函数先查找Markdown文件,然后查找HTML文件,然后查找Amber文件。 接下来,该例程将所有Markdown和Amber内容转换为HTML。 Amber文件可能在JSON文件中具有关联的数据。 该数据文件也被加载以处理琥珀文件。

Blackfriday的Markdown处理对Handlebars处理器产生影响。 HTML处理器的Blackfriday降价促销将所有双引号更改为其HTML转义的等效字符( &ldquo;&rdquo; )。 由于这不是渲染所必需的100%,因此我随后撤消了更改。 这样可以使所有使用双引号的Handlebars宏保持功能。

如果需要更多文件格式类型,只需在此处添加即可。 此例程加载每种内容类型。

//
// Function:         RenderPageContents
//
// Description:     This function is used to process 
//                    and render the contents of a page.
//                     It can be the main page, or a post 
//                    page, or any page. It accepts the
//                     input as the contents for the page 
//                    template, run the page template
//                     with it, process all shortcodes and 
//                    embedded codes, and return the
//                     results.
//
// Inputs:
//                ctx                The calling context
//                 contents         The pages main contents.
//                filename        The name of the file the 
//                                contents was taken from.
//
func RenderPageContents(ctx *web.Context, contents string, filename string) string {
    //
    // Set the header information
    //
    SetStandardHeader(ctx)

    //
    // Put the default site info.
    //
    pgData := SiteData.content

    //
    // Add data specific to this page.
    //
    pgData["content"] = contents

    //
    // read in that pages specific data to be added to 
    // the rest of the data. It is stored at the same 
    // place, but in a json file.
    //
    if wfile, err := os.Open(filename + ".json"); err == nil {
        //
        // Load the json file of extra data for this 
        // page. This could override the standard data as 
        // well.
        //
        enc := json.NewDecoder(wfile)
        enc.Decode(&pgData)
        wfile.Close()
    } else {
        log.Println("The page: " + filename + " did not have a json file.")
    }

    //
    // Set the Page Name data field.
    //
    pgData["PageName"] = filename

    //
    // Register the helpers.
    //
    // NOTICE:    All helpers can not have spaces in the 
    //            parameter. Therefore, all of these 
    //            helpers assume a "-" is a space. It gets 
    //            translated to a space before using.
    //
    // Helper:             save
    //
    // Description:     This helper allows you do define 
    //                    macros for expanding inside the 
    //                    template. You give it a name, 
    //                    "|", and text to expand into. 
    //                    Currently, all spaces have to be 
    //                    "-".
    //
    handlebars.RegisterHelper("save", func(params ...interface{}) string {
        if text, ok := params[0].(string); ok {
            parts := strings.Split(text, "|")
            content := strings.Replace(parts[1], "-", " ", -1)
            pgData[parts[0]] = content
            return content
        }
        return ""
    })

    //
    //  The format has to use these sets of constants:
    //            Stdlongmonth = "January"
    //            Stdmonth = "Jan"
    //            Stdnummonth = "1"
    //            Stdzeromonth = "01"
    //            Stdlongweekday = "Monday"
    //            Stdweekday = "Mon"
    //            Stdday = "2"
    //            Stdunderday = "_2"
    //            Stdzeroday = "02"
    //            Stdhour = "15"
    //            stdHour12 = "3"
    //            stdZeroHour12 = "03"
    //            Stdminute = "4"
    //            Stdzerominute = "04"
    //            Stdsecond = "5"
    //            Stdzerosecond = "05"
    //            Stdlongyear = "2006"
    //            Stdyear = "06"
    //            Stdpm = "Pm"
    //            Stdpm = "Pm"
    //            Stdtz = "Mst"
    //
    //  Helper:            date
    //
    //  Description:     This helper prints the current 
    //                    date/time in the format
    //                     given. Please refer to the above 
    //                    chart for proper format codes. 
    //                    EX:  07/20/2015 is "01/02/2006"
    //
    handlebars.RegisterHelper("date", func(params ...interface{}) string {
        if format, ok := params[0].(string); ok {
            format = strings.Replace(format, "-", " ", -1)
            tnow := time.Now()
            return tnow.Format(format)
        }
        return ""
    })

    //
    // Render the current for the first pass.
    //
    page := handlebars.Render(SiteData.template, pgData)

    //
    // Process any shortcodes on the page.
    //
    page1 := ProcessShortCodes(page)

    //
    // Render new content from Short Code and filters.
    //
    page2 := handlebars.Render(page1, pgData)

    //
    // Return the results.
    //
    return page2}

RenderPageContents()是用于创建网页的主要功能。 在设置了答复的标准标头之后,此例程将创建一个数据结构,并将其填充为默认内容,页面内容以及该页面的关联JSON文件。 Handlebars模板程序使用数据结构来呈现整个页面。

接下来,该例程定义所有Handlebars辅助函数。 当前,有两个: save助手和date助手。 如果您想要更多的辅助功能,可以在此处将其添加到项目中。

save帮助器有两个参数:名称与内容之间用|分隔| 。 由于“ Handlebars”辅助程序参数不能包含空格,因此这些参数使用-代替空格。 这使您可以在页面上下文内创建每页模板变量。 例如, {{save site|Custom-Computer-Tools}}宏会将“ Custom Computer Tools放置在定义点以及页面上具有{{site}}其他任何位置。

date助手使用格式字符串,并根据该格式字符串创建正确的日期。 例如, {{date January-2,-2006}}宏在该天产生October 13, 2015October 13, 2015日。

Handlebars模板程序处理该页面两次:在呈现简码之前(以防模板扩展中包含任何简码),以及在运行简码之后(以防简码添加任何Handlebars模板操作)。 最后,该函数返回所请求页面的完整HTML内容。

//
// Function:         SetStandardHeader
//
// Description:     This function is used as a one place 
//                    for setting the standard
//                     header information.
//
// Inputs:
//
func SetStandardHeader(ctx *web.Context) {
    //
    // Set caching for the item
    //
    ctx.SetHeader("Cache-Control", "public", false)

    //
    // Set the maximum age to one month (30 days)
    //
    ctx.SetHeader("Cache-Control", "max-age=2592000", false)

    //
    // Set the name to gpPress for the server type.
    //
    ctx.SetHeader("Server", "goPress - a CMS written in go from Custom Computer Tools: http://customct.com.", true)
}

SetStandardHeader()函数将所有自定义标题项目设置为答复。 在这里设置服务器信息和所有缓存控件。

Images.go库文件

下一个要处理的文件是Images.go文件,以及将图像发送到浏览器所需的所有功能。 由于这将是一个完整的Web服务器,因此它必须处理发送图像的二进制数据。 创建文件Images.go并将以下代码放入其中:

package goPress

import (
    "github.com/hoisie/web"
    "io"
    "io/ioutil"
    "log"
    "math/big"
    "os"
    "path/filepath"
)

//
// Function:          ImagesLoad
//
// Description:       This function is called to upload an image for the
//                    images directory.
//
// Inputs:
//                    val        Name of the image with relative path
//
func ImagesLoad(ctx *web.Context, val string) {
    LoadImage(ctx, SiteData.Sitebase+"images/"+val)
}

//
// Function:         LoadThemeImage
//
// Description:         This function loads images from the theme's directory.
//
// Inputs
//                     image        Name of the image file to load
//
func LoadThemeImage(ctx *web.Context, image string) {
    LoadImage(ctx, SiteData.stylingBase+SiteData.CurrentStyling+"/images/"+image)
}

该库文件与其他文件一样开始:包声明和库声明。 该ImagesLoad()函数和LoadThemeImage()函数建立到一个呼叫LoadImage()函数来完成实际工作。 这些功能允许从站点目录或当前主题目录加载图像。

//
// Function:         LoadImage
//
// Description:     This function does the work of 
//                    loading an image file and passing it 
//                    on.
//
// Inputs:
//
func LoadImage(ctx *web.Context, val string) {
    //
    // Get the file extension.
    //
    fileExt := filepath.Ext(val)

    //
    // Set the http header based on the file type.
    //
    SetStandardHeader(ctx)
    ctx.ContentType(fileExt)
    if fileExt == ".svg" {
        //
        // This is a text based file. Read it and send to the browser.
        //
        wfile, err := os.Open(val)
        if err == nil {
            bcontents, _ := ioutil.ReadAll(wfile)
            wfile.Close()
            ctx.WriteString(string(bcontents))
        }
    } else {
        //
        // This is a binary based file. Read it and sent the contents to the browser.
        //
        fi, err := os.Open(val)

        //
        // Set the size of the binary coming down the pipe. Chrome has to have this value
        // one larger than real.
        //
        finfo, _ := os.Stat(val)
        i := big.NewInt(finfo.Size())
        ctx.SetHeader("Accept-Ranges", "bytes", true)
        ctx.SetHeader("Content-Length", i.String(), true)

        if err != nil {
            log.Println(err)
            return
        }
        defer fi.Close()

        //
        // Create a buffer to contain the image data. Binary images usually get
        // very big.
        //
        buf := make([]byte, 1024)

        //
        // Go through the binary file 1K at a time and send to the browser.
        //
        for {
            //
            // Read a buffer full.
            //
            n, err := fi.Read(buf)
            if err != nil && err != io.EOF {
                log.Println(err)
                break
            }

            //
            // If nothing was read, then exit.
            //
            if n == 0 {
                break
            }

            //
            // Write the binary buffer to the browser.
            //
            n2, err := ctx.Write(buf[:n])
            if err != nil {
                log.Println(err)
                break
            } else if n2 != n {
                log.Println("Error in sending " + val + " to the browser. Amount read does not equal the amount sent.")
                break
            }
        }
    }
}

LoadImage()函数检查图像类型。 如果它是svg文件,则它将以纯文本格式加载。 假设所有其他文件类型都是二进制文件,则例程将更仔细地加载它们。 它将以1K块上传二进制文件。

StyleSheetScripts.go库文件

下一个文件用于加载CSS和JavaScript。 由于我们的构建脚本将所有CSS和JavaScript都编译为一个文件,因此这些功能非常简单。 创建文件StyleSheetScripts.go并添加以下行:

package goPress

import (
    "github.com/hoisie/web"
    "io/ioutil"
    "log"
    "os"
)

//
// Function:         GetStylesheets
//
// Description:     This function is used to produce 
//                    the stylesheet to the user.
//
// Inputs:
//
func GetStylesheets(ctx *web.Context) string {
    //
    // See if we have already loaded them or not. If so, 
    // just return the pre-loaded stylesheet.
    //
    ctx.SetHeader("Content-Type", "text/css", true)
    SetStandardHeader(ctx)
    tmp := ""
    if SiteData.stylesheet == "" {
        tmp = LoadFile(SiteData.Sitebase + "css/final/final.css")
        //
        // If we are testing, we do not want the server 
        // to cache the stylesheets. Therefore, if 
        // cache is true, cache them. Otherwise,
        // do not.
        //
        if SiteData.Cache == true {
            SiteData.stylesheet = tmp
        }
    } else {
        //
        // We have a cached style sheet. Send it to the 
        // browser.
        //
        tmp = SiteData.stylesheet
    }

    //
    // Return the stylesheet.
    //
    return tmp
}

//
// Function:         GetScripts
//
// Description:     This function is to load JavaScripts 
//                    to the browser. This will
//                     actually load all the JavaScript 
//                    files into one compressed file
//                     for uploading to the browser.
//
// Inputs:
//
func GetScripts(ctx *web.Context) string {
    //
    // See if we have already loaded them or not. If so, 
    // just return the pre-loaded scripts.
    //
    ctx.SetHeader("Content-Type", "text/javascript", true)
    SetStandardHeader(ctx)
    tmp := ""
    if SiteData.scripts == "" {
        tmp = LoadFile(SiteData.Sitebase + "js/final/final.js")

        //
        // If we are testing, we do not want the server 
        // to cache the scripts. Therefore, if cache is 
        // true, cache them. Otherwise, do not.
        //
        if SiteData.Cache == true {
            SiteData.scripts = tmp
        }
    } else {
        //
        // We have a cached style sheet. Send it to the 
        // browser.
        //
        tmp = SiteData.scripts
    }

    //
    // Return the resulting compiled stylesheet.
    //
    return tmp
}

//
// Function:         LoadFile
//
// Description:     This function if for loading 
//                    individual file contents.
//
// Inputs
//                     file         name of the file to be 
//                                loaded
//
func LoadFile(file string) string {
    ret := ""
    log.Println("Loading file: " + file)
    wfile, err := os.Open(file)
    if err == nil {
        bcontents, err := ioutil.ReadAll(wfile)
        err = err
        ret = string(bcontents)
        wfile.Close()
    } else {
        //
        // Could not read the file.
        //
        log.Println("Could not read: " + file)
    }
    return ret
}

该文件具有三个功能。 GetStylesheets()函数将加载已编译CSS文件。 GetScripts()函数将加载已编译JavaScript文件。 设置了缓存标志后,这两个函数都将缓存内容。 我在测试时关闭了Cache标志。 LoadFile()函数是用于获取文件内容的简单文件加载函数。

Shortcodes.go库文件

当我想要一台快速的服务器时,我还想要很多灵活性。 为了获得灵活性,宏扩展有两种不同类型:直接把手扩展和短代码扩展。

区别在于Handlebars扩展是一种简单的,低逻辑的扩展,而shortcode扩展是可以编程到系统中的任何东西:从外部站点下载信息,使用外部程序处理信息或几乎任何东西。

创建Shortcodes.go文件并将其放入其中:

package goPress

//
// Library:         Shortcodes
//
// Description:     This library gives the functionality 
//                    of shortcodes in the CMS. A shortcode 
//                    runs a function with specified 
//                    argument and a surrounded contents. 
//                    The function should process the 
//                    contents according to the arguments 
//                    and return a string for placement 
//                    into the page. Shortcodes are 
//                    recursively processed, therefore you 
//                    can have different shortcodes inside 
//                    of other shortcodes.  You can not 
//                    have the same shortcode inside of 
//                    itself.
//
import (
    "bytes"
    "log"
    "regexp"
)

//
// Type:             ShortcodeFunction
//
// Description:     This type defines a function that 
//                    implements a shortcode. The function
//                     should receive two strings and return 
//                    a string.
//
type ShortcodeFunction func(string, string) string

//
// Library Variables:
//
//             shortcodeStack         This array of functions 
//                                holds all of the 
//                                shortcodes usable by the 
//                                CMS. You add shortcodes 
//                                using the AddShortCode 
//                                function.
//
var (
    shortcodeStack map[string]ShortcodeFunction
)

//
// Library Function:
//
//                init         This function is called upon 
//                            library use to initialize any 
//                            variables used for the 
//                            library before anyone can 
//                            make a call to a library 
//                            function.
//

func init() {
    shortcodeStack = make(map[string]ShortcodeFunction)
}

该文件与其他所有文件一样以文件包和所用库的声明开始。 但是它与定义特殊的ShortcodeFunction变量类型,库变量和init()函数很快有所不同。 库变量只能通过库的功能查看。 这个库变量shortcodeStack是字符串到函数的映射。

库的init()函数使您可以在对库进行任何其他调用之前运行代码。 在这里,我初始化了shortcodeStack数据结构以保存短代码列表。

//
// Function:         AddShortCode
//
// Description:     This function adds a new shortcode to 
//                    be used.
//
// Inputs
//         name           Name of the shortcode
//         funct          function to process the shortcode
//
func AddShortCode(name string, funct ShortcodeFunction) {
    shortcodeStack[name] = funct
}

AddShortCode()函数允许您将用于处理短代码的函数加载到所有短代码的库变量中。

//
// Function:        ProcessShortCodes
//
// Description:     This function takes in a string, 
//                    searches for shortcodes, process the 
//                    shortcode, put the results into the 
//                    string, and then return the fully 
//                    processed string.
//
// Inputs
//                page     String with possible shortcodes
//
func ProcessShortCodes(page string) string {
    //
    // Create a work buffer.
    //
    var buff bytes.Buffer

    //
    // Search for shortcodes. We first capture a 
    // shortcode.
    //
    r, err := regexp.Compile(`\-\[([^\]]*)\]\-`)
    if err == nil {
        match := r.FindString(page)
        if match != "" {
            //
            // Get the indexes to the matching area.
            //
            index := r.FindStringIndex(page)

            //
            // Save all the text before the shortcode 
            // into the buffer.
            //
            buff.WriteString(page[0:index[0]])

            //
            // Get everything that is left after the 
            // shortcode.
            //
            remander := page[index[1]:]

            //
            // Separate the strings out and setup 
            // variables for their contents.
            //
            submatch := r.FindStringSubmatch(match)
            name := ""
            contents := ""
            args := ""

            //
            // Get the name of the shortcode and separate 
            // the extra arguments.
            //
            r3, err3 := regexp.Compile(`(\w+)(.*)`)
            if err3 == nil {
                submatch2 := r3.FindStringSubmatch(submatch[1])

                //
                // The first submatch is the name of the 
                // shortcode.
                //
                name = submatch2[1]

                //
                // The second submatch, if any, are the 
                // arguments for the shortcode.
                //
                args = submatch2[2]
            } else {
                //
                // Something happened to the internal 
                // matching.
                //
                name = submatch[1]
                args = ""
            }

            //
            // Find the end of the shortcode.
            //
            final := "\\-\\[\\/" + name + "\\]\\-"
            r2, err2 := regexp.Compile(final)
            if err2 == nil {
                index2 := r2.FindStringIndex(remander)
                if index2 != nil {
                    //
                    // Get the contents and what is left 
                    // over after the closing of the 
                    // shortcode.
                    //
                    contents = remander[:index2[0]]
                    remander2 := remander[index2[1]:]

                    //
                    // If it is a real shortcode, then 
                    // run it!
                    //
                    if shortcodeStack[name] != nil {
                        //
                        // See if there is any shortcodes 
                        // inside the contents area.
                        //
                        contents = ProcessShortCodes(contents)

                        //
                        // Run the shortcode and add it's 
                        // result to the buffer.
                        //
                        buff.WriteString(shortcodeStack[name](args, contents))
                    }

                    //
                    // Process any remaining shortcodes.
                    //
                    buff.WriteString(ProcessShortCodes(remander2))

                } else {
                    //
                    // We have a bad shortcode 
                    // definition.  All shortcodes have 
                    // to be closed. Therefore,
                    // simply do not process anything and 
                    // tell the logs.
                    //
                    log.Println("Bad Shortcode definition. It was not closed.  Name:  " + name)
                    buff.WriteString(page[index[0]:index[1]])
                    buff.WriteString(ProcessShortCodes(remander))
                }
            } else {
                //
                // There was an error in the regular 
                // expression for closing the shortcode.
                //
                log.Println("The closing shortcode's regexp did not work!")
            }
        } else {
            //
            // No shortcodes, just copy the page to the 
            // buffer.
            //
            buff.WriteString(page)
        }
    } else {
        //
        // If the Regular Expression is invalid, tell the 
        // world!
        //
        log.Println("RegEx: Invalid expression.")
    }

    //
    // Return the resulting buffer.
    //
    return buff.String()
}

ProcessShortCodes()函数采用字符串作为网页内容,并在其中搜索所有短代码。 因此,如果您有一个名为box的简码,则可以使用以下格式将其插入网页中:

-[box args="some items"]-
<p>This should be inside the box.</p>
-[/box]-

简码打开器中空格后面的所有内容都是要处理的简码的参数。 参数的格式由shortcode函数处理。

所有短代码都必须有一个结束短代码。 在发送到短代码处理功能之前,打开和关闭短代码的内部也是短代码的过程。 我使用-[]-定义了一个简码,这样内联JavaScript索引不会被混淆为简码。

//
// Function:         ShortcodeBox
//
// Description:     This shortcode is used to put the 
//                    surrounded HTML in a box div.
//
// Inputs:
//            parms         The parameters used by the 
//                        shortcode
//            context        The HTML enclosed by the opening 
//                        and closing shortcodes.
//
func ShortcodeBox(parms string, context string) string {
    return ("<div class='box'>" + context + "</div>")
}

//
// Function:         ShortcodeColumn1
//
// Description:     This shortcode is used to put the 
//                    surrounded HTML in the first column.
//
// Inputs:
//            parms         The parameters used by the 
//                        shortcode
//            context        The HTML enclosed by the opening 
//                        and closing shortcodes.
//
func ShortcodeColumn1(parms string, context string) string {
    return ("<div class='col1'>" + context + "</div>")
}

//
// Function:         ShortcodeColumn2
//
// Description:     This shortcode is used to put the 
//                    surrounded HTML in the second column.
//
// Inputs:
//            parms         The parameters used by the 
//                        shortcode
//            context        The HTML enclosed by the opening 
//                        and closing shortcodes.
//
func ShortcodeColumn2(parms string, context string) string {
    return ("<div class='col2'>" + context + "</div>")
}

//
// Function:        ShortcodePHP
//
// Description:        This shortcode is for surrounding a 
//                    code block and formatting it's look 
//                    and feel properly. This one is for a 
//                    PHP code block.
//
// Inputs:
//            parms         The parameters used by the 
//                        shortcode
//            context        The HTML enclosed by the opening 
//                        and closing shortcodes.
//
func ShortcodePHP(params string, context string) string {
    return ("<div class='showcode'><pre type='syntaxhighlighter' class='brush: php'>" + context + "</pre></div>")
}

//
// Function:        ShortcodeJS
//
// Description:        This shortcode is for surrounding a 
//                    code block and formatting it's look 
//                    and feel properly. This one is for a 
//                    JavaScript code block.
//
// Inputs:
//            parms         The parameters used by the 
//                        shortcode
//            context        The HTML enclosed by the opening 
//                        and closing shortcodes.
//
func ShortcodeJS(params string, context string) string {
    return ("<div class='showcode'><pre type='syntaxhighlighter' class='brush: javascript'>" + context + "</pre></div>")
}

//
// Function:        ShortcodeHTML
//
// Description:        This shortcode is for surrounding a 
//                    code block and formatting it's look 
//                    and feel properly. This one is for a 
//                    HTML code block.
//
// Inputs:
//            parms         The parameters used by the 
//                        shortcode
//            context        The HTML enclosed by the opening 
//                        and closing shortcodes.
//
func ShortcodeHTML(params string, context string) string {
    return ("<div class='showcode'><pre type='syntaxhighlighter' class='brush: html'>" + context + "</pre></div>")
}

//
// Function:        ShortcodeCSS
//
// Description:        This shortcode is for surrounding a 
//                    code block and formatting it's look 
//                    and feel properly. This one is for a 
//                    CSS code block.
//
// Inputs:
//            parms         The parameters used by the 
//                        shortcode
//            context        The HTML enclosed by the opening 
//                        and closing shortcodes.
//
func ShortcodeCSS(params string, context string) string {
    return ("<div class='showcode'><pre type='syntaxhighlighter' class='brush: css'>" + context + "</pre></div>")
}

//
// Function:         LoadDefaultShortcodes
//
// Description:     This function is used to load in all 
//                    the default shortcodes.
//
// Inputs:
//
func LoadDefaultShortcodes() {
    AddShortCode("Box", ShortcodeBox)
    AddShortCode("Column1", ShortcodeColumn1)
    AddShortCode("Column2", ShortcodeColumn2)
    AddShortCode("php", ShortcodePHP)
    AddShortCode("javascript", ShortcodeJS)
    AddShortCode("html", ShortcodeHTML)
    AddShortCode("css", ShortcodeCSS)
}

代码的最后一部分定义了七个简单的简码,并使用LoadDefaultShortcodes()函数将它们添加到简码数组中。 如果您需要其他功能,则只需更改此代码,它将在您网站的任何位置进行更新。

goPressServer.go主程序文件

最后创建的文件是主程序文件。 在开发目录的顶部,创建文件goPressServer.go并放置以下信息:

package main

import (
    "./src/goPress"
)

//
// Function:         main
//
// Description:     This is the main function that is 
//                    called whenever the program is
//                     executed. It will load the globals, 
//                    set the different routes, and
//                     start the server.
//
// Inputs:
//
func main() {
    //
    // Load the default Shortcodes.
    //
    goPress.LoadDefaultShortcodes()

    //
    // Load all global variables.
    //
    goPress.GetGlobals()

    //
    // Setup the Default routes.
    //
    goPress.DefaultRoutes()

    //
    // Run the web server
    //
    goPress.StartServer(goPress.SiteData.ServerAddress)
}

main()函数是程序运行时调用的例程。 它将首先设置短代码,加载全局变量,设置默认路由,然后启动服务器。

编译并运行

要编译整个程序,请移至goPressServer.go文件所在的顶层目录,然后键入:

go build goPressServer.go

如果所有的文件都在的地方,它应该编译成goPressServer在Mac和Linux系统,并goPressServer.exe在Windows上。

在终端中运行goPressServer

在终端中执行该程序时,您将看到带有上述日期和时间的日志记录。

服务器首页

如果打开浏览器到服务器的地址,则将显示首页。 您将看到示例短代码和所使用的两个不同的Handlebars帮助器函数。 您现在拥有自己的Web服务器!

如您所知,我更改了首页,并向教程“ 构建CMS:结构”中给出的原始网站设计添加了三页。 我还在site/js/目录中添加了JavaScript库Syntax Highlighter ,以使用简码在网页上显示代码。

所有这些更改都是为了炫耀车把和短代码处理。 但是,由于语法突出显示在压缩方面无法正常工作,因此我从Gulp文件中删除了JavaScript压缩。 所有更改都在本教程的下载文件中。

有一门新的课程, 《构建Web服务器的Go基础》,对Go语言及其编程进行了很好的介绍。

结论

既然您知道如何使用go语言构建简单但功能强大的Web服务器,是时候开始尝试了。 创建新的页面,帖子,可嵌入的部分和简码。 这个简单的平台比使用WordPress快得多,而且完全由您控制。 在下面的评论中告诉我有关您的服务器的信息。

翻译自: https://code.tutsplus.com/tutorials/building-a-cms-gopress--cms-25073

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值