Merge branch 'gva_gormv2_dev' of https://github.com/songzhibin97/gin-vue-admin into gva_gormv2_dev

This commit is contained in:
songzhibin97
2021-09-01 17:56:56 +08:00
103 changed files with 5023 additions and 18743 deletions
-2
View File
@@ -6,10 +6,8 @@ type ApiGroup struct {
CustomerApi
ExcelApi
FileUploadAndDownloadApi
SimpleUploaderApi
}
var fileUploadAndDownloadService = service.ServiceGroupApp.ExampleServiceGroup.FileUploadAndDownloadService
var customerService = service.ServiceGroupApp.ExampleServiceGroup.CustomerService
var excelService = service.ServiceGroupApp.ExampleServiceGroup.ExcelService
var simpleUploaderService = service.ServiceGroupApp.ExampleServiceGroup.SimpleUploaderService
@@ -1,95 +0,0 @@
package example
import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/model/example"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type SimpleUploaderApi struct {
}
// @Tags SimpleUploader
// @Summary 断点续传插件版示例
// @Security ApiKeyAuth
// @accept multipart/form-data
// @Produce application/json
// @Param file formData file true "断点续传插件版示例"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"切片创建成功"}"
// @Router /SimpleUploaderApi/upload [post]
func (s *SimpleUploaderApi) SimpleUploaderUpload(c *gin.Context) {
var chunk example.ExaSimpleUploader
_, header, err := c.Request.FormFile("file")
chunk.Filename = c.PostForm("filename")
chunk.ChunkNumber = c.PostForm("chunkNumber")
chunk.CurrentChunkSize = c.PostForm("currentChunkSize")
chunk.Identifier = c.PostForm("identifier")
chunk.TotalSize = c.PostForm("totalSize")
chunk.TotalChunks = c.PostForm("totalChunks")
var chunkDir = "./chunk/" + chunk.Identifier + "/"
hasDir, _ := utils.PathExists(chunkDir)
if !hasDir {
if err := utils.CreateDir(chunkDir); err != nil {
global.GVA_LOG.Error("创建目录失败!", zap.Any("err", err))
}
}
chunkPath := chunkDir + chunk.Filename + chunk.ChunkNumber
err = c.SaveUploadedFile(header, chunkPath)
if err != nil {
global.GVA_LOG.Error("切片创建失败!", zap.Any("err", err))
response.FailWithMessage("切片创建失败", c)
return
}
chunk.CurrentChunkPath = chunkPath
err = simpleUploaderService.SaveChunk(chunk)
if err != nil {
global.GVA_LOG.Error("切片创建失败!", zap.Any("err", err))
response.FailWithMessage("切片创建失败", c)
return
} else {
response.OkWithMessage("切片创建成功", c)
}
}
// @Tags SimpleUploader
// @Summary 断点续传插件版示例
// @Security ApiKeyAuth
// @Produce application/json
// @Param md5 query string true "md5"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
// @Router /SimpleUploaderApi/checkFileMd5 [get]
func (s *SimpleUploaderApi) CheckFileMd5(c *gin.Context) {
md5 := c.Query("md5")
err, chunks, isDone := simpleUploaderService.CheckFileMd5(md5)
if err != nil {
global.GVA_LOG.Error("md5读取失败!", zap.Any("err", err))
response.FailWithMessage("md5读取失败", c)
} else {
response.OkWithDetailed(gin.H{
"chunks": chunks,
"isDone": isDone,
}, "查询成功", c)
}
}
// @Tags SimpleUploader
// @Summary 合并文件
// @Security ApiKeyAuth
// @Produce application/json
// @Param md5 query string true "md5"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"合并成功"}"
// @Router /SimpleUploaderApi/mergeFileMd5 [get]
func (s *SimpleUploaderApi) MergeFileMd5(c *gin.Context) {
md5 := c.Query("md5")
fileName := c.Query("fileName")
err := simpleUploaderService.MergeFileMd5(md5, fileName)
if err != nil {
global.GVA_LOG.Error("md5读取失败!", zap.Any("err", err))
response.FailWithMessage("md5读取失败", c)
} else {
response.OkWithMessage("合并成功", c)
}
}
+1 -2
View File
@@ -155,8 +155,7 @@ func (autoApi *AutoCodeApi) CreateTemp(c *gin.Context) {
err := autoCodeService.CreateTemp(a, apiIds...)
if err != nil {
if errors.Is(err, system.AutoMoveErr) {
c.Writer.Header().Add("success", "false")
c.Writer.Header().Add("msgtype", "success")
c.Writer.Header().Add("success", "true")
c.Writer.Header().Add("msg", url.QueryEscape(err.Error()))
} else {
c.Writer.Header().Add("success", "false")
-1
View File
@@ -48,7 +48,6 @@ func MysqlTables(db *gorm.DB) {
example.ExaFileUploadAndDownload{},
example.ExaFile{},
example.ExaFileChunk{},
example.ExaSimpleUploader{},
example.ExaCustomer{},
system.SysOperationRecord{},
system.SysAutoCodeHistory{},
+7 -1
View File
@@ -4,7 +4,7 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/ws"
"go.uber.org/zap"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notify"
//email "github.com/flipped-aurora/gva-plug-email" // 在线仓库模式
"github.com/flipped-aurora/gin-vue-admin/server/plugin/email" // 本地插件仓库地址模式
"github.com/flipped-aurora/gin-vue-admin/server/plugin/example_plugin"
@@ -34,4 +34,10 @@ func InstallPlugin(PublicGroup *gin.RouterGroup, PrivateGroup *gin.RouterGroup)
global.GVA_CONFIG.Email.Port,
global.GVA_CONFIG.Email.IsSSL,
))
// 钉钉通知,暂时开放权限
PluginInit(PublicGroup, notify.CreateDDPlug(
"https://oapi.dingtalk.com/robot/send",
"8ded23f91917dc4f6275f44ba5ef243e6ed1d2cc74de83f01a6f5f5f39905671",
"SECaecf452bd6e671ab0d47469c3ad933e32fcc47b335333049a1b8961606192f38"))
}
-1
View File
@@ -51,7 +51,6 @@ func Routers() *gin.Engine {
systemRouter.InitSysDictionaryDetailRouter(PrivateGroup) // 字典详情管理
exampleRouter.InitFileUploadAndDownloadRouter(PrivateGroup) // 文件上传下载功能路由
exampleRouter.InitExcelRouter(PrivateGroup) // 表格导入导出
exampleRouter.InitSimpleUploaderRouter(PrivateGroup) // 断点续传(插件版)
exampleRouter.InitCustomerRouter(PrivateGroup) // 客户路由
// Code generated by github.com/flipped-aurora/gin-vue-admin/server Begin; DO NOT EDIT.
@@ -1,13 +0,0 @@
package example
type ExaSimpleUploader struct {
ChunkNumber string `json:"chunkNumber" gorm:"comment:当前切片标记"`
CurrentChunkSize string `json:"currentChunkSize" gorm:"comment:当前切片容量"`
CurrentChunkPath string `json:"currentChunkPath" gorm:"comment:切片本地路径"`
TotalSize string `json:"totalSize" gorm:"comment:总容量"`
Identifier string `json:"identifier" gorm:"comment:文件标识(md5"`
Filename string `json:"filename" gorm:"comment:文件名"`
TotalChunks string `json:"totalChunks" gorm:"comment:切片总数"`
IsDone bool `json:"isDone" gorm:"comment:是否上传完成"`
FilePath string `json:"filePath" gorm:"comment:文件本地路径"`
}
+26 -2
View File
@@ -1,4 +1,5 @@
## GVA 邮件发送功能插件
#### 开发者:GIN-VUE-ADMIN 官方
### 使用步骤
@@ -27,8 +28,9 @@
true,
))
#### 2. 配置说明
### 2. 配置说明
#### 2-1 全局配置结构体说明
//其中 Form 和 Secret 通常来说就是用户名和密码
type Email struct {
@@ -40,8 +42,17 @@
Port int // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
IsSSL bool // 是否SSL 是否开启SSL
}
#### 2-2 入参结构说明
//其中 Form 和 Secret 通常来说就是用户名和密码
### 方法API
type Email struct {
To string `json:"to"` // 邮件发送给谁
Subject string `json:"subject"` // 邮件标题
Body string `json:"body"` // 邮件内容
}
### 3. 方法API
utils.EmailTest(邮件标题,邮件主体) 发送测试邮件
例:utils.EmailTest("测试邮件""测试邮件")
@@ -49,3 +60,16 @@
例:utils.ErrorToEmail("测试邮件""测试邮件")
utils.Email(目标邮箱多个的话用逗号分隔,邮件标题,邮件主体) 发送测试邮件
例:utils.Email(”a.qq.com,b.qq.com“,"测试邮件""测试邮件")
### 4. 可直接调用的接口
测试接口: /email/emailTest [post] 已配置swagger
发送邮件接口接口: /email/emailSend [post] 已配置swagger
入参:
type Email struct {
To string `json:"to"` // 邮件发送给谁
Subject string `json:"subject"` // 邮件标题
Body string `json:"body"` // 邮件内容
}
+19
View File
@@ -3,6 +3,7 @@ package api
import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
email_response "github.com/flipped-aurora/gin-vue-admin/server/plugin/email/model/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/email/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
@@ -25,3 +26,21 @@ func (s *EmailApi) EmailTest(c *gin.Context) {
response.OkWithData("发送成功", c)
}
}
// @Tags System
// @Summary 发送邮件
// @Security ApiKeyAuth
// @Produce application/json
// @Param data body email_response.Email true "发送邮件必须的参数"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
// @Router /email/sendEmail [post]
func (s *EmailApi) SendEmail(c *gin.Context) {
var email email_response.Email
_ = c.ShouldBindJSON(&email)
if err := service.ServiceGroupApp.SendEmail(email.To, email.Subject, email.Body); err != nil {
global.GVA_LOG.Error("发送失败!", zap.Any("err", err))
response.FailWithMessage("发送失败", c)
} else {
response.OkWithData("发送成功", c)
}
}
@@ -0,0 +1,7 @@
package response
type Email struct {
To string `json:"to"` // 邮件发送给谁
Subject string `json:"subject"` // 邮件标题
Body string `json:"body"` // 邮件内容
}
+3 -1
View File
@@ -12,7 +12,9 @@ type EmailRouter struct {
func (s *EmailRouter) InitEmailRouter(Router *gin.RouterGroup) {
emailRouter := Router.Use(middleware.OperationRecord())
var EmailApi = api.ApiGroupApp.EmailApi.EmailTest
var SendEmail = api.ApiGroupApp.EmailApi.SendEmail
{
emailRouter.POST("emailTest", EmailApi) // 发送测试邮件
emailRouter.POST("emailTest", EmailApi) // 发送测试邮件
emailRouter.POST("sendEmail", SendEmail) // 发送邮件
}
}
+13
View File
@@ -18,3 +18,16 @@ func (e *EmailService) EmailTest() (err error) {
err = utils.EmailTest(subject, body)
return err
}
//@author: [maplepie](https://github.com/maplepie)
//@function: EmailTest
//@description: 发送邮件测试
//@return: err error
//@params to string 收件人
//@params subject string 标题(主题)
//@params body string 邮件内容
func (e *EmailService) SendEmail(to, subject, body string) (err error) {
err = utils.Email(to, subject, body)
return err
}
+74
View File
@@ -0,0 +1,74 @@
## GVA 钉钉群通知插件
本插件用于向钉钉群推送消息
### 1. 使用场景
- 当服务运行异常时,可以向钉钉推送异常信息,便于及时发现解决问题
- 推送一些关键业务的运行日志等
### 2. 配置说明
钉钉 token 等相关信息的获取,请参考 [钉钉官网](https://developers.dingtalk.com/document/robots/custom-robot-access?spm=ding_open_doc.document.0.0.7f8710afbfzduV#topic-2026027)
`plugin/notify/global/global.go` 文件中配置钉钉通知的URL ,Token 等
```go
// 在gin-vue-admin 主程序的initialize中的plugin的InstallPlugin 函数中写入如下代码
PluginInit(PublicGroup, notify.CreateDDPlug(
URL,
Token,
密钥))
}
```
### 3 参数说明
#### 3-1 全局参数说明
```go
Url string `mapstructure:"url" json:"url" yaml:"url"` // Url
Token string `mapstructure:"token" json:"token" yaml:"token"` // access_token
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥
```
#### 3-2 请求入参说明
```go
```
### 3方法API(可调用方法)
```go
//content 发送的内容
//atMobiles 需要艾特的人的手机号
//isAtAll 是否艾特全体
SendTextMessage(content string,atMobiles []string,isAtAll bool)
//content 发送的内容
//title 内容标题
//picUrl 配图
//messageUrl 点击跳转路径
SendLinkMessage(content,title,picUrl,messageUrl string)
//content 发送的内容(markdown语法)
//title 内容标题
//atMobiles 需要艾特的人的手机号
//isAtAll 是否艾特全体
SendMarkdownMessage(content,title string,atMobiles []string,isAtAll bool)
```
### 4. 可直接调用接口
发送文字消息接口: /notify/sendTextMessage [post] 已配置swagger
发送图文链接消息接口: /notify/sendLinkMessage [post] 已配置swagger
发送markdown消息接口: /notify/sendMarkdownMessage [post] 已配置swagger
入参:
type Email struct {
To string `json:"to"` // 邮件发送给谁
Subject string `json:"subject"` // 邮件标题
Body string `json:"body"` // 邮件内容
}
+67
View File
@@ -0,0 +1,67 @@
package api
import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
notify_response "github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/model/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type Api struct {
}
// @Tags Notify
// @Summary 发送文字消息接口
// @Security ApiKeyAuth
// @Produce application/json
// @Param data body notify_response.TextNotify true "发送文字消息的参数"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
// @Router /notify/sendTextMessage [post]
func (s *Api) SendTextMessage(c *gin.Context) {
var textNotify notify_response.TextNotify
_ = c.ShouldBindJSON(&textNotify)
if err := service.ServiceGroupApp.SendTextMessage(textNotify.Content, textNotify.AtMobiles, textNotify.IsAtAll); err != nil {
global.GVA_LOG.Error("发送失败!", zap.Any("err", err))
response.FailWithMessage("发送失败", c)
} else {
response.OkWithData("发送成功", c)
}
}
// @Tags Notify
// @Summary 发送图文链接消息接口
// @Security ApiKeyAuth
// @Produce application/json
// @Param data body notify_response.LinkNotify true "发送图文链接消息的参数"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
// @Router /notify/sendLinkMessage [post]
func (s *Api) SendLinkMessage(c *gin.Context) {
var linkNotify notify_response.LinkNotify
_ = c.ShouldBindJSON(&linkNotify)
if err := service.ServiceGroupApp.SendLinkMessage(linkNotify.Content, linkNotify.Title, linkNotify.PicUrl, linkNotify.MessageUrl); err != nil {
global.GVA_LOG.Error("发送失败!", zap.Any("err", err))
response.FailWithMessage("发送失败", c)
} else {
response.OkWithData("发送成功", c)
}
}
// @Tags Notify
// @Summary 发送markdown消息接口
// @Security ApiKeyAuth
// @Produce application/json
// @Param data body notify_response.MarkdownNotify true "发送markdown消息的参数"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
// @Router /notify/sendMarkdownMessage [post]
func (s *Api) SendMarkdownMessage(c *gin.Context) {
var markdownNotify notify_response.MarkdownNotify
_ = c.ShouldBindJSON(&markdownNotify)
if err := service.ServiceGroupApp.SendMarkdownMessage(markdownNotify.Content, markdownNotify.Title, markdownNotify.AtMobiles, markdownNotify.IsAtAll); err != nil {
global.GVA_LOG.Error("发送失败!", zap.Any("err", err))
response.FailWithMessage("发送失败", c)
} else {
response.OkWithData("发送成功", c)
}
}
+7
View File
@@ -0,0 +1,7 @@
package api
type ApiGroup struct {
Api
}
var ApiGroupApp = new(ApiGroup)
+7
View File
@@ -0,0 +1,7 @@
package config
type DingDing struct {
Url string `mapstructure:"url" json:"url" yaml:"url"` // Url
Token string `mapstructure:"token" json:"token" yaml:"token"` // access_token
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥
}
+5
View File
@@ -0,0 +1,5 @@
package global
import "github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/config"
var GlobalConfig_ = &config.DingDing{}
+28
View File
@@ -0,0 +1,28 @@
package notify
import (
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/global"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/router"
"github.com/gin-gonic/gin"
)
type ddPlugin struct {
Secret string
Token string
Url string
}
func CreateDDPlug(url, Token, Secret string) *ddPlugin {
global.GlobalConfig_.Url = url
global.GlobalConfig_.Token = Token
global.GlobalConfig_.Secret = Secret
return &ddPlugin{}
}
func (*ddPlugin) Register(group *gin.RouterGroup) {
router.RouterGroupApp.InitRouter(group)
}
func (*ddPlugin) RouterPath() string {
return "notify"
}
@@ -0,0 +1,21 @@
package response
type TextNotify struct { // 文字信息
Content string `json:"content"` // 发送的内容
AtMobiles []string `json:"atMobiles"` // 需要艾特的人的手机号
IsAtAll bool `json:"isAtAll"` // 是否艾特全体
}
type LinkNotify struct { // 图文链接信息
Content string `json:"content"` // 发送的内容
Title string `json:"title"` // 内容标题
PicUrl string `json:"picUrl"` // 配图
MessageUrl string `json:"messageUrl"` // 点击跳转路径
}
type MarkdownNotify struct { // markdown信息
Title string `json:"title"` // 内容标题
Content string `json:"content"` // 发送的内容
AtMobiles []string `json:"atMobiles"` // 需要艾特的人的手机号
IsAtAll bool `json:"isAtAll"` // 是否艾特全体
}
+7
View File
@@ -0,0 +1,7 @@
package router
type RouterGroup struct {
NotifyRouter
}
var RouterGroupApp = new(RouterGroup)
+18
View File
@@ -0,0 +1,18 @@
package router
import (
"github.com/flipped-aurora/gin-vue-admin/server/middleware"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/api"
"github.com/gin-gonic/gin"
)
type NotifyRouter struct {
}
func (s *NotifyRouter) InitRouter(Router *gin.RouterGroup) {
router := Router.Use(middleware.OperationRecord())
var SendTextMessage = api.ApiGroupApp.Api.SendTextMessage
{
router.POST("sendTextMessage", SendTextMessage)
}
}
+7
View File
@@ -0,0 +1,7 @@
package service
type ServiceGroup struct {
NotifyService
}
var ServiceGroupApp = new(ServiceGroup)
+157
View File
@@ -0,0 +1,157 @@
package service
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/global"
"io/ioutil"
"net/http"
"net/url"
"time"
)
type NotifyService struct {
}
//@author: [Espoir](https://github.com/nightsimon)
//@function: SendTextMessage
//@description: 发送钉钉文字信息
//@params content string发送的文字内容
//@params atMobiles []string 艾特的手机号
//@params isAtAll bool 是否艾特全体
//@return: err error
func (e *NotifyService) SendTextMessage(content string, atMobiles []string, isAtAll bool) (err error) {
msg := map[string]interface{}{
"msgtype": "text",
"text": map[string]string{
"content": content,
},
"at": map[string]interface{}{
"atMobiles": atMobiles,
"isAtAll": isAtAll,
},
}
return SendMessage(msg)
}
//@author: [Espoir](https://github.com/nightsimon)
//@function: SendLinkMessage
//@description: 发送钉钉图文链接信息
//@params content string 发送的文字内容
//@params title string 发送的标题
//@params picUrl string 艾特的手机号
//@params messageUrl string 是否艾特全体
//@return: err error
func (e *NotifyService) SendLinkMessage(content, title, picUrl, messageUrl string) (err error) {
msg := map[string]interface{}{
"msgtype": "link",
"link": map[string]string{
"text": content,
"title": title,
"picUrl": picUrl,
"messageUrl": messageUrl,
},
}
return SendMessage(msg)
}
//@author: [Espoir](https://github.com/nightsimon)
//@function: SendMarkdownMessage
//@description: 发送钉钉Markdown信息
//@params content 发送的文字内容
//@params title 发送的标题
//@params atMobiles []string 艾特的手机号
//@params isAtAll bool 是否艾特全体
//@return: err error
func (e *NotifyService) SendMarkdownMessage(content, title string, atMobiles []string, isAtAll bool) (err error) {
msg := map[string]interface{}{
"msgtype": "markdown",
"markdown": map[string]string{
"text": content,
"title": title,
},
"at": map[string]interface{}{
"atMobiles": atMobiles,
"isAtAll": isAtAll,
},
}
return SendMessage(msg)
}
func SendMessage(msg interface{}) error {
body := bytes.NewBuffer(nil)
err := json.NewEncoder(body).Encode(msg)
if err != nil {
return fmt.Errorf("msg json failed, msg: %v, err: %v", msg, err.Error())
}
value := url.Values{}
value.Set("access_token", global.GlobalConfig_.Token)
if global.GlobalConfig_.Secret != "" {
t := time.Now().UnixNano() / 1e6
value.Set("timestamp", fmt.Sprintf("%d", t))
value.Set("sign", sign(t, global.GlobalConfig_.Secret))
}
request, err := http.NewRequest(http.MethodPost, global.GlobalConfig_.Url, body)
if err != nil {
return fmt.Errorf("error request: %v", err.Error())
}
request.URL.RawQuery = value.Encode()
request.Header.Add("Content-Type", "application/json")
res, err := (&http.Client{}).Do(request)
if err != nil {
return fmt.Errorf("send dingTalk message failed, error: %v", err.Error())
}
defer func() { _ = res.Body.Close() }()
result, err := ioutil.ReadAll(res.Body)
if res.StatusCode != 200 {
return fmt.Errorf("send dingTalk message failed, %s", httpError(request, res, result, "http code is not 200"))
}
if err != nil {
return fmt.Errorf("send dingTalk message failed, %s", httpError(request, res, result, err.Error()))
}
type response struct {
ErrCode int `json:"errcode"`
}
var ret response
if err := json.Unmarshal(result, &ret); err != nil {
return fmt.Errorf("send dingTalk message failed, %s", httpError(request, res, result, err.Error()))
}
if ret.ErrCode != 0 {
return fmt.Errorf("send dingTalk message failed, %s", httpError(request, res, result, "errcode is not 0"))
}
return nil
}
func httpError(request *http.Request, response *http.Response, body []byte, error string) string {
return fmt.Sprintf(
"http request failure, error: %s, status code: %d, %s %s, body:\n%s",
error,
response.StatusCode,
request.Method,
request.URL.String(),
string(body),
)
}
func sign(t int64, secret string) string {
strToHash := fmt.Sprintf("%d\n%s", t, secret)
hmac256 := hmac.New(sha256.New, []byte(secret))
hmac256.Write([]byte(strToHash))
data := hmac256.Sum(nil)
return base64.StdEncoding.EncodeToString(data)
}
// 其余方法请参考 https://developers.dingtalk.com/document/robots/custom-robot-access?spm=ding_open_doc.document.0.0.7f8710afbfzduV#topic-2026027
+1
View File
@@ -0,0 +1 @@
package utils
File diff suppressed because one or more lines are too long
-2
View File
@@ -5,5 +5,3 @@
项目github:"https://github.com/piexlmax/github.com/flipped-aurora/gin-vue-admin/server"
希望大家给个star多多鼓励
暂时不保存大家生成的结构体 只为方便一次性使用
+2 -2
View File
@@ -4,7 +4,7 @@
{{- range .Fields}}
<el-form-item label="{{.FieldDesc}}:">
{{- if eq .FieldType "bool" }}
<el-switch active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" v-model="formData.{{.FieldJson}}" clearable ></el-switch>
<el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
{{- end }}
{{- if eq .FieldType "string" }}
<el-input v-model="formData.{{.FieldJson}}" clearable placeholder="请输入" />
@@ -19,7 +19,7 @@
{{- end }}
{{- end }}
{{- if eq .FieldType "time.Time" }}
<el-date-picker type="date" placeholder="选择日期" v-model="formData.{{ .FieldJson }}" clearable></el-date-picker>
<el-date-picker v-model="formData.{{ .FieldJson }}" type="date" placeholder="选择日期" clearable></el-date-picker>
{{- end }}
{{- if eq .FieldType "float64" }}
<el-input-number v-model="formData.{{ .FieldJson }}" :precision="2" clearable></el-input-number>
+17 -31
View File
@@ -24,13 +24,15 @@
<el-form-item>
<el-button size="mini" type="primary" icon="el-icon-search" @click="onSubmit">查询</el-button>
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openDialog">新增</el-button>
<el-popover v-model="deleteVisible" placement="top" width="160">
<el-popover v-model:visible="deleteVisible" placement="top" width="160">
<p>确定要删除吗?</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="deleteVisible = false">取消</el-button>
<el-button size="mini" type="primary" @click="onDelete">确定</el-button>
</div>
<el-button slot="reference" icon="el-icon-delete" size="mini" type="danger" style="margin-left: 10px;">批量删除</el-button>
<template #reference>
<el-button icon="el-icon-delete" size="mini" type="danger" style="margin-left: 10px;">批量删除</el-button>
</template>
</el-popover>
</el-form-item>
</el-form>
@@ -46,24 +48,24 @@
>
<el-table-column type="selection" width="55" />
<el-table-column label="日期" width="180">
<template slot-scope="scope">{{ "{{ scope.row.CreatedAt|formatDate }}" }}</template>
<template #default="scope">{{ "{{ formatDate(scope.row.CreatedAt) }}" }}</template>
</el-table-column>
{{- range .Fields}}
{{- if .DictType}}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
<template slot-scope="scope">
<template #default="scope">
{{"{{"}} filterDict(scope.row.{{.FieldJson}},"{{.DictType}}") {{"}}"}}
</template>
</el-table-column>
{{- else if eq .FieldType "bool" }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120">
<template slot-scope="scope">{{ "{{scope.row."}}{{.FieldJson}}{{"|formatBoolean}}" }}</template>
<template #default="scope">{{"{{"}} formatBoolean(scope.row.{{.FieldJson}}) {{"}}"}}</template>
</el-table-column> {{- else }}
<el-table-column label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120" />
{{- end }}
{{- end }}
<el-table-column label="按钮组">
<template slot-scope="scope">
<template #default="scope">
<el-button size="small" type="primary" icon="el-icon-edit" class="table-button" @click="update{{.StructName}}(scope.row)">变更</el-button>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="deleteRow(scope.row)">删除</el-button>
</template>
@@ -79,12 +81,12 @@
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
<el-dialog :before-close="closeDialog" :visible.sync="dialogFormVisible" title="弹窗操作">
<el-dialog v-model="dialogFormVisible" :before-close="closeDialog" title="弹窗操作">
<el-form :model="formData" label-position="right" label-width="80px">
{{- range .Fields}}
<el-form-item label="{{.FieldDesc}}:">
{{- if eq .FieldType "bool" }}
<el-switch active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" v-model="formData.{{.FieldJson}}" clearable ></el-switch>
<el-switch v-model="formData.{{.FieldJson}}" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
{{- end }}
{{- if eq .FieldType "string" }}
<el-input v-model="formData.{{.FieldJson}}" clearable placeholder="请输入" />
@@ -99,7 +101,7 @@
{{- end }}
{{- end }}
{{- if eq .FieldType "time.Time" }}
<el-date-picker type="date" placeholder="选择日期" v-model="formData.{{ .FieldJson }}" clearable />
<el-date-picker v-model="formData.{{ .FieldJson }}" type="date" placeholder="选择日期" clearable />
{{- end }}
{{- if eq .FieldType "float64" }}
<el-input-number v-model="formData.{{ .FieldJson }}" :precision="2" clearable />
@@ -107,10 +109,12 @@
</el-form-item>
{{- end }}
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="closeDialog">取 消</el-button>
<el-button type="primary" @click="enterDialog">确 定</el-button>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeDialog">取 消</el-button>
<el-button type="primary" @click="enterDialog">确 定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
@@ -124,27 +128,9 @@ import {
find{{.StructName}},
get{{.StructName}}List
} from '@/api/{{.PackageName}}' // 此处请自行替换地址
import { formatTimeToStr } from '@/utils/date'
import infoList from '@/mixins/infoList'
export default {
name: '{{.StructName}}',
filters: {
formatDate: function(time) {
if (time !== null && time !== '') {
var date = new Date(time)
return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
} else {
return ''
}
},
formatBoolean: function(bool) {
if (bool != null) {
return bool ? '是' : '否'
} else {
return ''
}
}
},
mixins: [infoList],
data() {
return {
-1
View File
@@ -4,5 +4,4 @@ type RouterGroup struct {
CustomerRouter
ExcelRouter
FileUploadAndDownloadRouter
SimpleUploaderRouter
}
@@ -1,18 +0,0 @@
package example
import (
v1 "github.com/flipped-aurora/gin-vue-admin/server/api/v1"
"github.com/gin-gonic/gin"
)
type SimpleUploaderRouter struct{}
func (e *SimpleUploaderRouter) InitSimpleUploaderRouter(Router *gin.RouterGroup) {
simpleUploaderRouter := Router.Group("simpleUploader")
var simpleUploaderApi = v1.ApiGroupApp.ExampleApiGroup.SimpleUploaderApi
{
simpleUploaderRouter.POST("upload", simpleUploaderApi.SimpleUploaderUpload) // 上传功能
simpleUploaderRouter.GET("checkFileMd5", simpleUploaderApi.CheckFileMd5) // 文件完整度验证
simpleUploaderRouter.GET("mergeFileMd5", simpleUploaderApi.MergeFileMd5) // 合并文件
}
}
-1
View File
@@ -4,5 +4,4 @@ type ServiceGroup struct {
FileUploadAndDownloadService
CustomerService
ExcelService
SimpleUploaderService
}
@@ -1,98 +0,0 @@
package example
import (
"errors"
"fmt"
"io/ioutil"
"os"
"strconv"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/example"
"gorm.io/gorm"
)
type SimpleUploaderService struct {
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: SaveChunk
//@description: 保存文件切片路径
//@param: uploader model.ExaSimpleUploader
//@return: err error
func (exa *SimpleUploaderService) SaveChunk(uploader example.ExaSimpleUploader) (err error) {
return global.GVA_DB.Create(uploader).Error
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: CheckFileMd5
//@description: 检查文件是否已经上传过
//@param: md5 string
//@return: err error, uploads []model.ExaSimpleUploader, isDone bool
func (exa *SimpleUploaderService) CheckFileMd5(md5 string) (err error, uploads []example.ExaSimpleUploader, isDone bool) {
err = global.GVA_DB.Find(&uploads, "identifier = ? AND is_done = ?", md5, false).Error
isDone = errors.Is(global.GVA_DB.First(&example.ExaSimpleUploader{}, "identifier = ? AND is_done = ?", md5, true).Error, gorm.ErrRecordNotFound)
return err, uploads, !isDone
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: MergeFileMd5
//@description: 合并文件
//@param: md5 string, fileName string
//@return: err error
func (exa *SimpleUploaderService) MergeFileMd5(md5 string, fileName string) (err error) {
finishDir := "./finish/"
dir := "./chunk/" + md5
// 如果文件上传成功 不做后续操作 通知成功即可
if !errors.Is(global.GVA_DB.First(&example.ExaSimpleUploader{}, "identifier = ? AND is_done = ?", md5, true).Error, gorm.ErrRecordNotFound) {
return nil
}
// 打开切片文件夹
rd, err := ioutil.ReadDir(dir)
_ = os.MkdirAll(finishDir, os.ModePerm)
// 创建目标文件
fd, err := os.OpenFile(finishDir+fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return
}
// 关闭文件
defer fd.Close()
// 将切片文件按照顺序写入
for k := range rd {
content, _ := ioutil.ReadFile(dir + "/" + fileName + strconv.Itoa(k+1))
_, err = fd.Write(content)
if err != nil {
_ = os.Remove(finishDir + fileName)
}
}
if err != nil {
return err
}
err = global.GVA_DB.Transaction(func(tx *gorm.DB) error {
// 删除切片信息
if err = tx.Delete(&example.ExaSimpleUploader{}, "identifier = ? AND is_done = ?", md5, false).Error; err != nil {
fmt.Println(err)
return err
}
data := example.ExaSimpleUploader{
Identifier: md5,
IsDone: true,
FilePath: finishDir + fileName,
Filename: fileName,
}
// 添加文件信息
if err = tx.Create(&data).Error; err != nil {
fmt.Println(err)
return err
}
return nil
})
err = os.RemoveAll(dir) // 清除切片
return err
}
+1 -1
View File
@@ -247,7 +247,7 @@ func (autoCodeService *AutoCodeService) CreateTemp(autoCode system.AutoCodeStruc
return err
}
if autoCode.AutoMoveFile {
return errors.New("创建代码成功并移动文件成功")
return system.AutoMoveErr
}
return nil
-1
View File
@@ -133,7 +133,6 @@ func (initDBService *InitDBService) InitDB(conf request.InitDB) error {
example.ExaFileUploadAndDownload{},
example.ExaFile{},
example.ExaFileChunk{},
example.ExaSimpleUploader{},
example.ExaCustomer{},
system.SysOperationRecord{},
system.SysAutoCodeHistory{},
-1
View File
@@ -36,7 +36,6 @@ var authorityMenus = []AuthorityMenus{
{"888", 18},
{"888", 19},
{"888", 20},
{"888", 21},
{"888", 22},
{"888", 23},
{"888", 24},
+1 -2
View File
@@ -35,8 +35,7 @@ var menus = []system.SysBaseMenu{
{GVA_MODEL: global.GVA_MODEL{ID: 18, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "3", Path: "dictionary", Name: "dictionary", Component: "view/superAdmin/dictionary/sysDictionary.vue", Sort: 5, Meta: system.Meta{Title: "字典管理", Icon: "notebook-2"}},
{GVA_MODEL: global.GVA_MODEL{ID: 19, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: true, ParentId: "3", Path: "dictionaryDetail/:id", Name: "dictionaryDetail", Component: "view/superAdmin/dictionary/sysDictionaryDetail.vue", Sort: 1, Meta: system.Meta{Title: "字典详情", Icon: "s-order"}},
{GVA_MODEL: global.GVA_MODEL{ID: 20, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "3", Path: "operation", Name: "operation", Component: "view/superAdmin/operation/sysOperationRecord.vue", Sort: 6, Meta: system.Meta{Title: "操作历史", Icon: "time"}},
{GVA_MODEL: global.GVA_MODEL{ID: 21, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "9", Path: "simpleUploader", Name: "simpleUploader", Component: "view/example/simpleUploader/simpleUploader", Sort: 6, Meta: system.Meta{Title: "断点续传(插件版)", Icon: "upload"}},
{GVA_MODEL: global.GVA_MODEL{ID: 22, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "0", Path: "https://www.github.com/flipped-aurora/gin-vue-admin/server.com", Name: "https://www.github.com/flipped-aurora/gin-vue-admin/server.com", Hidden: false, Component: "/", Sort: 0, Meta: system.Meta{Title: "官方网站", Icon: "s-home"}},
{GVA_MODEL: global.GVA_MODEL{ID: 22, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "0", Path: "https://www.gin-vue-admin.com", Name: "https://www.gin-vue-admin.com", Hidden: false, Component: "/", Sort: 0, Meta: system.Meta{Title: "官方网站", Icon: "s-home"}},
{GVA_MODEL: global.GVA_MODEL{ID: 23, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "0", Path: "state", Name: "state", Hidden: false, Component: "view/system/state.vue", Sort: 6, Meta: system.Meta{Title: "服务器状态", Icon: "cloudy"}},
{GVA_MODEL: global.GVA_MODEL{ID: 24, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "14", Path: "autoCodeAdmin", Name: "autoCodeAdmin", Hidden: false, Component: "view/systemTools/autoCodeAdmin/index.vue", Sort: 1, Meta: system.Meta{Title: "自动化代码管理", Icon: "s-finance"}},
{GVA_MODEL: global.GVA_MODEL{ID: 25, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "14", Path: "autoCodeEdit/:id", Name: "autoCodeEdit", Hidden: true, Component: "view/systemTools/autoCode/index.vue", Sort: 0, Meta: system.Meta{Title: "自动化代码(复用)", Icon: "s-finance"}},