From 35886fde4f9f2ebc644402103f3ea29a909fb455 Mon Sep 17 00:00:00 2001 From: zchengo <1933757688@qq.com> Date: Tue, 24 Jan 2023 20:03:35 +0800 Subject: [PATCH] feat: mail config and mail send --- server/api/config.go | 78 +++++++++++++ server/api/customer.go | 16 +++ server/common/mail.go | 28 ++++- server/dao/config.go | 82 ++++++++++++++ server/initialize/router.go | 7 ++ server/models/common.go | 11 ++ server/models/config.go | 39 +++++++ server/models/customer.go | 7 ++ server/response/errcode.go | 7 ++ server/service/config.go | 63 +++++++++++ server/service/customer.go | 30 +++++ web/src/api/config.js | 37 ++++++ web/src/api/customer.js | 9 ++ web/src/router/index.js | 6 + web/src/views/Config.vue | 217 ++++++++++++++++++++++++++++++++++++ web/src/views/Customer.vue | 92 ++++++++++++++- web/src/views/Home.vue | 10 +- 17 files changed, 731 insertions(+), 8 deletions(-) create mode 100644 server/api/config.go create mode 100644 server/dao/config.go create mode 100644 server/models/config.go create mode 100644 server/service/config.go create mode 100644 web/src/api/config.js create mode 100644 web/src/views/Config.vue diff --git a/server/api/config.go b/server/api/config.go new file mode 100644 index 0000000..fac674d --- /dev/null +++ b/server/api/config.go @@ -0,0 +1,78 @@ +package api + +import ( + "crm/models" + "crm/response" + "crm/service" + "log" + "strconv" + + "github.com/gin-gonic/gin" +) + +type MailConfigApi struct { + mailConfigService *service.MailConfigService +} + +func NewMailConfigApi() *MailConfigApi { + return &MailConfigApi{ + mailConfigService: service.NewMailConfigService(), + } +} + +// 保存邮件服务配置 +func (m *MailConfigApi) Save(context *gin.Context) { + var param models.MailConfigSaveParam + uid, _ := strconv.Atoi(context.Request.Header.Get("uid")) + err := context.ShouldBind(¶m) + if uid <= 0 || err != nil { + response.Result(response.ErrCodeParamInvalid, nil, context) + log.Println(err) + return + } + param.Creator = int64(uid) + errCode := m.mailConfigService.Save(¶m) + if errCode == 0 { + mailConfig, errCode := m.mailConfigService.GetInfo(int64(uid)) + response.Result(errCode, mailConfig, context) + return + } + response.Result(errCode, nil, context) +} + +// 开启或关闭邮件服务 +func (m *MailConfigApi) UpdateStatus(context *gin.Context) { + var param models.MailConfigStatusParam + uid, _ := strconv.Atoi(context.Request.Header.Get("uid")) + err := context.ShouldBind(¶m) + if uid <= 0 || err != nil { + response.Result(response.ErrCodeParamInvalid, nil, context) + log.Println(err) + return + } + param.Creator = int64(uid) + errCode := m.mailConfigService.UpdateStatus(¶m) + response.Result(errCode, nil, context) +} + +// 获取邮件服务配置信息 +func (m *MailConfigApi) GetInfo(context *gin.Context) { + uid, _ := strconv.Atoi(context.Request.Header.Get("uid")) + if uid <= 0 { + response.Result(response.ErrCodeParamInvalid, nil, context) + return + } + mailConfig, errCode := m.mailConfigService.GetInfo(int64(uid)) + response.Result(errCode, mailConfig, context) +} + +// 检查邮件配置的有效性 +func (m *MailConfigApi) Check(context *gin.Context) { + uid, _ := strconv.Atoi(context.Request.Header.Get("uid")) + if uid <= 0 { + response.Result(response.ErrCodeParamInvalid, nil, context) + return + } + errCode := m.mailConfigService.Check(int64(uid)) + response.Result(errCode, nil, context) +} \ No newline at end of file diff --git a/server/api/customer.go b/server/api/customer.go index 68d13fa..e01cbba 100644 --- a/server/api/customer.go +++ b/server/api/customer.go @@ -4,6 +4,7 @@ import ( "crm/models" "crm/response" "crm/service" + "log" "strconv" "github.com/gin-gonic/gin" @@ -45,6 +46,21 @@ func (c *CustomerApi) Update(context *gin.Context) { response.Result(errCode, nil, context) } +// 发送邮件给客户 +func (c *CustomerApi) SendMail(context *gin.Context) { + var param models.CustomerSendMailParam + uid, _ := strconv.Atoi(context.Request.Header.Get("uid")) + err := context.ShouldBind(¶m) + if uid <= 0 || err != nil { + response.Result(response.ErrCodeParamInvalid, nil, context) + log.Println(err) + return + } + param.Uid = int64(uid) + errCode := c.customerService.SendMail(¶m) + response.Result(errCode, nil, context) +} + // 删除客户 func (c *CustomerApi) Delete(context *gin.Context) { var param models.CustomerDeleteParam diff --git a/server/common/mail.go b/server/common/mail.go index 5ad1133..5ad9e00 100644 --- a/server/common/mail.go +++ b/server/common/mail.go @@ -2,13 +2,15 @@ package common import ( "crm/global" + "crm/models" "crypto/tls" "fmt" + "log" "gopkg.in/gomail.v2" ) -// 发送邮件 +// 发送邮件(系统级别) // QQ邮箱:SMTP 服务器地址:smtp.qq.com(SSL协议端口:465/994 | 非SSL协议端口:25) // 163邮箱:SMTP 服务器地址:smtp.163.com(端口:25) func SendMail(email, content string) error { @@ -30,4 +32,28 @@ func SendMail(email, content string) error { return err } return nil +} + +// 发送邮件给客户(客户级别) +func SendMailToCustomer(mp models.MailParam) error { + m := gomail.NewMessage() + m.SetHeader("From", mp.Sender) + m.SetHeader("To", mp.Receiver) + m.SetHeader("Subject", mp.Subject) + m.SetBody("text/html", mp.Content) + d := gomail.NewDialer(mp.Smtp, mp.Port, mp.Sender, mp.AuthCode) + d.TLSConfig = &tls.Config{InsecureSkipVerify: true} + if err := d.DialAndSend(m); err != nil { + log.Printf("send mail to customer error : %s", err) + return err + } + return nil +} + +// 检测STMP服务是否可连接 +func DialMail(smtp string, port int, sender, authCode string) error { + d := gomail.NewDialer(smtp, port, sender, authCode) + d.TLSConfig = &tls.Config{InsecureSkipVerify: true} + _, err := d.Dial() + return err } \ No newline at end of file diff --git a/server/dao/config.go b/server/dao/config.go new file mode 100644 index 0000000..f900f10 --- /dev/null +++ b/server/dao/config.go @@ -0,0 +1,82 @@ +package dao + +import ( + "crm/global" + "crm/models" + "time" +) + +type MailConfigDao struct { +} + +func NewMailConfigDao() *MailConfigDao { + return &MailConfigDao{} +} + +func (m *MailConfigDao) Save(param *models.MailConfigSaveParam) error { + if !isExists(param.Creator) { + return create(param) + } + return update(param) +} + +func (m *MailConfigDao) GetInfo(uid int64) (*models.MailConfig, error) { + var mc models.MailConfig + err := global.Db.Table(MAIL_CONFIG).Where("creator = ?", uid).First(&mc).Error + if err != nil { + return nil, err + } + return &mc, nil +} + +func (m *MailConfigDao) UpdateStatus(param *models.MailConfigStatusParam) error { + mailConfig := models.MailConfig{ + Status: param.Status, + Updated: time.Now().Unix(), + } + db := global.Db.Model(&mailConfig).Where("id = ? and creator = ?", param.Id, param.Creator) + if err := db.Updates(&mailConfig).Error; err != nil { + return err + } + return nil +} + +func isExists(uid int64) bool { + var mc models.MailConfig + db := global.Db.Table(MAIL_CONFIG).Where("creator = ?", uid).First(&mc) + return db.RowsAffected != NumberNull +} + +func create(param *models.MailConfigSaveParam) error { + mailConfig := models.MailConfig{ + Stmp: param.Stmp, + Port: param.Port, + AuthCode: param.AuthCode, + Email: param.Email, + Status: 1, + Creator: param.Creator, + Created: time.Now().Unix(), + } + if err := global.Db.Create(&mailConfig).Error; err != nil { + return err + } + return nil +} + +func update(param *models.MailConfigSaveParam) error { + mailConfig := models.MailConfig{ + Id: param.Id, + Stmp: param.Stmp, + Port: param.Port, + AuthCode: param.AuthCode, + Email: param.Email, + Status: param.Status, + Creator: param.Creator, + Updated: time.Now().Unix(), + } + db := global.Db.Model(&mailConfig).Select("*").Omit("id", "creator", "created") + if err := db.Updates(&mailConfig).Error; err != nil { + return err + } + return nil +} diff --git a/server/initialize/router.go b/server/initialize/router.go index bda8bf9..d3c4d2b 100644 --- a/server/initialize/router.go +++ b/server/initialize/router.go @@ -39,6 +39,7 @@ func Router() { route.GET("/customer/option", api.NewCustomerApi().QueryOption) route.GET("/customer/export", api.NewCustomerApi().Export) route.POST("/customer/create", api.NewCustomerApi().Create) + route.POST("/customer/send", api.NewCustomerApi().SendMail) route.PUT("/customer/update", api.NewCustomerApi().Update) route.DELETE("/customer/delete", api.NewCustomerApi().Delete) @@ -62,6 +63,12 @@ func Router() { // 仪表盘模块 route.GET("/dashboard/sum", api.NewDashboardApi().Summary) + // 配置模块 + route.GET("/config/info", api.NewMailConfigApi().GetInfo) + route.GET("/config/check", api.NewMailConfigApi().Check) + route.PUT("/config/save", api.NewMailConfigApi().Save) + route.PUT("/config/status", api.NewMailConfigApi().UpdateStatus) + // 订阅模块 route.GET("/subscribe/info", api.NewSubscribeApi().GetInfo) route.POST("/subscribe/pay", api.NewSubscribeApi().Pay) diff --git a/server/models/common.go b/server/models/common.go index 4542afa..e8cdcb5 100644 --- a/server/models/common.go +++ b/server/models/common.go @@ -5,3 +5,14 @@ type Page struct { PageNum int `form:"pageNum" json:"pageNum"` PageSize int `form:"pageSize" json:"pageSize"` } + +// 发送邮件参数模型 +type MailParam struct { + Smtp string + Port int + AuthCode string + Sender string + Subject string + Content string + Receiver string +} diff --git a/server/models/config.go b/server/models/config.go new file mode 100644 index 0000000..01fbd39 --- /dev/null +++ b/server/models/config.go @@ -0,0 +1,39 @@ +package models + +type MailConfig struct { + Id int64 `gorm:"primaryKey"` + Stmp string `gorm:"stmp"` + Port int `gorm:"port"` + AuthCode string `gorm:"auth_code"` + Email string `gorm:"email"` + Status int `gorm:"status"` + Creator int64 `gorm:"creator"` + Created int64 `gorm:"created"` + Updated int64 `gorm:"updated"` +} + +type MailConfigSaveParam struct { + Id int64 `json:"id" binding:"omitempty,gt=0"` + Stmp string `json:"stmp" binding:"omitempty,ip|hostname"` + Port int `json:"port" binding:"omitempty,gt=0"` + AuthCode string `json:"authCode" binding:"omitempty,gt=0"` + Email string `json:"email" binding:"omitempty,email"` + Status int `json:"status" binding:"omitempty,oneof=1 2"` + Creator int64 `json:"creator" binding:"omitempty"` +} + +type MailConfigStatusParam struct { + Id int64 `json:"id" binding:"required,gt=0"` + Status int `json:"status" binding:"required,oneof=1 2"` + Creator int64 `json:"creator" binding:"omitempty"` +} + +type MailConfigInfo struct { + Id int64 `json:"id"` + Stmp string `json:"stmp"` + Port int `json:"port"` + AuthCode string `json:"authCode"` + Email string `json:"email"` + Status int `json:"status"` + Usability int `json:"usability"` +} diff --git a/server/models/customer.go b/server/models/customer.go index 260b080..831b069 100644 --- a/server/models/customer.go +++ b/server/models/customer.go @@ -45,6 +45,13 @@ type CustomerUpdateParam struct { Status int `json:"status" binding:"-"` } +type CustomerSendMailParam struct { + Uid int64 `json:"uid" binding:"-"` + Receiver string `json:"receiver" binding:"required,email"` + Subject string `json:"subject" binding:"omitempty,gt=0"` + Content string `json:"content" binding:"required,gt=0"` +} + type CustomerDeleteParam struct { Ids []int64 `json:"ids" binding:"required"` } diff --git a/server/response/errcode.go b/server/response/errcode.go index 47174c0..57bf5fe 100644 --- a/server/response/errcode.go +++ b/server/response/errcode.go @@ -23,6 +23,10 @@ const ( ErrCodePayFailed = 20001 // 支付宝支付失败 ErrCodeFileExportFailed = 30001 // 文件导出失败 + + ErrCodeMailConfigInvalid = 50001 // 邮件服务配置无效 + ErrCodeMailSendFailed = 50002 // 邮件发送失败 + ErrCodeMailSendUnEnable = 50003 // 邮件服务未开启 ) var msg = map[int]string{ @@ -42,4 +46,7 @@ var msg = map[int]string{ ErrCodeEmailFormatInvalid: "email format invalid", ErrCodeUserPassResetFailed: "user password reset failed", ErrCodeFileExportFailed: "file export failed", + ErrCodeMailConfigInvalid: "mail config invalid", + ErrCodeMailSendFailed: "mail send failed", + ErrCodeMailSendUnEnable: "mail send server unEnable", } diff --git a/server/service/config.go b/server/service/config.go new file mode 100644 index 0000000..f26c3ed --- /dev/null +++ b/server/service/config.go @@ -0,0 +1,63 @@ +package service + +import ( + "crm/common" + "crm/dao" + "crm/models" + "crm/response" +) + +type MailConfigService struct { + mailConfigDao *dao.MailConfigDao +} + +func NewMailConfigService() *MailConfigService { + return &MailConfigService{ + mailConfigDao: dao.NewMailConfigDao(), + } +} + +// 保存邮件服务配置 +func (m *MailConfigService) Save(param *models.MailConfigSaveParam) int { + if err := m.mailConfigDao.Save(param); err != nil { + return response.ErrCodeFailed + } + return response.ErrCodeSuccess +} + +// 开启或关闭邮件服务 +func (m *MailConfigService) UpdateStatus(param *models.MailConfigStatusParam) int { + if err := m.mailConfigDao.UpdateStatus(param); err != nil { + return response.ErrCodeFailed + } + return response.ErrCodeSuccess +} + +// 获取邮件配置信息 +func (m *MailConfigService) GetInfo(uid int64) (*models.MailConfigInfo, int) { + mc, err := m.mailConfigDao.GetInfo(uid) + if err != nil { + return nil, response.ErrCodeFailed + } + mailConfig := models.MailConfigInfo{ + Id: mc.Id, + Stmp: mc.Stmp, + Port: mc.Port, + AuthCode: mc.AuthCode, + Email: mc.Email, + Status: mc.Status, + } + return &mailConfig, response.ErrCodeSuccess +} + +// 检查邮件配置的有效性 +func (m *MailConfigService) Check(uid int64) int { + mc, err := m.mailConfigDao.GetInfo(uid) + if err != nil { + return response.ErrCodeMailConfigInvalid + } + if err = common.DialMail(mc.Stmp, mc.Port, mc.Email, mc.AuthCode); err != nil { + return response.ErrCodeMailConfigInvalid + } + return response.ErrCodeSuccess +} diff --git a/server/service/customer.go b/server/service/customer.go index 23ad506..e42d79b 100644 --- a/server/service/customer.go +++ b/server/service/customer.go @@ -9,6 +9,11 @@ import ( "time" ) +const ( + Open = 1 + Close = 2 +) + type CustomerService struct { } @@ -57,6 +62,31 @@ func (c *CustomerService) Update(param *models.CustomerUpdateParam) int { return response.ErrCodeSuccess } +// 发送邮件给客户 +func (c *CustomerService) SendMail(param *models.CustomerSendMailParam) int { + var mc models.MailConfig + err := global.Db.Model(&models.MailConfig{}).Where("creator = ?", param.Uid).First(&mc).Error + if err != nil { + return response.ErrCodeMailSendFailed + } + if mc.Status == Close { + return response.ErrCodeMailSendUnEnable + } + mailParam := models.MailParam{ + Smtp: mc.Stmp, + Port: mc.Port, + AuthCode: mc.AuthCode, + Sender: mc.Email, + Subject: param.Subject, + Content: param.Content, + Receiver: param.Receiver, + } + if err := common.SendMailToCustomer(mailParam); err != nil { + return response.ErrCodeMailSendFailed + } + return response.ErrCodeSuccess +} + // 删除客户 func (c *CustomerService) Delete(param *models.CustomerDeleteParam) int { if err := global.Db.Delete(&models.Customer{}, param.Ids).Error; err != nil { diff --git a/web/src/api/config.js b/web/src/api/config.js new file mode 100644 index 0000000..6701e70 --- /dev/null +++ b/web/src/api/config.js @@ -0,0 +1,37 @@ +import request from '../axios/index' + +// 保存邮件服务配置 +export function saveMailConfig(param) { + return request({ + url: '/config/save', + method: 'put', + data: param, + }) +} + +// 开启或关闭邮件服务 +export function updateMailConfigStmpStatus(param) { + return request({ + url: '/config/status', + method: 'put', + data: param, + }) +} + +// 获取邮件服务配置信息 +export function getMailConfig(param) { + return request({ + url: '/config/info', + method: 'get', + data: param, + }) +} + +// 检查邮件配置的有效性 +export function checkMailConfig(param) { + return request({ + url: '/config/check', + method: 'get', + data: param, + }) +} \ No newline at end of file diff --git a/web/src/api/customer.js b/web/src/api/customer.js index 5bfad00..e01e43c 100644 --- a/web/src/api/customer.js +++ b/web/src/api/customer.js @@ -18,6 +18,15 @@ export function updateCustomer(param) { }) } +// 发送邮件给客户 +export function sendMailToCustomer(param) { + return request({ + url: '/customer/send', + method: 'post', + data: param, + }) +} + // 删除客户 export function deleteCustomer(param) { return request({ diff --git a/web/src/router/index.js b/web/src/router/index.js index 86e2009..ac7826e 100644 --- a/web/src/router/index.js +++ b/web/src/router/index.js @@ -11,6 +11,7 @@ import Dashboard from '../views/Dashboard.vue' import Customer from '../views/Customer.vue' import Contract from '../views/Contract.vue' import Product from '../views/Product.vue' +import Config from '../views/Config.vue' import Subscribe from '../views/Subscribe.vue' import Result from '../views/Result.vue' @@ -59,6 +60,11 @@ const routes = [ name: 'product', component: Product, }, + { + path: '/config', + name: 'config', + component: Config, + }, { path: '/subscribe', name: 'subscribe', diff --git a/web/src/views/Config.vue b/web/src/views/Config.vue new file mode 100644 index 0000000..b5bf636 --- /dev/null +++ b/web/src/views/Config.vue @@ -0,0 +1,217 @@ + + + \ No newline at end of file diff --git a/web/src/views/Customer.vue b/web/src/views/Customer.vue index 3a193b3..c75fed4 100644 --- a/web/src/views/Customer.vue +++ b/web/src/views/Customer.vue @@ -1,5 +1,5 @@