feat: complete settings

This commit is contained in:
engigu
2024-01-01 12:03:56 +08:00
parent 66b3a30a62
commit 4a1e663396
19 changed files with 242 additions and 155 deletions
+4 -95
View File
@@ -1,30 +1,7 @@
# Go Gin Example [![rcard](https://goreportcard.com/badge/github.com/EDDYCJY/go-gin-example)](https://goreportcard.com/report/github.com/EDDYCJY/go-gin-example) [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://godoc.org/github.com/EDDYCJY/go-gin-example) [![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/EDDYCJY/go-gin-example/master/LICENSE)
# Message Nest
`gin` 的一个例子,包含许多有用特性
## 目录
本项目提供 [Gin实践](https://segmentfault.com/a/1190000013297625) 的连载示例代码
1. [Gin实践 连载一 Golang介绍与环境安装](https://book.eddycjy.com/golang/gin/install.html)
2. [Gin实践 连载二 搭建Blog API's(一)](https://book.eddycjy.com/golang/gin/api-01.html)
3. [Gin实践 连载三 搭建Blog API's(二)](https://book.eddycjy.com/golang/gin/api-02.html)
4. [Gin实践 连载四 搭建Blog API's(三)](https://book.eddycjy.com/golang/gin/api-03.html)
5. [Gin实践 连载五 使用JWT进行身份校验](https://book.eddycjy.com/golang/gin/jwt.html)
6. [Gin实践 连载六 编写一个简单的文件日志](https://book.eddycjy.com/golang/gin/log.html)
7. [Gin实践 连载七 Golang优雅重启HTTP服务](https://book.eddycjy.com/golang/gin/reload-http.html)
8. [Gin实践 连载八 为它加上Swagger](https://book.eddycjy.com/golang/gin/swagger.html)
9. [Gin实践 连载九 将Golang应用部署到Docker](https://book.eddycjy.com/golang/gin/golang-docker.html)
10. [Gin实践 连载十 定制 GORM Callbacks](https://book.eddycjy.com/golang/gin/gorm-callback.html)
11. [Gin实践 连载十一 Cron定时任务](https://book.eddycjy.com/golang/gin/cron.html)
12. [Gin实践 连载十二 优化配置结构及实现图片上传](https://book.eddycjy.com/golang/gin/config-upload.html)
13. [Gin实践 连载十三 优化你的应用结构和实现Redis缓存](https://book.eddycjy.com/golang/gin/application-redis.html)
14. [Gin实践 连载十四 实现导出、导入 Excel](https://book.eddycjy.com/golang/gin/excel.html)
15. [Gin实践 连载十五 生成二维码、合并海报](https://book.eddycjy.com/golang/gin/image.html)
16. [Gin实践 连载十六 在图片上绘制文字](https://book.eddycjy.com/golang/gin/font.html)
17. [Gin实践 连载十七 用 Nginx 部署 Go 应用](https://book.eddycjy.com/golang/gin/nginx.html)
18. [Gin实践 番外 Golang交叉编译](https://book.eddycjy.com/golang/gin/cgo.html)
19. [Gin实践 番外 请入门 Makefile](https://book.eddycjy.com/golang/gin/makefile.html)
## 安装
```
@@ -34,83 +11,15 @@ $ go get github.com/EDDYCJY/go-gin-example
## 如何运行
### 必须
- Mysql
- Redis
### 准备
创建一个 `blog` 数据库,并且导入建表的 [SQL](https://message-nest/blob/master/docs/sql/blog.sql)
### 配置
你应该修改 `conf/app.ini` 配置文件
```
[database]
Type = mysql
User = root
Password = rootroot
Host = 127.0.0.1:3306
Name = blog
TablePrefix = blog_
[redis]
Host = 127.0.0.1:6379
Password =
MaxIdle = 30
MaxActive = 30
IdleTimeout = 200
...
```
[main.go](main.go)
### 运行
```
$ cd $GOPATH/src/go-gin-example
$ go run main.go
```
项目的运行信息和已存在的 API's
```
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /auth --> message-nest/routers/api.GetAuth (3 handlers)
[GIN-debug] GET /swagger/*any --> message-nest/vendor/github.com/swaggo/gin-swagger.WrapHandler.func1 (3 handlers)
[GIN-debug] GET /api/v1/tags --> message-nest/routers/api/v1.GetTags (4 handlers)
[GIN-debug] POST /api/v1/tags --> message-nest/routers/api/v1.AddTag (4 handlers)
[GIN-debug] PUT /api/v1/tags/:id --> message-nest/routers/api/v1.EditTag (4 handlers)
[GIN-debug] DELETE /api/v1/tags/:id --> message-nest/routers/api/v1.DeleteTag (4 handlers)
[GIN-debug] GET /api/v1/articles --> message-nest/routers/api/v1.GetArticles (4 handlers)
[GIN-debug] GET /api/v1/articles/:id --> message-nest/routers/api/v1.GetArticle (4 handlers)
[GIN-debug] POST /api/v1/articles --> message-nest/routers/api/v1.AddArticle (4 handlers)
[GIN-debug] PUT /api/v1/articles/:id --> message-nest/routers/api/v1.EditArticle (4 handlers)
[GIN-debug] DELETE /api/v1/articles/:id --> message-nest/routers/api/v1.DeleteArticle (4 handlers)
Listening port is 8000
Actual pid is 4393
```
Swagger 文档
![image](https://i.imgur.com/bVRLTP4.jpg)
## 特性
- RESTful API
- Gorm
- Swagger
- logging
- Jwt-go
- Gin
- Graceful restart or stop (fvbock/endless)
- App configurable
- Cron
- Redis
## 联系我
![image](https://image.eddycjy.com/7074be90379a121746146bc4229819f8.jpg)
### 运行[main.go](main.go)
+2 -9
View File
@@ -2,7 +2,6 @@ package main
import (
"fmt"
"log"
"message-nest/pkg/table"
"message-nest/service/cron_service"
"message-nest/service/env_service"
@@ -27,12 +26,6 @@ func init() {
cron_service.Setup()
}
// @title Golang Gin API
// @version 1.0
// @description An example of gin
// @termsOfService https://github.com/EDDYCJY/go-gin-example
// @license.name MIT
// @license.url https://message-nest/blob/master/LICENSE
func main() {
gin.SetMode(setting.ServerSetting.RunMode)
@@ -50,10 +43,10 @@ func main() {
MaxHeaderBytes: maxHeaderBytes,
}
log.Printf("[info] start http server listening http://0.0.0.0%s", endPoint)
logging.Logger.Info("start http server listening http://0.0.0.0", endPoint)
err := server.ListenAndServe()
if err != nil {
log.Printf("Server err: %v", err)
logging.Logger.Error("Server err: ", err)
}
}
+80
View File
@@ -0,0 +1,80 @@
package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
"message-nest/pkg/logging"
"time"
)
const (
status200 = 42
status404 = 43
status500 = 41
methodGET = 44
)
func formatDuration(duration time.Duration) string {
seconds := duration.Seconds()
switch {
case seconds >= 1:
return fmt.Sprintf("%.2fs", seconds)
default:
return fmt.Sprintf("%.3fms", float64(duration.Milliseconds()))
//default:
// return fmt.Sprintf("%dns", duration.Nanoseconds())
//case duration.Milliseconds() >= 1:
// return fmt.Sprintf("%.2fms", float64(duration.Milliseconds()))
//default:
// return fmt.Sprintf("%dns", duration.Nanoseconds())
}
}
func LogMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
// Process request
c.Next()
// Log only when path is not being skipped
// Stop timer
end := time.Now()
timeSub := end.Sub(start)
dr := formatDuration(timeSub)
clientIP := c.ClientIP()
method := c.Request.Method
statusCode := c.Writer.Status()
//bodySize := c.Writer.Size()
if raw != "" {
path = path + "?" + raw
}
var statusColor string
switch statusCode {
case 200:
statusColor = fmt.Sprintf("\033[%dm %d \033[0m", status200, statusCode)
case 500:
statusColor = fmt.Sprintf("\033[%dm %d \033[0m", status500, statusCode)
default:
statusColor = fmt.Sprintf("\033[%dm %d \033[0m", status404, statusCode)
}
var methodColor string
methodColor = fmt.Sprintf("\033[%dm %s \033[0m", methodGET, method)
logging.Logger.Infof("[GIN]|%s|%s|%s|%s| %s",
//start.Format("2006-01-02 15:04:06"),
statusColor,
dr,
clientIP,
methodColor,
path,
)
}
}
+1 -2
View File
@@ -47,7 +47,7 @@ func Setup() {
return setting.DatabaseSetting.TablePrefix + defaultTableName
}
db.LogMode(true)
//db.LogMode(true)
db.SingularTable(true)
db.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback)
db.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback)
@@ -65,7 +65,6 @@ func CloseDB() {
func updateTimeStampForCreateCallback(scope *gorm.Scope) {
if !scope.HasError() {
nowTime := time.Now()
fmt.Printf("fyyyyyynowTime %s", nowTime)
if createTimeField, ok := scope.FieldByName("CreatedOn"); ok {
if createTimeField.IsBlank {
+8
View File
@@ -162,3 +162,11 @@ func DeleteMsgTask(id string) error {
tx.Commit()
return nil
}
func EditSendTask(id string, data interface{}) error {
if err := db.Model(&SendTasks{}).Where("id = ? ", id).Updates(data).Error; err != nil {
return err
}
return nil
}
+31 -2
View File
@@ -1,15 +1,44 @@
package logging
import (
"bytes"
"fmt"
"github.com/sirupsen/logrus"
"os"
"path/filepath"
)
var Logger = logrus.New()
type MyFormatter struct{}
func (m *MyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
var b *bytes.Buffer
if entry.Buffer != nil {
b = entry.Buffer
} else {
b = &bytes.Buffer{}
}
timestamp := entry.Time.Format("2006-01-02 15:04:05")
var newLog string
//HasCaller()为true才会有调用信息
if entry.HasCaller() {
fName := filepath.Base(entry.Caller.File)
newLog = fmt.Sprintf("[%s] [%s] [%s:%d %s] %s\n",
timestamp, entry.Level, fName, entry.Caller.Line, entry.Caller.Function, entry.Message)
} else {
newLog = fmt.Sprintf("[%s] [%s] %s\n", timestamp, entry.Level, entry.Message)
}
b.WriteString(newLog)
return b.Bytes(), nil
}
func Setup() {
Logger := logrus.New()
Logger.SetFormatter(&logrus.JSONFormatter{})
Logger.SetReportCaller(true)
Logger.SetFormatter(&MyFormatter{})
Logger.SetOutput(os.Stdout)
Logger.SetLevel(logrus.InfoLevel)
}
+34 -1
View File
@@ -69,7 +69,7 @@ func GetMsgSendTaskList(c *gin.Context) {
}
type AddMsgSendTaskReq struct {
Name string `json:"name" validate:"required,max=100,min=1" label:"任务任务名"`
Name string `json:"name" validate:"required,max=100,min=1" label:"任务名"`
}
// AddMsgSendTask 添加发送任务
@@ -101,3 +101,36 @@ func AddMsgSendTask(c *gin.Context) {
appG.CResponse(http.StatusOK, "添加任务任务成功!", nil)
}
type EditMsgSendTaskReq struct {
ID string `json:"id" validate:"required,len=36" label:"任务id"`
TaskName string `json:"name" validate:"required,max=100,min=1" label:"任务任务名"`
}
// EditMsgSendTask 编辑消息任务
func EditMsgSendTask(c *gin.Context) {
var (
appG = app.Gin{C: c}
req EditMsgSendTaskReq
)
errCode, errMsg := app.BindJsonAndPlayValid(c, &req)
if errCode != e.SUCCESS {
appG.CResponse(errCode, errMsg, nil)
return
}
MsgSendTaskService := send_task_service.SendTaskService{
ID: req.ID,
}
var data = map[string]string{}
data["name"] = req.TaskName
err := MsgSendTaskService.Edit(data)
if err != nil {
appG.CResponse(http.StatusBadRequest, "编辑发信任务失败!", nil)
return
}
appG.CResponse(http.StatusOK, "编辑发信任务成功!", nil)
}
+4 -2
View File
@@ -3,7 +3,6 @@ package routers
import (
"github.com/gin-gonic/gin"
"message-nest/middleware"
"message-nest/routers/api"
"message-nest/routers/api/v1"
)
@@ -11,7 +10,9 @@ import (
// InitRouter initialize routing information
func InitRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r.Use(middleware.LogMiddleware())
//gin.DefaultWriter = logging.Logger.Out
//gin.DefaultErrorWriter = logging.Logger.Out
r.Use(gin.Recovery())
r.Use(middleware.Cors())
@@ -32,6 +33,7 @@ func InitRouter() *gin.Engine {
apiv1.GET("/sendtasks/list", v1.GetMsgSendTaskList)
apiv1.POST("/sendtasks/add", v1.AddMsgSendTask)
apiv1.POST("/sendtasks/delete", v1.DeleteMsgSendTask)
apiv1.POST("/sendtasks/edit", v1.EditMsgSendTask)
// sendtasks/ins
apiv1.POST("/sendtasks/ins/addmany", v1.AddManyTasksIns)
+1 -1
View File
@@ -30,7 +30,7 @@ func (sw *SendTaskInsService) ValidateDiffIns(ins models.SendTasksIns) (string,
_, Msg := app.CommonPlaygroundValid(emailConfig)
return Msg, emailConfig
}
return "", empty
return "未知的渠道的config校验", empty
}
func (st *SendTaskInsService) ManyAdd(taskIns []models.SendTasksIns) string {
+34 -5
View File
@@ -5,6 +5,7 @@ import (
"message-nest/models"
"message-nest/pkg/logging"
"message-nest/pkg/message"
"message-nest/service/send_ins_service"
"message-nest/service/send_task_service"
"message-nest/service/send_way_service"
"strings"
@@ -56,7 +57,7 @@ func (sm *SendMessageService) Send() string {
// 邮箱类型的实例
emailAuth, ok := msgObj.(send_way_service.WayDetailEmail)
if ok {
errMsg := sm.SendTaskEmail(emailAuth)
errMsg := sm.SendTaskEmail(emailAuth, ins.SendTasksIns)
sm.MarkStatus(errMsg, &status)
logOutput = append(logOutput, sm.TransError(errMsg))
continue
@@ -119,11 +120,39 @@ func (sm *SendMessageService) TransError(err string) string {
}
}
// GetSendMsg
func (sm *SendMessageService) GetSendMsg(ins models.SendTasksIns) string {
data := map[string]string{}
data["text"] = sm.Text
data["html"] = sm.HTML
data["markdown"] = sm.MarkDown
content, ok := data[ins.ContentType]
if !ok || len(content) == 0 {
content, ok := data["text"]
if !ok {
logging.Logger.Error("text节点数据为空!")
return ""
} else {
return content
}
} else {
return content
}
}
// SendTaskEmail 执行发送邮件
func (sm *SendMessageService) SendTaskEmail(auth send_way_service.WayDetailEmail) string {
func (sm *SendMessageService) SendTaskEmail(auth send_way_service.WayDetailEmail, ins models.SendTasksIns) string {
insService := send_ins_service.SendTaskInsService{}
errStr, c := insService.ValidateDiffIns(ins)
if errStr != "" {
return errStr
}
config, ok := c.(models.InsEmailConfig)
if !ok {
return "邮箱config校验失败"
}
var emailer message.EmailMessage
emailer.Init(auth.Server, auth.Port, auth.Account, auth.Passwd)
//errMsg := emailer.SendTextMessage("sayheya@qq.com", "test", "This is a test email from message-nest.")
//return errMsg
return ""
errMsg := emailer.SendTextMessage(config.ToAccount, config.Title, sm.GetSendMsg(ins))
return errMsg
}
+4
View File
@@ -27,6 +27,10 @@ func (st *SendTaskService) Delete() error {
return models.DeleteMsgTask(st.ID)
}
func (st *SendTaskService) Edit(data map[string]string) error {
return models.EditSendTask(st.ID, data)
}
func (st *SendTaskService) GetTaskWithIns() (models.TaskIns, error) {
return models.GetTasksIns(st.ID)
}
+2 -2
View File
@@ -16,9 +16,9 @@ class ApiStrGenerate {
if (options.markdown) {
data.html = '** Hello World! **';
}
let dataStr = JSON.stringify(data, null, 4)
let dataStr = JSON.stringify(data, null, 4);
let example = `curl -X POST --location '${gethttpOrigin()}/api/v1/message/send' \\
--header 'Accept: application/json' \\
--header 'Content-Type: application/json' \\
--data '${dataStr}'`;
return example;
}
@@ -44,11 +44,11 @@
<script >
import { computed, ref, reactive, toRefs, onMounted } from 'vue'
// import { InfoFilled } from '@element-plus/icons-vue'
import { usePageState } from '../../../store/page_sate';
import { request } from '../../../api/api'
import addTaskPopUpComponent from './addTaskPopUp.vue'
import editTaskPopUpComponent from './editTaskPopUp.vue'
import viewTaskAPIPopUpComponent from './viewTaskAPIPopUp.vue'
import { usePageState } from '@/store/page_sate';
import { request } from '@/api/api'
import addTaskPopUpComponent from './view/addTaskPopUp.vue'
import editTaskPopUpComponent from './view/editTaskPopUp.vue'
import viewTaskAPIPopUpComponent from './view/viewTaskAPIPopUp.vue'
import tableDeleteButton from '@/views/common/tableDeleteButton.vue'
import { useRouter } from 'vue-router';
import { CONSTANT } from '@/constant'
@@ -95,8 +95,8 @@ import { defineComponent, onMounted, watch, reactive, toRefs } from 'vue';
import { _ } from 'lodash';
import { QuestionFilled } from '@element-plus/icons-vue'
import { v4 as uuidv4 } from 'uuid';
import { usePageState } from '../../../store/page_sate.js';
import { request } from '../../../api/api'
import { usePageState } from '@/store/page_sate.js';
import { request } from '@/api/api'
export default defineComponent({
@@ -226,7 +226,6 @@ export default defineComponent({
.ins-area {
display: flex;
max-width: 900px;
}
.add-top {
@@ -6,6 +6,11 @@
</el-tooltip>
</template>
<div class="add-top">
<el-input v-model="currTaskInput.taskName" placeholder="请输入任务名" size="small" class="taskNameInput"></el-input>
<el-button @click="handleEditTask()" size="small" type="primary" style="margin-left: 20px;">修改</el-button>
</div>
<div class="dashed" />
@@ -81,6 +86,7 @@ import { v4 as uuidv4 } from 'uuid';
import { usePageState } from '@/store/page_sate.js';
import { request } from '@/api/api'
import tableDeleteButton from '@/views/common/tableDeleteButton.vue'
import { ElMessage } from 'element-plus'
export default defineComponent({
@@ -119,7 +125,8 @@ export default defineComponent({
let params = { id: state.currTaskInput.taskId };
const rsp = await request.get('/sendtasks/ins/gettask', { params: params });
state.insTableData = await rsp.data.data.ins_data;
state.total = await rsp.data.data.total;
// state.total = await rsp.data.data.total;
state.currTaskInput.taskName = await rsp.data.data.name;
}
const handleCancer = () => {
@@ -154,19 +161,6 @@ export default defineComponent({
}
}
// //
// const clickStore = () => {
// let insData = {
// id: uuidv4(),
// task_id: state.currTaskInput.taskId,
// way_id: state.currWayTmp.id,
// way_type: state.currWayTmp.type,
// way_name: state.currWayTmp.name,
// content_type: state.currInsInput.content_type
// };
// }
const formatExtraInfo = (scope) => {
if (!scope.row.config) {
return ""
@@ -207,8 +201,16 @@ export default defineComponent({
}
}
const handleEditTask = async () => {
let postData = { id: state.currTaskInput.taskId, name: state.currTaskInput.taskName };
const rsp = await request.post('/sendtasks/edit', postData);
if (await rsp.data.code == 200) {
ElMessage({ message: await rsp.data.msg, type: 'success' })
}
}
return {
...toRefs(state), handleCancer, handleAddSubmit,
...toRefs(state), handleCancer, handleAddSubmit, handleEditTask,
searchID, handleDelete, insRowStyle, formatExtraInfo
};
},
@@ -231,10 +233,10 @@ export default defineComponent({
}
/* .add-top {
.add-top {
margin-bottom: 20px;
}
*/
.ins-add {
flex: 45%;
}
@@ -56,12 +56,12 @@
<script >
import { reactive, toRefs, onMounted } from 'vue'
import addWayComponent from './addWayPopUp.vue'
import editWayComponent from './editWayPopUp.vue'
import { usePageState } from '../../../store/page_sate.js';
import { request } from '../../../api/api'
import addWayComponent from './view/addWayPopUp.vue'
import editWayComponent from './view/editWayPopUp.vue'
import { usePageState } from '@/store/page_sate.js';
import { request } from '@/api/api'
import { CopyDocument } from '@element-plus/icons-vue'
import { copyToClipboard } from '../../../util/clipboard.js';
import { copyToClipboard } from '@/util/clipboard.js';
import tableDeleteButton from '@/views/common/tableDeleteButton.vue'
import { CONSTANT } from '@/constant'
@@ -40,9 +40,9 @@
<script>
import { defineComponent, onMounted, watch, reactive, toRefs } from 'vue';
import { usePageState } from '../../../store/page_sate.js';
import { request } from '../../../api/api'
import { CONSTANT } from '../../../constant'
import { usePageState } from '@/store/page_sate.js';
import { request } from '@/api/api'
import { CONSTANT } from '@/constant'
import { _ } from 'lodash';
import { ElMessage } from 'element-plus'
@@ -42,9 +42,9 @@
<script>
import { defineComponent, onMounted, watch, reactive, toRefs } from 'vue';
import { usePageState } from '../../../store/page_sate.js';
import { request } from '../../../api/api'
import { CONSTANT } from '../../../constant'
import { usePageState } from '@/store/page_sate.js';
import { request } from '@/api/api'
import { CONSTANT } from '@/constant'
import { _ } from 'lodash';
import { ElMessage } from 'element-plus'