This commit is contained in:
xing 2021-05-28 18:40:39 +08:00
commit 866e90b251
23 changed files with 1928 additions and 0 deletions

29
configs/config.yaml Normal file
View File

@ -0,0 +1,29 @@
Server:
RunMode: debug
HttpPort: 8080
ReadTimeout: 60
WriteTimeout: 60
App:
DefaultPageSize: 10
MaxPageSize: 100
LogSavePath: storage/logs
LogFileName: app
LogFileExt: .log
UploadSavePath: storage/uploads
UploadServerUrl: "http://127.0.0.1:8080/static"
UploadImageMaxSize: 5 # MB
UploadImageAllowExts:
- .jpg
- .jpeg
- .png
Database:
DBType: mysql
Username: xing # 填写你的数据库账号
Password: xing # 填写你的数据库密码
Host: 127.0.0.1:3306
DBName: tt
TablePrefix: blog_
Charset: utf8mb4
ParseTime: True
MaxIdleConns: 10
MaxOpenConns: 30

422
docs/docs.go Normal file
View File

@ -0,0 +1,422 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag
package docs
import (
"bytes"
"encoding/json"
"strings"
"github.com/alecthomas/template"
"github.com/swaggo/swag"
)
var doc = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{.Description}}",
"title": "{{.Title}}",
"termsOfService": "https://github.com/go-programming-tour-book",
"contact": {},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/api/v1/tags": {
"get": {
"produces": [
"application/json"
],
"summary": "获取多个标签",
"parameters": [
{
"maxLength": 100,
"type": "string",
"description": "标签名称",
"name": "name",
"in": "query"
},
{
"enum": [
0,
1
],
"type": "integer",
"default": 1,
"description": "状态",
"name": "state",
"in": "query"
},
{
"type": "integer",
"default": 1,
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"default": 10,
"description": "每页数量",
"name": "page_size",
"in": "query"
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"$ref": "#/definitions/model.TagSwagger"
}
},
"400": {
"description": "请求错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
},
"500": {
"description": "内部错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
}
}
},
"post": {
"produces": [
"application/json"
],
"summary": "新增标签",
"parameters": [
{
"maxLength": 100,
"minLength": 3,
"type": "string",
"description": "标签名称",
"name": "name",
"in": "formData",
"required": true
},
{
"enum": [
0,
1
],
"type": "integer",
"default": 1,
"description": "状态",
"name": "state",
"in": "formData"
},
{
"maxLength": 100,
"minLength": 3,
"type": "string",
"description": "创建者",
"name": "created_by",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"$ref": "#/definitions/model.TagSwagger"
}
},
"400": {
"description": "请求错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
},
"500": {
"description": "内部错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
}
}
}
},
"/api/v1/tags/{id}": {
"put": {
"produces": [
"application/json"
],
"summary": "更新标签",
"parameters": [
{
"type": "integer",
"description": "标签 ID",
"name": "id",
"in": "path",
"required": true
},
{
"minLength": 3,
"type": "string",
"description": "标签名称",
"name": "name",
"in": "formData"
},
{
"enum": [
0,
1
],
"type": "integer",
"default": 1,
"description": "状态",
"name": "state",
"in": "formData"
},
{
"maxLength": 100,
"minLength": 3,
"type": "string",
"description": "修改者",
"name": "modified_by",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/model.TagSwagger"
}
}
},
"400": {
"description": "请求错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
},
"500": {
"description": "内部错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
}
}
},
"delete": {
"produces": [
"application/json"
],
"summary": "删除标签",
"parameters": [
{
"type": "integer",
"description": "标签 ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"type": "string"
}
},
"400": {
"description": "请求错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
},
"500": {
"description": "内部错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
}
}
}
},
"/upload/file": {
"post": {
"produces": [
"application/json"
],
"summary": "上传图片",
"parameters": [
{
"enum": [
1,
2,
3
],
"type": "integer",
"description": "文件类型1图片",
"name": "type",
"in": "formData",
"required": true
},
{
"type": "file",
"description": "文件",
"name": "state",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"$ref": "#/definitions/service.FileInfo"
}
},
"400": {
"description": "请求错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
},
"500": {
"description": "内部错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
}
}
}
}
},
"definitions": {
"app.Pager": {
"type": "object",
"properties": {
"page": {
"type": "integer"
},
"page_size": {
"type": "integer"
},
"total_rows": {
"type": "integer"
}
}
},
"errorcode.Error": {
"type": "object"
},
"model.Tag": {
"type": "object",
"properties": {
"created_by": {
"type": "string"
},
"created_on": {
"type": "integer"
},
"deleted_on": {
"type": "integer"
},
"id": {
"type": "integer"
},
"is_del": {
"type": "integer"
},
"modified_by": {
"type": "string"
},
"modified_on": {
"type": "integer"
},
"name": {
"type": "string"
},
"state": {
"type": "integer"
}
}
},
"model.TagSwagger": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"$ref": "#/definitions/model.Tag"
}
},
"pager": {
"$ref": "#/definitions/app.Pager"
}
}
},
"service.FileInfo": {
"type": "object",
"properties": {
"accessUrl": {
"type": "string"
},
"name": {
"type": "string"
}
}
}
}
}`
type swaggerInfo struct {
Version string
Host string
BasePath string
Schemes []string
Title string
Description string
}
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = swaggerInfo{
Version: "1.0",
Host: "",
BasePath: "",
Schemes: []string{},
Title: "博客系统",
Description: "Go 语言编程之旅:一起用 Go 做项目",
}
type s struct{}
func (s *s) ReadDoc() string {
sInfo := SwaggerInfo
sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
t, err := template.New("swagger_info").Funcs(template.FuncMap{
"marshal": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
}).Parse(doc)
if err != nil {
return doc
}
var tpl bytes.Buffer
if err := t.Execute(&tpl, sInfo); err != nil {
return doc
}
return tpl.String()
}
func init() {
swag.Register(swag.Name, &s{})
}

358
docs/swagger.json Normal file
View File

@ -0,0 +1,358 @@
{
"swagger": "2.0",
"info": {
"description": "Go 语言编程之旅:一起用 Go 做项目",
"title": "博客系统",
"termsOfService": "https://github.com/go-programming-tour-book",
"contact": {},
"version": "1.0"
},
"paths": {
"/api/v1/tags": {
"get": {
"produces": [
"application/json"
],
"summary": "获取多个标签",
"parameters": [
{
"maxLength": 100,
"type": "string",
"description": "标签名称",
"name": "name",
"in": "query"
},
{
"enum": [
0,
1
],
"type": "integer",
"default": 1,
"description": "状态",
"name": "state",
"in": "query"
},
{
"type": "integer",
"default": 1,
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"default": 10,
"description": "每页数量",
"name": "page_size",
"in": "query"
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"$ref": "#/definitions/model.TagSwagger"
}
},
"400": {
"description": "请求错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
},
"500": {
"description": "内部错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
}
}
},
"post": {
"produces": [
"application/json"
],
"summary": "新增标签",
"parameters": [
{
"maxLength": 100,
"minLength": 3,
"type": "string",
"description": "标签名称",
"name": "name",
"in": "formData",
"required": true
},
{
"enum": [
0,
1
],
"type": "integer",
"default": 1,
"description": "状态",
"name": "state",
"in": "formData"
},
{
"maxLength": 100,
"minLength": 3,
"type": "string",
"description": "创建者",
"name": "created_by",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"$ref": "#/definitions/model.TagSwagger"
}
},
"400": {
"description": "请求错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
},
"500": {
"description": "内部错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
}
}
}
},
"/api/v1/tags/{id}": {
"put": {
"produces": [
"application/json"
],
"summary": "更新标签",
"parameters": [
{
"type": "integer",
"description": "标签 ID",
"name": "id",
"in": "path",
"required": true
},
{
"minLength": 3,
"type": "string",
"description": "标签名称",
"name": "name",
"in": "formData"
},
{
"enum": [
0,
1
],
"type": "integer",
"default": 1,
"description": "状态",
"name": "state",
"in": "formData"
},
{
"maxLength": 100,
"minLength": 3,
"type": "string",
"description": "修改者",
"name": "modified_by",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/model.TagSwagger"
}
}
},
"400": {
"description": "请求错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
},
"500": {
"description": "内部错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
}
}
},
"delete": {
"produces": [
"application/json"
],
"summary": "删除标签",
"parameters": [
{
"type": "integer",
"description": "标签 ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"type": "string"
}
},
"400": {
"description": "请求错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
},
"500": {
"description": "内部错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
}
}
}
},
"/upload/file": {
"post": {
"produces": [
"application/json"
],
"summary": "上传图片",
"parameters": [
{
"enum": [
1,
2,
3
],
"type": "integer",
"description": "文件类型1图片",
"name": "type",
"in": "formData",
"required": true
},
{
"type": "file",
"description": "文件",
"name": "state",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"$ref": "#/definitions/service.FileInfo"
}
},
"400": {
"description": "请求错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
},
"500": {
"description": "内部错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
}
}
}
}
},
"definitions": {
"app.Pager": {
"type": "object",
"properties": {
"page": {
"type": "integer"
},
"page_size": {
"type": "integer"
},
"total_rows": {
"type": "integer"
}
}
},
"errorcode.Error": {
"type": "object"
},
"model.Tag": {
"type": "object",
"properties": {
"created_by": {
"type": "string"
},
"created_on": {
"type": "integer"
},
"deleted_on": {
"type": "integer"
},
"id": {
"type": "integer"
},
"is_del": {
"type": "integer"
},
"modified_by": {
"type": "string"
},
"modified_on": {
"type": "integer"
},
"name": {
"type": "string"
},
"state": {
"type": "integer"
}
}
},
"model.TagSwagger": {
"type": "object",
"properties": {
"list": {
"type": "array",
"items": {
"$ref": "#/definitions/model.Tag"
}
},
"pager": {
"$ref": "#/definitions/app.Pager"
}
}
},
"service.FileInfo": {
"type": "object",
"properties": {
"accessUrl": {
"type": "string"
},
"name": {
"type": "string"
}
}
}
}
}

241
docs/swagger.yaml Normal file
View File

@ -0,0 +1,241 @@
definitions:
app.Pager:
properties:
page:
type: integer
page_size:
type: integer
total_rows:
type: integer
type: object
errorcode.Error:
type: object
model.Tag:
properties:
created_by:
type: string
created_on:
type: integer
deleted_on:
type: integer
id:
type: integer
is_del:
type: integer
modified_by:
type: string
modified_on:
type: integer
name:
type: string
state:
type: integer
type: object
model.TagSwagger:
properties:
list:
items:
$ref: '#/definitions/model.Tag'
type: array
pager:
$ref: '#/definitions/app.Pager'
type: object
service.FileInfo:
properties:
accessUrl:
type: string
name:
type: string
type: object
info:
contact: { }
description: Go 语言编程之旅:一起用 Go 做项目
termsOfService: https://github.com/go-programming-tour-book
title: 博客系统
version: "1.0"
paths:
/api/v1/tags:
get:
parameters:
- description: 标签名称
in: query
maxLength: 100
name: name
type: string
- default: 1
description: 状态
enum:
- 0
- 1
in: query
name: state
type: integer
- default: 1
description: 页码
in: query
name: page
type: integer
- default: 10
description: 每页数量
in: query
name: page_size
type: integer
produces:
- application/json
responses:
"200":
description: 成功
schema:
$ref: '#/definitions/model.TagSwagger'
"400":
description: 请求错误
schema:
$ref: '#/definitions/errorcode.Error'
"500":
description: 内部错误
schema:
$ref: '#/definitions/errorcode.Error'
summary: 获取多个标签
post:
parameters:
- description: 标签名称
in: formData
maxLength: 100
minLength: 3
name: name
required: true
type: string
- default: 1
description: 状态
enum:
- 0
- 1
in: formData
name: state
type: integer
- description: 创建者
in: formData
maxLength: 100
minLength: 3
name: created_by
required: true
type: string
produces:
- application/json
responses:
"200":
description: 成功
schema:
$ref: '#/definitions/model.TagSwagger'
"400":
description: 请求错误
schema:
$ref: '#/definitions/errorcode.Error'
"500":
description: 内部错误
schema:
$ref: '#/definitions/errorcode.Error'
summary: 新增标签
/api/v1/tags/{id}:
delete:
parameters:
- description: 标签 ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: 成功
schema:
type: string
"400":
description: 请求错误
schema:
$ref: '#/definitions/errorcode.Error'
"500":
description: 内部错误
schema:
$ref: '#/definitions/errorcode.Error'
summary: 删除标签
put:
parameters:
- description: 标签 ID
in: path
name: id
required: true
type: integer
- description: 标签名称
in: formData
minLength: 3
name: name
type: string
- default: 1
description: 状态
enum:
- 0
- 1
in: formData
name: state
type: integer
- description: 修改者
in: formData
maxLength: 100
minLength: 3
name: modified_by
required: true
type: string
produces:
- application/json
responses:
"200":
description: 成功
schema:
items:
$ref: '#/definitions/model.TagSwagger'
type: array
"400":
description: 请求错误
schema:
$ref: '#/definitions/errorcode.Error'
"500":
description: 内部错误
schema:
$ref: '#/definitions/errorcode.Error'
summary: 更新标签
/upload/file:
post:
parameters:
- description: 文件类型1图片
enum:
- 1
- 2
- 3
in: formData
name: type
required: true
type: integer
- description: 文件
in: formData
name: state
required: true
type: file
produces:
- application/json
responses:
"200":
description: 成功
schema:
$ref: '#/definitions/service.FileInfo'
"400":
description: 请求错误
schema:
$ref: '#/definitions/errorcode.Error'
"500":
description: 内部错误
schema:
$ref: '#/definitions/errorcode.Error'
summary: 上传图片
swagger: "2.0"

7
global/db.go Normal file
View File

@ -0,0 +1,7 @@
package global
import "github.com/jinzhu/gorm"
var (
DBEngine *gorm.DB
)

13
global/setting.go Normal file
View File

@ -0,0 +1,13 @@
package global
import (
"blog/pkg/logger"
"blog/pkg/setting"
)
var (
ServerSetting *setting.ServerSettingS
AppSetting *setting.AppSettingS
DatabaseSetting *setting.DatabaseSettingS
Logger *logger.Logger
)

46
go.mod Normal file
View File

@ -0,0 +1,46 @@
module blog
go 1.16
require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gin-gonic/gin v1.7.2
github.com/go-openapi/spec v0.20.3 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/go-playground/validator v9.31.0+incompatible // indirect
github.com/go-playground/validator/v10 v10.6.1 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/jinzhu/gorm v1.9.16
github.com/json-iterator/go v1.1.11 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/pelletier/go-toml v1.9.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.1
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 // indirect
github.com/swaggo/gin-swagger v1.3.0 // indirect
github.com/swaggo/swag v1.7.0 // indirect
github.com/ugorji/go v1.2.6 // indirect
github.com/urfave/cli v1.20.0 // indirect
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 // indirect
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/tools v0.1.2 // indirect
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

11
internal/dao/dao.go Normal file
View File

@ -0,0 +1,11 @@
package dao
import "github.com/jinzhu/gorm"
type Dao struct {
engine *gorm.DB
}
func New(engine *gorm.DB) *Dao {
return &Dao{engine: engine}
}

47
internal/dao/tag.go Normal file
View File

@ -0,0 +1,47 @@
package dao
import (
"blog/internal/model"
"blog/pkg/app"
)
func (d *Dao) CountTag(name string, state uint8) (int, error) {
tag := model.Tag{Name: name, State: state}
return tag.Count(d.engine)
}
func (d *Dao) GetTagList(name string, state uint8, page, pageSize int) ([]*model.Tag, error) {
tag := model.Tag{Name: name, State: state}
pageOffset := app.GetPageOffset(page, pageSize)
return tag.List(d.engine, pageOffset, pageSize)
}
func (d *Dao) CreateTag(name string, state uint8, createdBy string) error {
tag := model.Tag{
Name: name,
State: state,
Model: &model.Model{CreatedBy: createdBy},
}
return tag.Create(d.engine)
}
func (d *Dao) UpdateTag(id uint32, name string, state *uint8, modifiedBy string) error {
tag := model.Tag{
Model: &model.Model{ID: id},
}
values := map[string]interface{}{
"state": *state,
"modified_by": modifiedBy,
}
if name != "" {
values["name"] = name
}
return tag.Update(d.engine, values)
}
func (d *Dao) DeleteTag(id uint32) error {
tag := model.Tag{Model: &model.Model{ID: id}}
return tag.Delete(d.engine)
}

View File

@ -0,0 +1,38 @@
package middleware
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
"github.com/go-playground/locales/zh_Hant_TW"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
enTranslations "github.com/go-playground/validator/v10/translations/en"
zhTranslations "github.com/go-playground/validator/v10/translations/zh"
)
func Translations() gin.HandlerFunc {
return func(c *gin.Context) {
uni := ut.New(en.New(), zh.New(), zh_Hant_TW.New())
locale := c.GetHeader("locale")
trans, _ := uni.GetTranslator(locale)
v, ok := binding.Validator.Engine().(*validator.Validate)
if ok {
switch locale {
case "zh":
_ = zhTranslations.RegisterDefaultTranslations(v, trans)
break
case "en":
_ = enTranslations.RegisterDefaultTranslations(v, trans)
break
default:
_ = zhTranslations.RegisterDefaultTranslations(v, trans)
break
}
c.Set("trans", trans)
}
c.Next()
}
}

21
internal/model/article.go Normal file
View File

@ -0,0 +1,21 @@
package model
import "blog/pkg/app"
type Article struct {
*Model
Title string `json:"title"`
Desc string `json:"desc"`
Content string `json:"content"`
CoverImageUrl string `json:"cover_image_url"`
State uint8 `json:"state"`
}
func (a Article) TableName() string {
return "blog_article"
}
type ArticleSwagger struct {
List []*Article
Pager *app.Pager
}

View File

@ -0,0 +1,11 @@
package model
type ArticleTag struct {
*Model
TagID uint32 `json:"tag_id"`
ArticleID uint32 `json:"article_id"`
}
func (a ArticleTag) TableName() string {
return "blog_article_tag"
}

106
internal/model/model.go Normal file
View File

@ -0,0 +1,106 @@
package model
import (
"blog/global"
"blog/pkg/setting"
"fmt"
"github.com/jinzhu/gorm"
"time"
)
type Model struct {
ID uint32 `gorm:"primary_key" json:"id"`
CreatedBy string `json:"created_by"`
ModifiedBy string `json:"modified_by"`
CreatedOn uint32 `json:"created_on"`
ModifiedOn uint32 `json:"modified_on"`
DeletedOn uint32 `json:"deleted_on"`
IsDel uint8 `json:"is_del"`
}
func NewDBEngine(databaseSetting *setting.DatabaseSettingS) (*gorm.DB, error) {
db, err := gorm.Open(databaseSetting.DBType, fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=%s&parseTime=%t&loc=Local",
databaseSetting.UserName,
databaseSetting.Password,
databaseSetting.Host,
databaseSetting.DBName,
databaseSetting.Charset,
databaseSetting.ParseTime,
))
if err != nil {
return nil, err
}
if global.ServerSetting.RunMode == "debug" {
db.LogMode(true)
}
db.SingularTable(true)
db.DB().SetMaxIdleConns(databaseSetting.MaxIdleConns)
db.DB().SetMaxOpenConns(databaseSetting.MaxOpenConns)
db.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback)
db.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback)
db.Callback().Delete().Replace("gorm:delete", deleteCallback)
return db, nil
}
func updateTimeStampForCreateCallback(scope *gorm.Scope) {
if !scope.HasError() {
nowTime := time.Now().Unix()
if createTimeField, ok := scope.FieldByName("CreatedOn"); ok {
if createTimeField.IsBlank {
_ = createTimeField.Set(nowTime)
}
}
if modifyTimeField, ok := scope.FieldByName("ModifiedOn"); ok {
if modifyTimeField.IsBlank {
_ = modifyTimeField.Set(nowTime)
}
}
}
}
func updateTimeStampForUpdateCallback(scope *gorm.Scope) {
if _, ok := scope.Get("gorm:update_column"); !ok {
_ = scope.SetColumn("ModifiedOn", time.Now().Unix())
}
}
func deleteCallback(scope *gorm.Scope) {
if !scope.HasError() {
var extraOption string
if str, ok := scope.Get("gorm:delete_option"); ok {
extraOption = fmt.Sprint(str)
}
deletedOnField, hasDeletedOnField := scope.FieldByName("DeletedOn")
isDelField, hasIsDelField := scope.FieldByName("IsDel")
if !scope.Search.Unscoped && hasDeletedOnField && hasIsDelField {
now := time.Now().Unix()
scope.Raw(fmt.Sprintf(
"UPDATE %v SET %v=%v,%v=%v%v%v",
scope.QuotedTableName(),
scope.Quote(deletedOnField.DBName),
scope.AddToVars(now),
scope.Quote(isDelField.DBName),
scope.AddToVars(1),
addExtraSpaceIfExist(scope.CombinedConditionSql()),
addExtraSpaceIfExist(extraOption),
)).Exec()
} else {
scope.Raw(fmt.Sprintf(
"DELETE FROM %v%v%v",
scope.QuotedTableName(),
addExtraSpaceIfExist(scope.CombinedConditionSql()),
addExtraSpaceIfExist(extraOption),
)).Exec()
}
}
}
func addExtraSpaceIfExist(str string) string {
if str != "" {
return " " + str
}
return ""
}

67
internal/model/tag.go Normal file
View File

@ -0,0 +1,67 @@
package model
import (
"blog/pkg/app"
"github.com/jinzhu/gorm"
)
type Tag struct {
*Model
Name string `json:"name"`
State uint8 `json:"state"`
}
func (t Tag) TableName() string {
return "blog_tag"
}
type TagSwagger struct {
List []*Tag
Pager *app.Pager
}
func (t Tag) Count(db *gorm.DB) (int, error) {
var count int
if t.Name != "" {
db = db.Where("name = ?", t.Name)
}
db = db.Where("state = ?", t.State)
if err := db.Model(&t).Where("is_del = ?", 0).Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
func (t Tag) List(db *gorm.DB, pageOffset, pageSize int) ([]*Tag, error) {
var tags []*Tag
var err error
if pageOffset >= 0 && pageSize > 0 {
db = db.Offset(pageOffset).Limit(pageSize)
}
if t.Name != "" {
db = db.Where("name = ?", t.Name)
}
db = db.Where("state = ?", t.State)
if err = db.Where("is_del = ?", 0).Find(&tags).Error; err != nil {
return nil, err
}
return tags, nil
}
func (t Tag) Create(db *gorm.DB) error {
return db.Create(&t).Error
}
func (t Tag) Update(db *gorm.DB, values interface{}) error {
if err := db.Model(t).Where("id = ? AND is_del = ?", t.ID, 0).Updates(values).Error; err != nil {
return err
}
return nil
}
func (t Tag) Delete(db *gorm.DB) error {
return db.Where("id = ? AND is_del = ?", t.Model.ID, 0).Delete(&t).Error
}

View File

@ -0,0 +1,52 @@
package api
import (
"blog/global"
"blog/internal/service"
"blog/pkg/app"
"blog/pkg/convert"
"blog/pkg/errorcode"
"blog/pkg/upload"
"github.com/gin-gonic/gin"
)
type Upload struct{}
func NewUpload() Upload {
return Upload{}
}
// @Summary 上传图片
// @Produce json
// @Param type formData int true "文件类型1图片" required Enums(1,2,3)
// @Param state formData file true "文件"
// @Success 200 {object} service.FileInfo "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /upload/file [post]
func (u Upload) UploadFile(c *gin.Context) {
response := app.NewResponse(c)
file, fileHeader, err := c.Request.FormFile("file")
if err != nil {
response.ToErrorResponse(errorcode.InvalidParams.WithDetails(err.Error()))
return
}
fileType := convert.StrTo(c.PostForm("type")).MustInt()
if fileHeader == nil || fileType <= 0 {
response.ToErrorResponse(errorcode.InvalidParams)
return
}
svc := service.New(c.Request.Context())
fileInfo, err := svc.UploadFile(upload.FileType(fileType), file, fileHeader)
if err != nil {
global.Logger.Errorf("svc.UploadFile err: %v", err)
response.ToErrorResponse(errorcode.ErrorUploadFileFail.WithDetails(err.Error()))
return
}
response.ToResponse(gin.H{
"file_access_url": fileInfo.AccessUrl,
})
}

View File

@ -0,0 +1,21 @@
package v1
import (
"blog/pkg/app"
"blog/pkg/errorcode"
"github.com/gin-gonic/gin"
)
type Article struct{}
func NewArticle() Article {
return Article{}
}
func (a Article) Get(c *gin.Context) {
app.NewResponse(c).ToErrorResponse(errorcode.ServerError)
}
func (a Article) List(c *gin.Context) {}
func (a Article) Create(c *gin.Context) {}
func (a Article) Update(c *gin.Context) {}
func (a Article) Delete(c *gin.Context) {}

View File

@ -0,0 +1,146 @@
package v1
import (
"blog/global"
"blog/internal/service"
"blog/pkg/app"
"blog/pkg/convert"
"blog/pkg/errorcode"
"github.com/gin-gonic/gin"
)
type Tag struct {
}
func NewTag() Tag {
return Tag{}
}
func (t Tag) Get(c *gin.Context) {}
// @Summary 获取多个标签
// @Produce json
// @Param name query string false "标签名称" maxlength(100)
// @Param state query int false "状态" Enums(0, 1) default(1)
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(10)
// @Success 200 {object} model.TagSwagger "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/tags [get]
func (t Tag) List(c *gin.Context) {
param := service.TagListRequest{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
global.Logger.Infof("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errorcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
svc := service.New(c.Request.Context())
pager := app.Pager{Page: app.GetPage(c), PageSize: app.GetPageSize(c)}
totalRows, err := svc.CountTag(&service.CountTagRequest{Name: param.Name, State: param.State})
if err != nil {
global.Logger.Errorf("svc.CountTag err: %v", err)
response.ToErrorResponse(errorcode.ErrorCountTagFail)
return
}
tags, err := svc.GetTagList(&param, &pager)
if err != nil {
global.Logger.Errorf("svc.GetTagList err: %v", err)
response.ToErrorResponse(errorcode.ErrorGetTagListFail)
return
}
response.ToResponseList(tags, totalRows)
}
// @Summary 新增标签
// @Produce json
// @Param name formData string true "标签名称" minlength(3) maxlength(100)
// @Param state formData int false "状态" Enums(0, 1) default(1)
// @Param created_by formData string true "创建者" minlength(3) maxlength(100)
// @Success 200 {object} model.TagSwagger "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/tags [post]
func (t Tag) Create(c *gin.Context) {
param := service.CreateTagRequest{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
global.Logger.Infof("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errorcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
svc := service.New(c.Request.Context())
err := svc.CreateTag(&param)
if err != nil {
global.Logger.Errorf("svc.CreateTag err: %v", err)
response.ToErrorResponse(errorcode.ErrorCreateTagFail)
return
}
response.ToResponse(gin.H{})
return
}
// @Summary 更新标签
// @Produce json
// @Param id path int true "标签 ID"
// @Param name formData string false "标签名称" minlength(3),maxlength(100)
// @Param state formData int false "状态" Enums(0, 1) default(1)
// @Param modified_by formData string true "修改者" minlength(3) maxlength(100)
// @Success 200 {array} model.TagSwagger "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/tags/{id} [put]
func (t Tag) Update(c *gin.Context) {
param := service.UpdateTagRequest{ID: convert.StrTo(c.Param("id")).MustUInt32()}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
global.Logger.Infof("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errorcode.InvalidParams.WithDetails(errs.Errors()...))
}
svc := service.New(c.Request.Context())
err := svc.UpdateTag(&param)
if err != nil {
global.Logger.Errorf("svc.UpdateTag err: %v", err)
response.ToErrorResponse(errorcode.ErrorUpdateTagFail)
return
}
response.ToResponse(gin.H{})
}
// @Summary 删除标签
// @Produce json
// @Param id path int true "标签 ID"
// @Success 200 {string} string "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/tags/{id} [delete]
func (t Tag) Delete(c *gin.Context) {
param := service.DeleteTagRequest{ID: convert.StrTo(c.Param("id")).MustUInt32()}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
global.Logger.Infof("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errorcode.InvalidParams.WithDetails(errs.Errors()...))
}
svc := service.New(c.Request.Context())
err := svc.DeleteTag(&param)
if err != nil {
global.Logger.Errorf("svc.DeleteTag err: %v", err)
response.ToErrorResponse(errorcode.ErrorDeleteTagFail)
return
}
response.ToResponse(gin.H{})
}

View File

@ -0,0 +1,39 @@
package routess
import (
_ "blog/docs"
"blog/internal/middleware"
"blog/internal/routess/api"
v1 "blog/internal/routess/api/v1"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
func NewRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.Use(middleware.Translations())
article := v1.NewArticle()
tag := v1.NewTag()
upload := api.NewUpload()
r.POST("/upload/file", upload.UploadFile)
r.GET("/doc/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
apiv1 := r.Group("/api/v1")
{
apiv1.POST("/tags", tag.Create)
apiv1.DELETE("/tags/:id", tag.Delete)
apiv1.PUT("/tags/:id", tag.Update)
apiv1.PATCH("/tags/:id/state", tag.Update)
apiv1.GET("/tags", tag.List)
apiv1.POST("/articles", article.Create)
apiv1.DELETE("/articles/:id", article.Delete)
apiv1.PUT("/articles/:id", article.Update)
apiv1.PATCH("/articles/:id/state", article.Update)
apiv1.GET("/articles/:id", article.Get)
apiv1.GET("/articles", article.List)
}
return r
}

View File

@ -0,0 +1,18 @@
package service
import (
"blog/global"
"blog/internal/dao"
"context"
)
type Service struct {
ctx context.Context
dao *dao.Dao
}
func New(ctx context.Context) Service {
svc := Service{ctx: ctx}
svc.dao = dao.New(global.DBEngine)
return svc
}

53
internal/service/tag.go Normal file
View File

@ -0,0 +1,53 @@
package service
import (
"blog/internal/model"
"blog/pkg/app"
)
type CountTagRequest struct {
Name string `form:"name" binding:"max=100"`
State uint8 `form:"state,default=1" binding:"oneof=0 1"`
}
type TagListRequest struct {
Name string `form:"name" binding:"max=100"`
State uint8 `form:"state,default=1" binding:"oneof=0 1"`
}
type CreateTagRequest struct {
Name string `form:"name" binding:"required,min=3,max=100"`
CreatedBy string `form:"created_by" binding:"required,min=3,max=100"`
State uint8 `form:"state,default=1" binding:"oneof=0 1"`
}
type UpdateTagRequest struct {
ID uint32 `form:"id" binding:"required,gte=1"`
Name string `form:"name" binding:"min=3,max=100"`
State *uint8 `form:"state" binding:"required,oneof=0 1"`
ModifiedBy string `form:"modified_by" binding:"required,min=3,max=100"`
}
type DeleteTagRequest struct {
ID uint32 `form:"id" binding:"required,gte=1"`
}
func (svc *Service) CountTag(param *CountTagRequest) (int, error) {
return svc.dao.CountTag(param.Name, param.State)
}
func (svc *Service) GetTagList(param *TagListRequest, pager *app.Pager) ([]*model.Tag, error) {
return svc.dao.GetTagList(param.Name, param.State, pager.Page, pager.PageSize)
}
func (svc *Service) CreateTag(param *CreateTagRequest) error {
return svc.dao.CreateTag(param.Name, param.State, param.CreatedBy)
}
func (svc *Service) UpdateTag(param *UpdateTagRequest) error {
return svc.dao.UpdateTag(param.ID, param.Name, param.State, param.ModifiedBy)
}
func (svc *Service) DeleteTag(param *DeleteTagRequest) error {
return svc.dao.DeleteTag(param.ID)
}

View File

@ -0,0 +1,42 @@
package service
import (
"blog/global"
"blog/pkg/upload"
"errors"
"mime/multipart"
"os"
)
type FileInfo struct {
Name string
AccessUrl string
}
func (svc *Service) UploadFile(fileType upload.FileType, file multipart.File, fileHeader *multipart.FileHeader) (*FileInfo, error) {
fileName := upload.GetFileName(fileHeader.Filename)
if !upload.CheckContainExt(fileType, fileName) {
return nil, errors.New("file suffix is not supported")
}
if upload.CheckMaxSize(fileType, file) {
return nil, errors.New("exceeded maximum file limit")
}
uploadSavePath := upload.GetSavePath()
if upload.CheckSavePath(uploadSavePath) {
if err := upload.CreateSavePath(uploadSavePath, os.ModePerm); err != nil {
return nil, errors.New("failed to create save directory")
}
}
if upload.CheckPermission(uploadSavePath) {
return nil, errors.New("insufficient file permissions")
}
dst := uploadSavePath + "/" + fileName
if err := upload.SaveFile(fileHeader, dst); err != nil {
return nil, err
}
accessUrl := global.AppSetting.UploadServerUrl + "/" + fileName
return &FileInfo{Name: fileName, AccessUrl: accessUrl}, nil
}

90
main.go Normal file
View File

@ -0,0 +1,90 @@
package main
import (
"blog/global"
"blog/internal/model"
"blog/internal/routess"
"blog/pkg/logger"
"blog/pkg/setting"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
"gopkg.in/natefinch/lumberjack.v2"
"log"
"net/http"
"time"
)
func init() {
err := setupSetting()
if err != nil {
log.Fatalf("init.setupSetting err: %v", err)
}
err = setupDBEngine()
if err != nil {
log.Fatalf("init.setupDBEngine err: %v", err)
}
err = setupLogger()
if err != nil {
log.Fatalf("init.setupLogger err: %v", err)
}
}
func setupDBEngine() error {
var err error
global.DBEngine, err = model.NewDBEngine(global.DatabaseSetting)
if err != nil {
return err
}
return nil
}
func setupLogger() error {
global.Logger = logger.NewLogger(&lumberjack.Logger{
Filename: global.AppSetting.LogSavePath + "/" + global.AppSetting.LogFileName + global.AppSetting.LogFileExt,
MaxSize: 600,
MaxAge: 10,
LocalTime: true,
}, "", log.LstdFlags).WithCaller(2)
return nil
}
func setupSetting() error {
newSetting, err := setting.NewSetting()
if err != nil {
return err
}
err = newSetting.ReadSection("Server", &global.ServerSetting)
if err != nil {
return err
}
err = newSetting.ReadSection("App", &global.AppSetting)
if err != nil {
return err
}
err = newSetting.ReadSection("Database", &global.DatabaseSetting)
if err != nil {
return err
}
global.ServerSetting.ReadTimeout *= time.Second
global.ServerSetting.WriteTimeout *= time.Second
return nil
}
// @title 博客系统
// @version 1.0
// @description Go 语言编程之旅:一起用 Go 做项目
// @termsOfService https://github.com/go-programming-tour-book
func main() {
gin.SetMode(global.ServerSetting.RunMode)
global.Logger.Infof("%s: go-programming-tour-book/%s", "eddycjy", "blog-service")
r := routess.NewRouter()
s := &http.Server{
Addr: ":8080",
Handler: r,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
s.ListenAndServe()
}

50
storage/logs/app.log Normal file
View File

@ -0,0 +1,50 @@
2021/05/27 11:05:37 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622084737609101710}
2021/05/27 11:21:12 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622085672929232372}
2021/05/27 11:51:10 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622087470164603650}
2021/05/27 11:51:27 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622087487441027324}
2021/05/27 11:54:41 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622087681328002588}
2021/05/27 11:57:54 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622087874303187563}
2021/05/27 14:07:35 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622095655653485221}
2021/05/27 14:08:02 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622095682368233943}
2021/05/27 14:08:25 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622095705608187304}
2021/05/27 14:37:06 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622097426493197748}
2021/05/27 14:37:14 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State必须是[0 1]中的一个","time":1622097434325394979}
2021/05/27 14:38:00 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State必须是[0 1]中的一个","time":1622097480138488964}
2021/05/27 16:02:37 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622102557922696550}
2021/05/27 16:06:13 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: ","time":1622102773119591365}
2021/05/27 16:17:48 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622103468619951851}
2021/05/27 16:20:57 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622103657062869940}
2021/05/27 16:39:06 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622104746415966590}
2021/05/27 16:46:18 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622105178606120868}
2021/05/27 16:47:02 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622105222324148943}
2021/05/27 19:32:06 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622115126240365402}
2021/05/27 19:38:00 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622115480450697332}
2021/05/27 19:56:13 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622116573249023863}
2021/05/27 19:57:06 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622116626105455018}
2021/05/27 19:59:00 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622116740320292554}
2021/05/27 19:59:17 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622116757447119782}
2021/05/27 19:59:51 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622116791393630451}
2021/05/28 10:18:28 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622168308865618331}
2021/05/28 11:01:23 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622170883605638925}
2021/05/28 11:21:46 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622172106599262267}
2021/05/28 11:31:47 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622172707489849654}
2021/05/28 11:31:50 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622172710615258635}
2021/05/28 11:31:53 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622172713867713030}
2021/05/28 11:33:49 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622172829474375529}
2021/05/28 11:34:18 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622172858829135730}
2021/05/28 11:34:19 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622172859814946252}
2021/05/28 11:35:47 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622172947398620704}
2021/05/28 11:38:50 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622173130414371110}
2021/05/28 11:41:36 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622173296327015991}
2021/05/28 16:28:31 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622190511003684831}
2021/05/28 16:53:48 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622192028460804461}
2021/05/28 17:02:49 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622192569815835955}
2021/05/28 17:04:13 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622192653208795175}
2021/05/28 17:05:45 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622192745007678561}
2021/05/28 17:05:49 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622192749363800854}
2021/05/28 17:06:02 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"app.BindAndValid errs: State为必填字段","time":1622192762407831575}
2021/05/28 17:09:42 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622192982235099983}
2021/05/28 17:10:37 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622193037895200224}
2021/05/28 17:22:05 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622193725365423500}
2021/05/28 18:35:09 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622198109257587951}
2021/05/28 18:36:38 {"callers":["/home/xing/IdeaProjects/blog/main.go: 26 main.init.0"],"level":"info","message":"eddycjy: go-programming-tour-book/blog-service","time":1622198198869750999}