Используйте go для создания личного блога (3): сервер анализирует файлы md

задняя часть база данных Go GitHub
Используйте go для создания личного блога (3): сервер анализирует файлы md

В предыдущей статье была представлена ​​настройка подключения к базе данных и маршрутизация, в этой статье начинаются более важные функции, принимаются и анализируются 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) Свяжитесь со мной для обсуждения, поделитесь практикой новичков, есть много недостатков, дайте пожалуйста еще совет.