go原生http开发简易blog(一)项目简介与搭建

news2024/11/28 8:49:07

文章目录

  • 一、项目简介
  • 二、项目搭建前置知识
  • 三、首页- - -前端文件与后端结构体定义
  • 四、配置文件加载
  • 五、构造假数据- - -显示首页内容

代码地址:https://gitee.com/lymgoforIT/goblog

一、项目简介

使用Go原生http开发一个简易的博客系统,包含一下功能:

  • 文章增删改查,文章列表分页显示
  • 评论系统
  • 文章分类、归档

学习本项目能学到什么?

  • 使用Go开发web项目基本思路,初步具有工程思维,如路由分组、目录组织,代码封装等。
  • 博客基本功能开发套路,如博客与评论的查询、分类、归档、分页等。
  • 循序渐进,掌握编程思维和思路。这一点是最重要的,会不断优化代码,而不是一步给出最终代码,这样更能培养编程思维和思路。

页面效果

在这里插入图片描述

二、项目搭建前置知识

接下来,我们会一步一步实现博客首页,开始我们可能会把代码都写到main.go中,后期再不断调整,优化,形成工程化的目录结构。

首先是启动一个http服务的代码,如下:

package main

import (
	"log"
	"net/http"
)

func main(){
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		writer.Write([]byte("ok"))
	})

	if err := server.ListenAndServe();err != nil {
		log.Println(err)
	}
}

启动后,浏览器访问,可以看到ok字样
在这里插入图片描述

这里返回给前端的不过是一个字符串而已,实际工作中一般前后端交互都是使用的JSON数据或者protoc协议的,那么想要返回JSON字符串给前端,又该如何做呢?

其实也简单,设置好请求头即可,如下:

package main

import (
	"encoding/json"
	"log"
	"net/http"
)

type IndexData struct {
	Title string `json:"title"`
	Desc  string `json:"desc"`
}

func index(w http.ResponseWriter, r *http.Request) {
	// 设置请求头,指明返回的是JSON数据
	w.Header().Set("Content-Type", "application/json")
	var indexData IndexData
	indexData.Title = "go博客"
	indexData.Desc = "入门学习笔记"
	jsonStr, _ := json.Marshal(indexData)
	w.Write(jsonStr)
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/", index)

	if err := server.ListenAndServe(); err != nil {
		log.Println(err)
	}
}

在这里插入图片描述

目前浏览器得到的响应结果都是后端直接返回的数据,但我们前端是给用户使用的,需要有一定页面才行。

Go中渲染html页面,可以使用Go自带的html/template库,使用方式如下:

首先建立template/index.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
hello lym blog!!
<br>
标题:{{.Title}}
<br>
描述:{{.Desc}}
</body>
</html>
package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
)

type IndexData struct {
	Title string `json:"title"`
	Desc  string `json:"desc"`
}

func index(w http.ResponseWriter, r *http.Request) {
	// 设置请求头,指明返回的是JSON数据
	w.Header().Set("Content-Type", "application/json")
	var indexData IndexData
	indexData.Title = "go博客"
	indexData.Desc = "入门学习笔记"
	jsonStr, _ := json.Marshal(indexData)
	w.Write(jsonStr)
}

func indexHtml(w http.ResponseWriter, r *http.Request) {
	// 使用给定的名字分配一个html模板
	t := template.New("index.html")
	viewPath, _ := os.Getwd()
	// 将html文件关联到模板上
	t, _ = t.ParseFiles(viewPath + "/template/index.html")
	var indexData IndexData
	indexData.Title = "go博客"
	indexData.Desc = "入门学习笔记"
	// 使用给定的数据结构解析模板,并将结果写入w
	err := t.Execute(w, indexData)
	fmt.Println(err)
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/", index)
	http.HandleFunc("/index", indexHtml)

	if err := server.ListenAndServe(); err != nil {
		log.Println(err)
	}
}

此时访问index路径,便会看到是页面展示了,不过还没有css,js等静态文件渲染,后期会慢慢加上。
在这里插入图片描述

三、首页- - -前端文件与后端结构体定义

有了前面的基础知识,我们现在就可以搭建首页啦!

因为本学习笔记主要注重的是后端逻辑,所以前端页面和静态文件等是直接用的已有的,放到项目目录下即可。如果有需要,可以到码云上取:https://gitee.com/lymgoforIT/goblog

在这里插入图片描述

首页展示时,有很多数据是从后端获取的,所以我们首先需要定义一些结构体,用于数据渲染,其中有一些通用数据,且基本不怎么变的,我们可以放到配置文件中,而不用存DB,比如博客系统的标题、标签以及相关系统变量等。
在这里插入图片描述

config/config.go

package config

type Viewer struct {
	Title       string   // 首页标题
	Description string   // 首页描述
	Logo        string   // 博客系统logo
	Navigation  []string // 导航栏
	Bilibili    string   // bilibili
	Avatar      string   // 头像
	UserName    string   // 用户名
	UserDesc    string   // 用户描述
}

// 系统相关配置
type SystemConfig struct {
	AppName         string
	Version         float32
	CurrentDir      string // 项目路径
	CdnURL          string // cdn地址,用户缓存静态资源
	QiniuAccessKey  string // 使用七牛云存储图片资源
	QiniuSecretKey  string
	Valine          bool // 评论系统用的Valine
	ValineAppid     string
	ValineAppkey    string
	ValineServerURL string
}

其余数据基本是从DB获取的,与DB交互的结构体,我们一般会单独建立model文件夹存放。
models/post.go

package models

import (
	"goblog/config"
	"html/template"
	"time"
)

// 用于与DB交互,与DB中字段一一对应
type Post struct {
	Pid        int       `json:"pid"`        // 文章ID
	Title      string    `json:"title"`      // 文章标题
	Slug       string    `json:"slug"`       // 自定义页面 path
	Content    string    `json:"content"`    // 文章的html
	Markdown   string    `json:"markdown"`   // 文章的Markdown
	CategoryId int       `json:"categoryId"` //分类id
	UserId     int       `json:"userId"`     //用户id
	ViewCount  int       `json:"viewCount"`  //查看次数
	Type       int       `json:"type"`       //文章类型 0 普通,1 自定义文章
	CreateAt   time.Time `json:"createAt"`   // 创建时间
	UpdateAt   time.Time `json:"updateAt"`   // 更新时间
}

// 用于给前端响应,所以有了分类名称和用户名等信息,而不是分类id或者用户id
type PostMore struct {
	Pid          int           `json:"pid"`          // 文章ID
	Title        string        `json:"title"`        // 文章标题
	Slug         string        `json:"slug"`         // 自定义页面 path
	Content      template.HTML `json:"content"`      // 文章的html
	CategoryId   int           `json:"categoryId"`   // 文章的Markdown
	CategoryName string        `json:"categoryName"` // 分类名
	UserId       int           `json:"userId"`       // 用户id
	UserName     string        `json:"userName"`     // 用户名
	ViewCount    int           `json:"viewCount"`    // 查看次数
	Type         int           `json:"type"`         // 文章类型 0 普通,1 自定义文章
	CreateAt     string        `json:"createAt"`
	UpdateAt     string        `json:"updateAt"`
}

type PostReq struct {
	Pid        int    `json:"pid"`
	Title      string `json:"title"`
	Slug       string `json:"slug"`
	Content    string `json:"content"`
	Markdown   string `json:"markdown"`
	CategoryId int    `json:"categoryId"`
	UserId     int    `json:"userId"`
	Type       int    `json:"type"`
}

type SearchResp struct {
	Pid   int    `orm:"pid" json:"pid"` // 文章ID
	Title string `orm:"title" json:"title"`
}

type PostRes struct {
	config.Viewer
	config.SystemConfig
	Article PostMore
}

models/home.go

用于封装前端需要的首页数据

package models

import "goblog/config"

type HomeResponse struct {
	config.Viewer
	Categorys []Category
	Posts     []PostMore
	Total     int
	Page      int
	Pages     []int
	PageEnd   bool // 当前页是否是最后一页,决定分页那里是否显示左右箭头
}

四、配置文件加载

这里配置文件我们用的toml,实际工作中可能yaml用的更多一点。

config/config.toml

[viewer]
    Title = "Go语言博客"
    Description = "Go语言博客"
    Logo = "/resource/images/logo.png"
    Navigation = ["首页","/", "GO语言","/golang", "归档","/pigeonhole", "关于","/about"]
    Bilibili = "https://space.bilibili.com/473844125"
    Zhihu = "https://www.zhihu.com/people/ma-shen-zhi-lu"
    Avatar = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F13147603927%2F1000.jpg&refer=http%3A%2F%2Finews.gtimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1647242040&t=c6108010ed46b4acebe18955acdd2d24"
    UserName = "张三"
    UserDesc = "长得非常帅的程序员"
[system]
    CdnURL = "https://static.mszlu.com/goblog/es6/md-assets"
    QiniuAccessKey = "替换自己的"
    QiniuSecretKey = "替换自己的"
    Valine = true
    ValineAppid = "替换自己的"
    ValineAppkey = "替换自己的"
    ValineServerURL = "替换自己的"

toml文件我们可以使用github.com/BurntSushi/toml包,获取

 go get github.com/BurntSushi/toml

配置文件加载,常规套路是定义全局变量,使用init加载

config/config.go

package config

import (
	"os"

	"github.com/BurntSushi/toml"
)

var Cfg *TomlConfig

func init() {
	Cfg = new(TomlConfig)
	var err error
	Cfg.System.CurrentDir, err = os.Getwd()
	if err != nil {
		panic(any(err))
	}
	Cfg.System.AppName = "lym-go-blog"
	Cfg.System.Version = 1.0
	_, err = toml.DecodeFile("config/config.toml", &Cfg)
	if err != nil {
		panic(any(err))
	}
}

type TomlConfig struct {
	Viewer Viewer
	System SystemConfig
}

type Viewer struct {
	Title       string   // 首页标题
	Description string   // 首页描述
	Logo        string   // 博客系统logo
	Navigation  []string // 导航栏
	Bilibili    string   // bilibili
	Avatar      string   // 头像
	UserName    string   // 用户名
	UserDesc    string   // 用户描述
}

// 系统相关配置
type SystemConfig struct {
	AppName         string
	Version         float32
	CurrentDir      string // 项目路径
	CdnURL          string // cdn地址,用户缓存静态资源
	QiniuAccessKey  string // 使用七牛云存储图片资源
	QiniuSecretKey  string
	Valine          bool // 评论系统用的Valine
	ValineAppid     string
	ValineAppkey    string
	ValineServerURL string
}

五、构造假数据- - -显示首页内容

main.go

注意看代码注释

package main

import (
	"goblog/config"
	"goblog/models"
	"html/template"
	"log"
	"net/http"
	"time"
)

type IndexData struct {
	Title string `json:"title"`
	Desc  string `json:"desc"`
}

// 前端html页面中使用了一些函数,所以这里需要定义一下
// 是否偶数
func IsODD(num int) bool {
	return num%2 == 0
}

func GetNextName(strs []string, index int) string {
	return strs[index+1]
}

// 日期按指定格式转换
func Date(layout string) string {
	return time.Now().Format(layout)
}

func index(w http.ResponseWriter, r *http.Request) {
	t := template.New("index.html")
	// 拿到当前的路径
	path := config.Cfg.System.CurrentDir
	//访问博客首页模板的时候,因为有多个模板的嵌套,解析文件的时候,需要将其涉及到的所有模板都进行解析
	home := path + "/template/home.html"
	header := path + "/template/layout/header.html"
	footer := path + "/template/layout/footer.html"
	personal := path + "/template/layout/personal.html"
	post := path + "/template/layout/post-list.html"
	pagination := path + "/template/layout/pagination.html" // 页码

	// 定义模板中需要用到的函数
	t.Funcs(template.FuncMap{"isODD": IsODD, "getNextName": GetNextName, "date": Date})
	t, err := t.ParseFiles(path+"/template/index.html", home, header, footer, personal, post, pagination)
	if err != nil {
		log.Println(err)
	}

	//页面上涉及到的所有的数据,必须有定义
	var categorys = []models.Category{
		{
			Cid:  1,
			Name: "go",
		},
		{
			Cid:  2,
			Name: "python",
		},
	}

	var posts = []models.PostMore{
		{
			Pid:          1,
			Title:        "go博客",
			Content:      "这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容",
			UserName:     "张三",
			ViewCount:    123,
			CreateAt:     "2023-12-17",
			CategoryId:   1,
			CategoryName: "go",
			Type:         0,
		},
		{
			Pid:          2,
			Title:        "这是第二篇博客",
			Content:      "这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容这里是内容",
			UserName:     "李四",
			ViewCount:    1314,
			CreateAt:     "2023-12-17",
			CategoryId:   1,
			CategoryName: "go",
			Type:         0,
		},
	}
	var hr = &models.HomeResponse{
		Viewer:    config.Cfg.Viewer,
		Categorys: categorys,
		Posts:     posts,
		Total:     2,
		Page:      1,
		Pages:     []int{1},
		PageEnd:   true,
	}
	t.Execute(w, hr)
}

func main() {
	//程序入口,一个项目 只能有一个入口
	//web程序,http协议 ip port
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/", index)

	// 因为静态文件放到了public/resource目录下,但是页面中写路径的时候都写的resource,所以这里转一下
	http.Handle("/resource/", http.StripPrefix("/resource/", http.FileServer(http.Dir("public/resource/"))))
	if err := server.ListenAndServe(); err != nil {
		log.Println(err)
	}
}

此时通过流量器访问8080端口,便可看到如下界面了

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1318647.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CCF-CSP真题《202309-5 阻击》思路+ c++满分题解

想查看其他题的真题及题解的同学可以前往查看&#xff1a;CCF-CSP真题附题解大全 试题编号&#xff1a;202309-5试题名称&#xff1a;阻击时间限制&#xff1a;2.0s内存限制&#xff1a;512.0MB问题描述&#xff1a; 问题描述 上回提到&#xff0c;西西艾弗岛下方有一个庞大的遗…

PyTorch自动梯度计算(注意点)

if params.grad is not None: params.grad.zero_() 我们实际的运算往往会涉及到若干个requires-grad为true的张量进行运算&#xff0c;在这种情况下&#xff0c;Pytorch会计算整个计算图上的损失的导数&#xff0c;并把这些结果累加到grad属性中。多次调用backward()会导致梯度…

chrome升级后,调试vue在控制台输出总是显示cjs.js

当前chrome版本120.0.6099.72 在vue中使用console.log输出时&#xff0c;总是显示cjs.js多少多少行&#xff0c;不能显示源文件名及行数 【解决方案】 打开控制台的设置 左侧找到“Ignore List”&#xff0c;取消勾选"enable Lgnore Listing"&#xff0c;并重启chr…

基于C/C++的非系统库自定义读写ini配置

INI文件由节、键、值组成。 节 [section] 参数 &#xff08;键值&#xff09; namevalue 这里将常用的操作方式封装成了一个dll供外部使用 // 下列 ifdef 块是创建使从 DLL 导出更简单的 // 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 LIBCFG_EXPORTS // 符号…

紫光FPGA DDR3 IP使用和注意事项(axi4协议)

紫光DDR3 IP使用 对于紫光ddr3 IP核的使用需要注意事情。 阅读ddr ip手册&#xff1a; 1、注意&#xff1a;对于写地址通道&#xff0c;axi_awvalid要一直拉高&#xff0c;axi_awready才会拉高。使用的芯片型号时PG2L100H-6FBG676&#xff0c;不同的型号IP核接口和axi的握手协…

wireshark下载安装

下载 Wireshark Download 等待下载完成 安装 双击 下面的一定垚勾选上 下图的也一定要勾选上 修改为不重启&#xff0c;不需要重启也是正常的

嵌入式中的门电路详讲

NOT门电路 NOT(非门)是数字逻辑电路中的一种基本逻辑门,也称为反相器。它执行的是逻辑非操作,即将输入信号取反。NOT门具有一个输入和一个输出。 A输入,B输出,以下是真值表: A B 0 1 1 0 AND门电路 AND(与门)是数字逻辑电路中的一种基本逻辑门,用于执行逻辑与操作。…

从 0 开始实现一个 SpringBoot + Vue 项目

从 0 开始实现一个 SpringBoot Vue 项目 从 0 开始实现一个 SpringBoot Vue 项目软件和工具创建 SpringBoot 后端项目创建 MySQL 数据库配置文件实现增删改查接口Model 层mapper 层service 层controller 层测试 从 0 开始实现一个 SpringBoot Vue 项目 软件和工具 后端开发…

jmeter如何循环运行到csv文件最后一行后停止

1、首先在线程组中设置’循环次数‘–勾选永远 2、csv数据文件设置中设置&#xff1a; 遇到文件结束符再次循环?——改为&#xff1a;False 遇到文件结束符停止线程?——改为&#xff1a;True 3、再次运行就会根据文档的行数运行数据 &#xff08;如果需要在循环控制器中&…

[python]用python获取EXCEL文件内容并保存到DBC

目录 关键词平台说明背景所需库实现过程方法1.1.安装相关库2.代码实现 关键词 python、excel、DBC、openpyxl 平台说明 项目Valuepython版本3.6 背景 在搭建自动化测试平台的时候经常会提取DBC文件中的信息并保存为excel或者其他文件格式&#xff0c;用于自动化测试。本文…

STL容器之string(上)

目录 什么是STL string类 string类常见接口 string类的常见构造函数 string类对象的容器操作 string类对象的访问及遍历操作 string类对象的修改操作 拓展 从本期开始&#xff0c;我们将正式学习C中的STL&#xff0c;美国的麦克阿瑟将军说过&#xff1a;“C不能没有STL就…

轻量封装WebGPU渲染系统示例<52>- Json数据描述材质、纹理等3D渲染场景信息

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/material/src/voxgpu/sample/DataDrivenScene3.ts 当前示例运行效果: ​​​​​​​ Json数据: {"renderer": {"mtplEnabled": true,"camera": {"eye&quo…

Linux实操——安装Mysql

安装Mysql 一、检查是否已经安装了mariadb数据库,并卸载二、下载mysql包&#xff0c;并通过ftp上传到服务器三、解压安装包四、创建数据存储文件夹五、创建执行mysqld命令的用户&#xff0c;并初始化mysql六、启用传输安全七、启动mysql&#xff0c;验证是否安装成功 总结 博主…

若依 ruoyi-vue3+ts-plus+ 宇道 yudao-ruoyi-vue-pro前端显示部门名称

想在 index.vue 上显示列表里对应的部门名称 效果 引入dept import * as DeptApi from "/api/system/dept"; 初始化时 /** 初始化 **/ onMounted(async () > {getList();deptNameList.value await DeptApi.getSimpleDeptList(); }) 加载id 对应的部门名称 …

威联通硬盘休眠后修改系统定时任务

按照网上一些教程&#xff0c;成功将威联通的机械硬盘设置了自动休眠。但是发现每天有多个整点硬盘会自动唤醒&#xff0c;怀疑是系统内置的定时任务触发了硬盘唤醒。 通过查看系统日志中事件和访问记录&#xff0c;判断出一些引发硬盘唤醒的自动任务&#xff0c;将这些定时任…

机器学习项目精选 第一期:超完整数据科学资料合集

大噶吼&#xff0c;不说废话&#xff0c;分享一波我最近看过并觉得非常硬核的资源&#xff0c;包括Python、机器学习、深度学习、大模型等等。 1、超完整数据科学资料合集 地址&#xff1a;https://github.com/krishnaik06/The-Grand-Complete-Data-Science-Materials Pytho…

【基础算法】试除法判定质数(优化)

文章目录 算法优化模板题目代码实现 算法优化模板 bool is_prime(int n){if(n < 2) return false;for(int i 2;i < n / i;i ){ //优化内容if(n % i 0){return false;}}return true; }注意这里的一个总要优化是for循环的终止条件是i<n/i。为什么不是i<n或者i<…

Java EE 网络之网络初识

文章目录 1. 网络发展史1.1 独立模式1.2 网络互连1.3 局域网 LAN1.4 广域网 WAN 2. 网络通信基础2.1 IP 地址2.2 端口号2.3 认识协议2.4 五元组2.5 协议分层2.5.1 什么是协议分层2.5.2 分层的作用2.5.3 OSI七层协议2.5.4 TCP/IP五层协议2.5.5 网络设备所在分层 2.6 分装和分用 …

10天玩转Python第9天:python 面向对象 全面详解与代码示例

今日内容 异常 模块和包 导入模块(导包)if __name__ "__main__": Unitest 框架的学习 了解, 基本组成 异常 异常传递[了解] 异常传递是 Python 中已经实现好了,我们不需要操作, 我们知道异常会进行传递. ​ 异常传递: 在函数嵌套调用的过程中, 被调用的函数 ,发…

获取MODIS的NDVI/EVI产品

目录 简介源代码运行流程参考博文 简介 本项目是使用MODIS的NDVI产品&#xff08;MOD13Q1&#xff09;,可获取从2000年至今的所有数据&#xff0c;更新频率为16天 MOD13Q1 V6.1产品以每像素为基础提供植被指数(VI)值。这里有两个主要的植被层。第一种是归一化植被指数(NDVI)&a…