Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/twilikiss/go-blog
Go简易博客项目
https://github.com/twilikiss/go-blog
Last synced: 10 days ago
JSON representation
Go简易博客项目
- Host: GitHub
- URL: https://github.com/twilikiss/go-blog
- Owner: Twilikiss
- Created: 2024-04-07T08:35:20.000Z (8 months ago)
- Default Branch: master
- Last Pushed: 2024-04-16T09:50:32.000Z (7 months ago)
- Last Synced: 2024-04-16T13:22:32.515Z (7 months ago)
- Language: JavaScript
- Size: 4.72 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# 简易Go语言博客项目
参考了B站一个[up](https://b23.tv/7hXiHkL)的视频做出来的(~~应该也是哪个开源项目~~),原项目很糙,简介展示有致命Bug和很多小功能没有去实现。web框架搭建上也比较混乱,当然刚学完Go拿这个练练手绝对ok的
### 1. 项目展示
#### 1.1 首页
![image-20240416171907061](pictrues/image-20240416171907061.png)
#### 1.2 登陆页面
![image-20240416171930560](pictrues/image-20240416171930560.png)
#### 1.3 搜索功能
![image-20240416172047008](pictrues/image-20240416172047008.png)
#### 1.4 写作页面
![image-20240416172305488](pictrues/image-20240416172305488.png)
#### 1.5 支持图片上传
后台设置好服务器路径,本地上传图片到服务器会回显在md图片地址栏
![image-20240416172936195](pictrues/image-20240416172936195.png)
#### 1.6 支持自定义类别展示
![image-20240416173032990](pictrues/image-20240416173032990.png)
#### 1.7 文章详情页面展示
![image-20240416173150406](pictrues/image-20240416173150406.png)
![image-20240416173209636](pictrues/image-20240416173209636.png)
#### 1.8 整合文章评论区
![image-20240416173259891](pictrues/image-20240416173259891.png)
## 2. 原开源项目的Bug修正和项目补全
#### 2. 1 简介展示Bug
原代码关于简介是这样处理的
```go
// ... 这里的context就是数据库存储的md5转存为Html格式的数据,本质还是带html格式的文本
content := []rune(post.Content)
if len(content) > 100 {
content = content[0:100]
}
``` 如果context是html格式的文本,我们直接粗暴的截取`context = context[:100]`会导致获取到的html代码不完整,像标签不能闭合、与内容有关的数据少之又少等等问题,导致我们最后渲染到页面上乱码或者显示出错。
![img](https://cdn.nlark.com/yuque/0/2024/png/28956008/1711445255360-fe61ce36-fadf-4ccb-8c4d-d392c604627f.png)
我们的改进方案是将我们的简介“脱HTML化”,把所有html的标签和换行去掉,再来取前100个字符,另外为了方便和准确地解析中文字符,要用上`[]rune`(**关于rune的理解见文末**)
```go
content := utils.TrimHtml(post.Content)if len(content) >= 100 {
content = string([]rune(utils.TrimHtml(post.Content))[:100])
}
```![img](https://cdn.nlark.com/yuque/0/2024/png/28956008/1711445292506-9af92fa9-1db0-49f6-a038-e457d2e33a75.png)
#### 2.2 基于Redis的计数器和IP访问次数限制器
原项目的“观看次数”没有做,这边我打算使用Redis来实现我们的计数器,另外顺手也试试为这个项目加入一个防止用户恶意刷次数的IP访问次数限制。
##### 2.2.1 为啥用Redis?
因为Redis6.0引入**多线程I/O**,只是用来**处理网络数据的读写和协议的解析**,而**执行命令依旧是单线程**,从实现机制可以看出,Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。所以我们不需要去考虑控制 Key、Lua、事务,LPUSH/LPOP 等等的并发及线程安全问题。单个 Redis 命令的执行是原子性的。
##### 2.2.2 关键代码
先要使用Redis,你需要将自己的Redis的ip和port,以及密码和选定的数据库编号填入`config.toml`
![img](https://cdn.nlark.com/yuque/0/2024/png/28956008/1712480810932-c407ecc3-53ae-44b0-b917-6afa7aa4380e.png)
具体实现在`utils`中
```go
package utilsimport (
"fmt"
"github.com/go-redis/redis"
"go-blog/config"
"strconv"
)func NewClient() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: config.Cfg.Redis.Addr,
Password: config.Cfg.Redis.Password,
DB: config.Cfg.Redis.DataBase,
})// 通过 client.Ping() 来检查是否成功连接到了 redis 服务器
_, err := client.Ping().Result()
if err != nil {
panic(err)
}
return client
}func AddViews(client *redis.Client, articleId int) {
id := strconv.Itoa(articleId)
_, err := client.Incr(id).Result()
if err != nil {
panic(err)
}
}func GetViews(client *redis.Client, articleId int) int {
id := strconv.Itoa(articleId)
result, _ := client.Exists(id).Result()
if result != 0 {
val, err := client.Get(id).Result()
if err != nil {
panic(err)
}views, err := strconv.Atoi(val)
if err != nil {
panic(err)
}
return views
} else {
return 0
}}
func CountString(client *redis.Client, userip string) error {
val, err := client.Exists(userip).Result()
if err != nil {
return err
}if val == 0 { // no exists
_, err := client.Incr(userip).Result()
if err != nil {
return err
}_, err = client.Expire(userip, 5*1000*1000*1000*10).Result() //设置键的过期时间 10s
if err != nil {
return err
}} else { //exist
count, err := client.Get(userip).Result()
if err != nil {
return err
}temp, _ := strconv.Atoi(count)
fmt.Println(temp)if temp < 100 {
_, err = client.Incr(userip).Result()
if err != nil {
return err
}
} else {
return fmt.Errorf("访问次数受限")
}}
return nil
}
```Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于计数场景。
## PS : 如何理解和使用rune?
首先,rune是什么?
```go
type rune = int32
```rune is an alias for int32 and is equivalent to int32 in all ways. It is used, by convention, to distinguish character values from integer values.
rune 是 int32 的别名,在所有方面都等同于 int32。按照惯例,它用于区分字符值和整数值。
由于Go语言中采用的是统一的UTF-8编码,英文字母在底层占1个字节,特殊字符和中文汉字则占用1~3个字节,在Go语言中文的计数和截取并不如其他语言(比如Python)那么容易,所以Go提供了rune类型来处理中文的计数和分割问题,以支持国际化多语言。
下面我们通过一个例子来感受一下Go中使用rune的两个场景。
```go
package mainimport "fmt"
func main() {
s := "爱莉希雅"
fmt.Println(len(s)) //输出12
fmt.Println(len([]rune(s))) //输出4b := "abcd"
fmt.Println(len(b)) //输出4
fmt.Println(len([]rune(b))) //输出4
}
```当然,如果我们使用的是英文就不会出现这种情况,但是由于特殊字符和中文等占用1~3个字节
![img](https://cdn.nlark.com/yuque/0/2023/png/28956008/1695392650473-f1d93996-f795-4dcf-bb45-b86541449317.png) Unicode码点就是描述rune的值,可以根据上图去理解
Unicode为16进制,Unicode码点=Unicode的16进制转换为10进制
PS:我们也可以借助我们自己写的代码来解决这个问题
```go
func Index(s string, index int) string {
var i int
for _, data := range s {
if i == index {
return string(data)
}
i++
}
return ""
}
```要注意我们不能直接拿`range`生成的`index`来使用,因为那个index是指字符串底层byte数组的起始下标。