В предыдущей статье была представлена настройка подключения к базе данных и маршрутизация, в этой статье начинаются более важные функции, принимаются и анализируются md-файлы, затем сохраняется важная информация в базе данных и, наконец, заменяется md через интерфейс, представленный в виде html-строки.
Работа с интерфейсом загрузки файлов
После получения файла он начинает читать файл и сохраняет важную информацию в файле в базе данных, чтобы указанную информацию блога можно было просмотреть через ключевую информацию в будущем, а затем вернуть во внешний интерфейс для отображения.
Сначала мыcontrollers
Создайте контроллер в каталоге, который обрабатывает загрузку файлов:
controllers/uploadFile.go :
Сначала введите различные шаблоны, созданные с помощью xorm, а затем используйте их для хранения данных в базе данных.
package controllers
import (
"crypto/md5"
"errors"
"fmt"
...
"github.com/gin-gonic/gin"
"github.com/zachrey/blog/models"
)
const postDir = "./posts/" // 将文件方法放到一个指定的目录,如果文件过多可以放在现在各种云上面。
// UpLoadFile 上传文件的控制器
func UpLoadFile(c *gin.Context) {
// 指定数据的字段名,从该字段获取文件流
file, header, err := c.Request.FormFile("upload")
// 获取上传的文件名,存储的时候使用
filename := header.Filename
// 将文件名md5转一下,便于存储
md5FileName := fmt.Sprintf("%x", md5.Sum([]byte(filename)))
// 获取文件名的扩展名 .md
fileExt := filepath.Ext(postDir + filename)
// 文件的存储路径
filePath := postDir + md5FileName + fileExt
// 打印下日志
log.Println("[INFO] upload file: ", header.Filename)
// 判断放文件的目录下是否已经有存在相同文件
has := hasSameNameFile(md5FileName+fileExt, postDir)
if has {
c.JSON(http.StatusOK, gin.H{
"status": 1,
"msg": "服务器已有相同文件名称",
})
return
}
// 根据文件名的md5值,创建服务器上的文件
out, err := os.Create(filePath)
if err != nil {
log.Fatal(err)
}
defer out.Close()
// 将信息存入数据库操作。。。。
// 将信息存入数据库操作。。。。
// 将信息存入数据库操作。。。。
// 将信息存入数据库操作。。。。
c.JSON(http.StatusOK, gin.H{
"status": 0,
"msg": "上传成功",
})
}
func hasSameNameFile(fileName, dir string) bool {
files, _ := ioutil.ReadDir(dir)
// 遍历目录,是否存在相同的文件,这里有两种方式来查重,可以去数据库查询是否有相同的文件名也是可以的。
for _, f := range files {
if fileName == f.Name() {
return true
}
}
return false
}
Записать проанализированную информацию из файла в базу данных
Основная информация блога включает в себя: заголовок, категорию, метку и количество слов и т. д. Здесь оговаривается, как записать эту информацию в файл md, чтобы облегчить серверу анализ и последующее сохранение ее в базе данных. Указывает, что первые несколько строк файла md должны содержать следующую информацию:
title: 标题1
categories: 分类1, 分类2, 分类三
label: JS, ES6
---------------------------------------------
Информативность и основное содержание---------------------------------------------
разделять. Затем прочитайте файл, запишите данные в базу данных или напишите логику сохранения в базу данных в том же файле. Давайте завершим содержание. Оставленное пустым выше «сохранение в базе данных операции» является следующим содержанием.
controllers/uploadFile.go :
package controllers
import (
"crypto/md5"
"errors"
"fmt"
...
"github.com/gin-gonic/gin"
"github.com/zachrey/blog/models"
)
...
var gr sync.WaitGroup
var isShouldRemove = false
// UpLoadFile 上传文件的控制器
func UpLoadFile(c *gin.Context) {
...
// 根据文件名的md5值,创建服务器上的文件
out, err := os.Create(filePath)
if err != nil {
log.Fatal(err)
}
// 处理完整个上传过程后,是否需要删除创建的文件,在存在错误的情况下, 解析出错就删掉刚创建的文件。
//这个操作一定需要放在 out.close上面,因为创建的文件流,如果不关闭的话,
//无法进行删除操作,又因为defer栈的执行顺序,所以必须放在上面声明。
defer func() {
if isShouldRemove {
err = os.Remove(filePath)
if err != nil {
log.Println("[ERROR] ", err)
}
}
}()
// 关闭文件流,存储文件。
defer out.Close()
// 将上传文件的内容copy到新建的文件中,然后进行存储。
_, err = io.Copy(out, file)
if err != nil {
log.Fatal(err)
}
// 如果读取解析文件存在错误的话,则isShouldRemove复制为true,最后由defer进行删除
err = readMdFileInfo(filePath)
if err != nil {
isShouldRemove = true
c.JSON(http.StatusOK, gin.H{
"status": 1,
"msg": err.Error(),
})
return
}
... // 返回json数据,请看上一段的内容
}
// 读取文件信息,并写入数据库
func readMdFileInfo(filePath string) error {
// 读取文件
fileread, _ := ioutil.ReadFile(filePath)
// 将内容切割成每一行
lines := strings.Split(string(fileread), "\n")
// 文字内容以第5行往后开始数
body := strings.Join(lines[5:], "")
// 计算文字内容的文字个数
textAmount := GetStrLength(body)
log.Println(lines)
// 信息的标识
const (
TITLE = "title: "
CATEGORIES = "categories: "
LABEL = "label: "
)
var (
postId int64
postCh chan int64
categoryCh chan []int64
labelCh chan []int64
)
mdInfo := make(map[string]string)
/**
* 并发插入三组相互不特别依赖的信息
*/
for i, lens := 0, len(lines); i < lens && i < 5; i++ { // 只查找前五行
switch {
case strings.HasPrefix(lines[i], TITLE):
mdInfo[TITLE] = strings.TrimLeft(lines[i], TITLE)
postCh = make(chan int64)
// 存入数据库,存入成功返回插入的记录id,放入通道postCh中
go models.InsertPost(mdInfo[TITLE], filepath.Base(filePath), int64(textAmount), postCh)
case strings.HasPrefix(lines[i], CATEGORIES):
mdInfo[CATEGORIES] = strings.TrimLeft(lines[i], CATEGORIES)
categoryCh = make(chan []int64)
// 存入数据库
go models.InsertCategory(mdInfo[CATEGORIES], categoryCh)
case strings.HasPrefix(lines[i], LABEL):
mdInfo[LABEL] = strings.TrimLeft(lines[i], LABEL)
labelCh = make(chan []int64)
// 存入数据库
go models.InsertLabel(mdInfo[LABEL], labelCh)
}
}
/**
* 插入相互相关的信息
*/
postId = <-postCh //等待文章成功插入
if postId == 0 {
return errors.New("服务器上已有相同文章标题")
}
log.Println("[INFO] postId: ", postId)
// 插入文章与分类的关联信息,文字与分类关联表
if categoryCh != nil {
go func() {
categoryIds := <-categoryCh
log.Println("[INFO] categoryIds: ", categoryIds)
for _, v := range categoryIds {
models.InsertPostAndCategory(postId, v)
}
}()
}
// 插入文章与标签的关联信息,文字与标签关联表
if labelCh != nil {
go func() {
labels := <-labelCh
log.Println("[INFO] labels: ", labels)
for _, v := range labels {
models.InsertPostAndLabel(postId, v)
}
}()
}
return nil // 全部成功插入,则返回没有错误,如果存在错误,则往上返回
}
...
// GetStrLength 返回输入的字符串的字数,汉字和中文标点算 1 个字数,英文和其他字符 2 个算 1 个字数,不足 1 个算 1个
func GetStrLength(str string) float64 {
var total float64
reg := regexp.MustCompile("/·|,|。|《|》|‘|’|”|“|;|:|【|】|?|(|)|、/")
for _, r := range str {
if unicode.Is(unicode.Scripts["Han"], r) || reg.Match([]byte(string(r))) {
total = total + 1
} else {
total = total + 0.5
}
}
return math.Ceil(total)
}
используется здесьgoroutine
а также通道
Метод, постарайтесь лучше ознакомиться с характеристиками Go, используйте concurrent для вставки трех наборов несвязанных записей в базу данных, а затем подождите, пока три набора будут вставлены по отдельности, а затем последовательно вставьте взаимосвязанные записи.
конфигурация маршрутизации
routers/router.go:
package routers
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
ctrs "github.com/zachrey/blog/controllers"
"github.com/zachrey/blog/models"
)
...
func loadRouters(router *gin.Engine) {
...
router.POST("/upload", ctrs.UpLoadFile)
...
}
В этот момент мы используемpostman
Для проверки запустите службу, выберите File Upload.
наконец-то
Как поступить с загруженными файлами md, находится в центре внимания всей системы блога.После сохранения данных статьи вы можете делать различные API на основе этих данных. В следующем документе давайте обсудим, как реализовать инструмент cmd и загружать файлы. Если в нем есть какой-то код, который не совсем ясен, вы можете перейти кмой гитхаб, просмотрите полный исходный код и запустите его напрямую. Добро пожаловать, чтобы оставить сообщение или использовать электронную почту (zz__0123@163.com) Свяжитесь со мной для обсуждения, поделитесь практикой новичков, есть много недостатков, дайте пожалуйста еще совет.