Browse Source

邮件处理逻辑提交 1版

pull/42/head
yehongyang 3 years ago
parent
commit
e0f8d15ec7
11 changed files with 1057 additions and 20 deletions
  1. +19
    -0
      dao/base/EscalateModelHead.dao.go
  2. +55
    -0
      dao/base/implments/EscalateModelHead.dao.impl.go
  3. +0
    -2
      main.go
  4. +18
    -17
      models/base/EscalateMsg.model.go
  5. +149
    -1
      services/base/implments/EscalateModelHead.service.impl.go
  6. +9
    -0
      services/base/implments/EscalateMsg.service.impl.go
  7. +193
    -0
      utils/email.go
  8. +110
    -0
      utils/mysmtp/auth.go
  9. +47
    -0
      utils/mysmtp/mysmtp.go
  10. +423
    -0
      utils/mysmtp/smtp.go
  11. +34
    -0
      utils/reflect.go

+ 19
- 0
dao/base/EscalateModelHead.dao.go View File

@ -75,6 +75,25 @@ type EscalateModelHeadDAO interface {
*
******************************************************************************/
SelectOne(string) (*model.EscalateModelHead, error)
/******************************************************************************
*
* @Function Name : SelectAll
*-----------------------------------------------------------------------------
*
* @Description : 查找所有EscalateModelHead
*
* @Function Parameters : 主键
*
* @Return Value : 查找到的EscalateModelHead
*
* @Return Value : 执行时发生的错误
*
* @Author : 代码生成器创建
*
* @Date : 2021-10-13 10:39:41
*
******************************************************************************/
SelectAll() (map[string]model.EscalateModelHead, error)
/******************************************************************************
*
* @Function Name : UpdateOne


+ 55
- 0
dao/base/implments/EscalateModelHead.dao.impl.go View File

@ -98,6 +98,61 @@ func (impl *EscalateModelHeadDAOImplement) SelectOne(escalateModelId string) (*m
return &data, nil
}
/******************************************************************************
*
* @Reference leit.com/LAPP_CHEERSSON_BACKEND/dao/base/EscalateModelHeadDAO.SelectAll
*
******************************************************************************/
func (impl *EscalateModelHeadDAOImplement) SelectAll() (map[string]model.EscalateModelHead, error) {
parameters := []interface{}{impl.plantNr}
where := fmt.Sprintf("%s = ?", meta.EscalateModelHead_PlantNr.ColumnName)
session := impl.session.Table(impl.meta.TableName)
session = session.Where(where, parameters...)
session = session.OrderBy(meta.EscalateModelHead_PlantNr.ColumnName)
var data []model.EscalateModelHead
err := session.Find(&data)
if err != nil {
return nil, grmi.NewDataBaseError(err)
}
newData := make(map[string]model.EscalateModelHead)
var oneKey []string
for _, _v := range data {
oneKey = append(oneKey, _v.EscalateModelId)
}
//查询EscalateModelLevellst
var leveList []model.EscalateModelLevellst
leveListMap := make(map[string][]model.EscalateModelLevellst)
err = impl.session.Table(meta.EscalateModelLevellst.TableName).In("EscalateModelId", oneKey).Find(&leveList)
if err != nil {
return nil, grmi.NewDataBaseError(err)
}
//查询EscalateModelLevelDetail
var detailList []model.EscalateModelLevelDetail
dateilListMap := make(map[string][]model.EscalateModelLevelDetail)
err = impl.session.Table(meta.EscalateModelLevelDetail.TableName).OrderBy(meta.EscalateModelLevellst_EscalateLevel.Name+" asc").In("EscalateModelId", oneKey).Find(&detailList)
if err != nil {
return nil, grmi.NewDataBaseError(err)
}
//将详情放入map数组
for _, _v := range detailList {
dateilListMap[string(_v.EscalateLevel)+","+_v.EscalateModelId] = append(dateilListMap[string(_v.EscalateLevel)+","+_v.EscalateModelId], _v)
}
//将详情map 放入listmap
for _, _v := range leveList {
_v.EscalateModelLevelDetail = dateilListMap[string(_v.EscalateLevel)+","+_v.EscalateModelId]
leveListMap[_v.EscalateModelId] = append(leveListMap[_v.EscalateModelId], _v)
}
//将详情map放入head
//将详情放入级别map
for _, _v := range data {
_v.EscalateModelLevellst = leveListMap[_v.EscalateModelId]
newData[_v.EscalateModelId] = _v
}
return newData, nil
}
/******************************************************************************
*
* @Reference leit.com/LAPP_CHEERSSON_BACKEND/dao/base/EscalateModelHeadDAO.UpdateOne


+ 0
- 2
main.go View File

@ -120,8 +120,6 @@ func imain() {
if err != nil {
log.Fatal("init logger config failed, error:", err.Error())
}
//启动监听端口
app.Run(iris.Addr(":9003"), iris.WithConfiguration(conf.C))


+ 18
- 17
models/base/EscalateMsg.model.go View File

@ -20,23 +20,24 @@ import (
*
******************************************************************************/
type EscalateMsg struct {
PlantNr int `xorm:"pk int 'PlantNr'" json:"EscalateMsg-PlantNr"`
MsgId string `xorm:"pk nvarchar(40) 'MsgId'" json:"EscalateMsg-MsgId"`
EscalateModelId string `xorm:"nvarchar(40) 'EscalateModelId' not null" json:"EscalateMsg-EscalateModelId"`
EscalateLevel int `xorm:"int 'EscalateLevel' not null" json:"EscalateMsg-EscalateLevel"`
Descr string `xorm:"nvarchar(100) 'Descr' not null" json:"EscalateMsg-Descr"`
Status int `xorm:"int 'Status' not null" json:"EscalateMsg-Status"`
EscalateStatus int `xorm:"int 'EscalateStatus' not null" json:"EscalateMsg-EscalateStatus"`
EscalateTime grmi.DateTime `xorm:"datetime 'EscalateTime'" json:"EscalateMsg-EscalateTime"`
CtrlPara1 int `xorm:"int 'CtrlPara1' not null" json:"EscalateMsg-CtrlPara1"`
CtrlPara2 int `xorm:"int 'CtrlPara2' not null" json:"EscalateMsg-CtrlPara2"`
CtrlStr1 string `xorm:"nvarchar(255) 'CtrlStr1' not null" json:"EscalateMsg-CtrlStr1"`
CtrlStr2 string `xorm:"nvarchar(255) 'CtrlStr2' not null" json:"EscalateMsg-CtrlStr2"`
CtrlTime1 grmi.DateTime `xorm:"datetime 'CtrlTime1'" json:"EscalateMsg-CtrlTime1"`
CtrlTime2 grmi.DateTime `xorm:"datetime 'CtrlTime2'" json:"EscalateMsg-CtrlTime2"`
LastModify grmi.DateTime `xorm:"datetime 'LastModify' not null updated" json:"EscalateMsg-LastModify"`
LastUser string `xorm:"nvarchar(20) 'LastUser' not null" json:"EscalateMsg-LastUser"`
CreateTime grmi.DateTime `xorm:"datetime 'CreateTime' not null created" json:"EscalateMsg-CreateTime"`
PlantNr int `xorm:"pk int 'PlantNr'" json:"EscalateMsg-PlantNr"`
MsgId string `xorm:"pk nvarchar(40) 'MsgId'" json:"EscalateMsg-MsgId"`
EscalateModelId string `xorm:"nvarchar(40) 'EscalateModelId' not null" json:"EscalateMsg-EscalateModelId"`
EscalateLevel int `xorm:"int 'EscalateLevel' not null" json:"EscalateMsg-EscalateLevel"`
Descr string `xorm:"nvarchar(100) 'Descr' not null" json:"EscalateMsg-Descr"`
Status int `xorm:"int 'Status' not null" json:"EscalateMsg-Status"`
EscalateStatus int `xorm:"int 'EscalateStatus' not null" json:"EscalateMsg-EscalateStatus"`
EscalateTime grmi.DateTime `xorm:"datetime 'EscalateTime'" json:"EscalateMsg-EscalateTime"`
CtrlPara1 int `xorm:"int 'CtrlPara1' not null" json:"EscalateMsg-CtrlPara1"`
CtrlPara2 int `xorm:"int 'CtrlPara2' not null" json:"EscalateMsg-CtrlPara2"`
CtrlStr1 string `xorm:"nvarchar(255) 'CtrlStr1' not null" json:"EscalateMsg-CtrlStr1"`
CtrlStr2 string `xorm:"nvarchar(255) 'CtrlStr2' not null" json:"EscalateMsg-CtrlStr2"`
CtrlTime1 grmi.DateTime `xorm:"datetime 'CtrlTime1'" json:"EscalateMsg-CtrlTime1"`
CtrlTime2 grmi.DateTime `xorm:"datetime 'CtrlTime2'" json:"EscalateMsg-CtrlTime2"`
LastModify grmi.DateTime `xorm:"datetime 'LastModify' not null updated" json:"EscalateMsg-LastModify"`
LastUser string `xorm:"nvarchar(20) 'LastUser' not null" json:"EscalateMsg-LastUser"`
CreateTime grmi.DateTime `xorm:"datetime 'CreateTime' not null created" json:"EscalateMsg-CreateTime"`
EscalateMsgDetail []EscalateMsgDetail `xorm:"-" json:"EscalateMsg-EscalateMsgDetail"`
}
/******************************************************************************


+ 149
- 1
services/base/implments/EscalateModelHead.service.impl.go View File

@ -9,6 +9,10 @@ import (
"leit.com/LAPP_CHEERSSON_BACKEND/grmi"
meta "leit.com/LAPP_CHEERSSON_BACKEND/meta/base"
model "leit.com/LAPP_CHEERSSON_BACKEND/models/base"
"leit.com/LAPP_CHEERSSON_BACKEND/utils"
"log"
"strconv"
"time"
)
/******************************************************************************
@ -42,6 +46,9 @@ var DefaultConditionOfEscalateModelHead = grmi.NewCondition(
nil,
)
//创建一个模板缓存type
var headMsgConfig map[string]model.EscalateModelHead
/******************************************************************************
*
* @Description : EscalateModelHead的默认分页查询条件
@ -86,7 +93,31 @@ type EscalateModelHeadServiceImplement struct {
*
******************************************************************************/
func NewEscalateModelHeadServiceImplement() *EscalateModelHeadServiceImplement {
return &EscalateModelHeadServiceImplement{}
escalateModelHeadServiceImplement := &EscalateModelHeadServiceImplement{}
//创建队列
utils.EmailSendFoudnMap["escalatemodelhead"] = escalateModelHeadServiceImplement
utils.EmailSendStruct["escalatemodelhead"] = utils.NewEmailSendStruct(10, "escalatemodelhead", 10)
headMsgConfig = make(map[string]model.EscalateModelHead)
//将配置放入map中
engine := db.Eloquent.Master()
session := engine.NewSession()
escalateModelHeadDAO := dal.NewEscalateModelHeadDAO(session, model.PlantNr, "system")
_headMsgConfig, err := escalateModelHeadDAO.SelectAll()
if err != nil {
panic("邮件配置导入失败")
}
headMsgConfig = _headMsgConfig
//将未完成信息写入队列
escalateMsgDao := dal.NewEscalateMsgDAO(session, model.PlantNr, "system")
urlParameters := make(map[string]string)
urlParameters["Status"] = "2"
predicates, err := DefaultConditionOfEscalateMsg.BuildPredicates(urlParameters)
msgList, err := escalateMsgDao.Select(predicates, nil)
for _, _v := range msgList {
utils.EmailSendStruct["escalatemodelhead"].SeyChannels(utils.ChannelsInfo{Key: _v.MsgId, Info: _v})
}
return escalateModelHeadServiceImplement
}
/******************************************************************************
@ -520,3 +551,120 @@ func (impl *EscalateModelHeadServiceImplement) Update(user *global.User, entitie
}
return nil
}
/**
处理消息
*/
func (impl *EscalateModelHeadServiceImplement) ManageFunction(info utils.ChannelsInfo) (bool, utils.ChannelsInfo) {
data, ok := info.Info.(model.EscalateMsg)
if !ok {
//写入日志
log.Println("消息体类型错误跳过消息")
return false, utils.ChannelsInfo{}
}
//获取下一次需要发送的用户
var nextUser model.EscalateModelLevellst
for _, _v := range headMsgConfig[data.EscalateModelId].EscalateModelLevellst {
if _v.EscalateLevel > data.EscalateLevel {
nextUser = _v
break
}
}
if nextUser.EscalateLevel == 0 {
//没有更高级的 无需在发送邮件
return false, info
}
//判断消息是不是需要再次发送 当前时间小于下次要发送的时间 下次要发送的时间为下一等级间隔时间 + 当前发送时间
var sendTime time.Time
switch nextUser.TimeUom {
case "y":
sendTime = data.EscalateTime.Restore().AddDate(nextUser.EscalateLeadTime, 0, 0)
break
case "m":
sendTime = data.EscalateTime.Restore().AddDate(0, nextUser.EscalateLeadTime, 0)
break
case "d":
sendTime = data.EscalateTime.Restore().AddDate(0, 0, nextUser.EscalateLeadTime)
break
case "h":
sendTime = data.EscalateTime.Restore().Add(time.Duration(nextUser.EscalateLeadTime) * 1 * time.Hour)
break
case "i":
sendTime = data.EscalateTime.Restore().Add(time.Duration(nextUser.EscalateLeadTime) * 1 * time.Minute)
break
case "s":
sendTime = data.EscalateTime.Restore().Add(time.Duration(nextUser.EscalateLeadTime) * 1 * time.Second)
break
default:
log.Println("错误的时间定义")
return false, info
}
if time.Now().Before(sendTime) {
//当前时间在下次发送时间之后 无需发送等待处理
return true, info
}
//需要对下一级发送邮件
data.EscalateLevel = nextUser.EscalateLevel
data.EscalateTime = grmi.DateTime(time.Now())
//将修改提交到数据库
engine := db.Eloquent.Master()
session := engine.NewSession()
defer session.Close()
err := session.Begin()
escalateMsgDao := dal.NewEscalateMsgDAO(session, model.PlantNr, "system")
err = escalateMsgDao.UpdateOne(&data)
if err != nil {
session.Rollback()
//写入日志
log.Println("修改数据库错误跳过信息")
return false, utils.ChannelsInfo{}
}
//写入发送消息明细表
var detailData []model.EscalateMsgDetail
userDao := dal.NewUserDAO(session, model.PlantNr, "system")
for _, _v := range nextUser.EscalateModelLevelDetail {
detailData = append(detailData, model.EscalateMsgDetail{
PlantNr: model.PlantNr,
MsgId: data.MsgId,
Pos: _v.Pos,
InformUserId: _v.InformUserId,
InformType: _v.InformType,
MsgHead: _v.MsgHead,
MsgBody: _v.MsgBody,
MsgSignature: _v.MsgSignature,
Status: 2,
EscalateTime: grmi.DateTime(time.Now()),
ReplyTime: grmi.DateTime(time.Now()),
})
userInfo, _ := userDao.SelectOne(_v.InformUserId)
address := headMsgConfig[data.EscalateModelId].EMailServerHost + ":" + strconv.FormatInt(int64(headMsgConfig[data.EscalateModelId].EMailServerPort), 10)
//发送邮件
err = utils.SendMail(
headMsgConfig[data.EscalateModelId].FromEmail,
headMsgConfig[data.EscalateModelId].EmailPwd,
address,
userInfo.Email,
"system",
_v.MsgHead,
_v.MsgBody,
"",
)
}
escalateMsgDetailDao := dal.NewEscalateMsgDetailDAO(session, model.PlantNr, "system")
err = escalateMsgDetailDao.Insert(&detailData)
if err != nil {
session.Rollback()
log.Println("明细表添加错误 停止发送")
return false, info
}
err = session.Commit()
if err != nil {
log.Println("事务提交失败 停止发送")
return false, info
}
info.Info = data
return true, info
}

+ 9
- 0
services/base/implments/EscalateMsg.service.impl.go View File

@ -160,6 +160,15 @@ func (impl *EscalateMsgServiceImplement) SelectOne(user *global.User, msgId stri
if err != nil {
return nil, err
}
//查询对应详细
detailDao := dal.NewEscalateMsgDetailDAO(session, user.PlantNr, user.UserId)
dataList, err := detailDao.Select([]grmi.Predicate{
meta.EscalateMsgDetail_MsgId.NewPredicate(grmi.Equal, result.MsgId),
}, nil)
if err != nil {
return nil, err
}
result.EscalateMsgDetail = dataList
return result, nil
}


+ 193
- 0
utils/email.go View File

@ -0,0 +1,193 @@
package utils
import (
"leit.com/LAPP_CHEERSSON_BACKEND/utils/mysmtp"
"log"
"reflect"
"strings"
"sync"
"time"
)
var EmailSendStruct = make(map[string]*emailSendStruct)
var EmailSendFoudnMap = make(map[string]interface{})
type emailSendStruct struct {
channels chan ChannelsInfo
channelCount int
successMap map[string]int //完成状态 如果map中包含消息下角标表明消息已完成发送
successMapMutex sync.Mutex
manageCount int //需要开启消费者的数量
manageStartCount int //已经开启消费者的数量
manageMaxCount int //最大开启数量
manageAverage int //开启,关闭一条信道基数
serviceName string //使用业务层
}
func NewEmailSendStruct(maxCount int, serviceName string, manageAverage int) *emailSendStruct {
model := &emailSendStruct{}
model.channels = make(chan ChannelsInfo)
model.channelCount = 0
model.manageCount = 1
model.manageStartCount = 1
model.manageMaxCount = maxCount
model.successMap = make(map[string]int)
model.successMapMutex = sync.Mutex{}
model.serviceName = serviceName
model.manageAverage = manageAverage
//开启主信道消费
go model.startSend()
//开启动态信道消费
go model.dynamic()
return model
}
/***
返回消息数量
*/
func (_this *emailSendStruct) GetChannelCount() int {
return _this.channelCount
}
/***
第一次写入信道
*/
func (_this *emailSendStruct) SeyChannels(info ChannelsInfo) {
_this.channels <- info
_this.channelCount++
if _this.channelCount > (_this.manageStartCount+1)*_this.manageAverage && _this.manageCount < _this.manageMaxCount {
//需要开启一条新通道
_this.manageCount++
}
if _this.channelCount < (_this.manageStartCount-1)*_this.manageAverage {
_this.manageCount--
}
}
/***
将数据重新放入信道
*/
func (_this *emailSendStruct) seyChannels(info ChannelsInfo) {
_this.channels <- info
}
//完成状态 如果map中包含消息下角标表明消息已完成发送
func (_this *emailSendStruct) SetSuccessMap(key string) {
_this.successMapMutex.Lock()
defer _this.successMapMutex.Unlock()
_this.successMap[key] = 1
}
//如果读取到完成信息直接销毁掉map
func (_this *emailSendStruct) getSuccessMap(key string) bool {
_this.successMapMutex.Lock()
defer _this.successMapMutex.Unlock()
_, ok := _this.successMap[key]
if ok {
delete(_this.successMap, key)
_this.channelCount--
}
return ok
}
//开始处理消息
func (_this *emailSendStruct) startSend() {
for {
info := <-_this.channels
_this.manageFunction(info)
}
}
//动态信道
func (_this *emailSendStruct) dynamic() {
for {
if _this.manageCount > _this.manageStartCount {
//需要开启一条信道处理信息
_this.manageStartCount++
go func(_i int) {
log.Println("动态队列开启:", _i)
for {
if _this.manageCount < _this.manageStartCount && _this.manageCount < _i {
log.Println("动态队列关闭", _i)
break
}
info := <-_this.channels
_this.manageFunction(info)
}
_this.manageStartCount--
}(_this.manageStartCount)
}
time.Sleep(2 * time.Second)
}
}
/**
调用处理方法
*/
func (_this *emailSendStruct) manageFunction(info ChannelsInfo) {
if _this.getSuccessMap(info.Key) {
//信息处理完成下一条消息
return
}
refUser := ReflectApi(_this.serviceName, EmailSendFoudnMap)
setNameMethod := refUser.MethodByName("ManageFunction")
args := []reflect.Value{
reflect.ValueOf(info),
} //构造一个类型为reflect.Value的切片
isErr, reflectValue := ReflectApiCall(&setNameMethod, args)
if isErr || (!isErr && reflectValue == nil) {
panic("服务注册异常")
}
var ok bool
var isRepetition bool
for key, value := range reflectValue {
switch key {
case 0:
isRepetition, ok = value.Interface().(bool)
break
case 1:
info, ok = value.Interface().(ChannelsInfo)
break
}
if !ok {
panic("服务返回参数注册异常")
}
}
if isRepetition {
//等待5S后放回信道中
_this.seyChannels(info)
}
return
}
type ChannelsInfo struct {
Key string
Info interface{}
}
/*!
username 发送者邮件
password 授权码
host 主机地址 smtp.qq.com:587 smtp.qq.com:25
to 接收邮箱 多个接收邮箱使用 ; 隔开
name 发送人名称
subject 发送主题
body 发送内容
mailType 发送邮件内容类型
*/
func SendMail(username, password, host, to, name, subject, body, mailType string) error {
hp := strings.Split(host, ":")
auth := mysmtp.LoginAuth(username, password, hp[0])
var contentType string
if mailType == "html" {
contentType = "Content-Type: text/" + mailType + "; charset=UTF-8"
} else {
contentType = "Content-Type: text/plain" + "; charset=UTF-8"
}
msg := []byte("To: " + to + "\r\nFrom: " + name + "<" + username + ">\r\nSubject: " + subject + "\r\n" + contentType + "\r\n\r\n" + body)
sendTo := strings.Split(to, ";")
err := mysmtp.SendMail(host, auth, username, sendTo, msg)
return err
}

+ 110
- 0
utils/mysmtp/auth.go View File

@ -0,0 +1,110 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mysmtp
import (
"crypto/hmac"
"crypto/md5"
"errors"
"fmt"
)
// Auth is implemented by an SMTP authentication mechanism.
type Auth interface {
// Start begins an authentication with a server.
// It returns the name of the authentication protocol
// and optionally data to include in the initial AUTH message
// sent to the server. It can return proto == "" to indicate
// that the authentication should be skipped.
// If it returns a non-nil error, the SMTP client aborts
// the authentication attempt and closes the connection.
Start(server *ServerInfo) (proto string, toServer []byte, err error)
// Next continues the authentication. The server has just sent
// the fromServer data. If more is true, the server expects a
// response, which Next should return as toServer; otherwise
// Next should return toServer == nil.
// If Next returns a non-nil error, the SMTP client aborts
// the authentication attempt and closes the connection.
Next(fromServer []byte, more bool) (toServer []byte, err error)
}
// ServerInfo records information about an SMTP server.
type ServerInfo struct {
Name string // SMTP server name
TLS bool // using TLS, with valid certificate for Name
Auth []string // advertised authentication mechanisms
}
type plainAuth struct {
identity, username, password string
host string
}
// PlainAuth returns an Auth that implements the PLAIN authentication
// mechanism as defined in RFC 4616. The returned Auth uses the given
// username and password to authenticate to host and act as identity.
// Usually identity should be the empty string, to act as username.
//
// PlainAuth will only send the credentials if the connection is using TLS
// or is connected to localhost. Otherwise authentication will fail with an
// error, without sending the credentials.
func PlainAuth(identity, username, password, host string) Auth {
return &plainAuth{identity, username, password, host}
}
func isLocalhost(name string) bool {
return name == "localhost" || name == "127.0.0.1" || name == "::1"
}
func (a *plainAuth) Start(server *ServerInfo) (string, []byte, error) {
// Must have TLS, or else localhost server.
// Note: If TLS is not true, then we can't trust ANYTHING in ServerInfo.
// In particular, it doesn't matter if the server advertises PLAIN auth.
// That might just be the attacker saying
// "it's ok, you can trust me with your password."
if !server.TLS && !isLocalhost(server.Name) {
return "", nil, errors.New("unencrypted connection")
}
if server.Name != a.host {
return "", nil, errors.New("wrong host name")
}
resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password)
return "PLAIN", resp, nil
}
func (a *plainAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
// We've already sent everything.
return nil, errors.New("unexpected server challenge")
}
return nil, nil
}
type cramMD5Auth struct {
username, secret string
}
// CRAMMD5Auth returns an Auth that implements the CRAM-MD5 authentication
// mechanism as defined in RFC 2195.
// The returned Auth uses the given username and secret to authenticate
// to the server using the challenge-response mechanism.
func CRAMMD5Auth(username, secret string) Auth {
return &cramMD5Auth{username, secret}
}
func (a *cramMD5Auth) Start(server *ServerInfo) (string, []byte, error) {
return "CRAM-MD5", nil, nil
}
func (a *cramMD5Auth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
d := hmac.New(md5.New, []byte(a.secret))
d.Write(fromServer)
s := make([]byte, 0, d.Size())
return []byte(fmt.Sprintf("%s %x", a.username, d.Sum(s))), nil
}
return nil, nil
}

+ 47
- 0
utils/mysmtp/mysmtp.go View File

@ -0,0 +1,47 @@
package mysmtp
import "errors"
/*
auth login
*/
type loginAuth struct {
username, password string
host string
}
/*
auth login 验证
*/
func LoginAuth(username, password, host string) Auth {
return &loginAuth{username, password, host}
}
/*
初步验证服务器信息输入账号
*/
func (a *loginAuth) Start(server *ServerInfo) (string, []byte, error) {
// 如果不是安全连接,也不是本地的服务器,报错,不允许不安全的连接
if !server.TLS && !isLocalhost(server.Name) {
return "", nil, errors.New("unencrypted connection")
}
// 如果服务器信息和 Auth 对象的服务器信息不一致,报错
if server.Name != a.host {
return "", nil, errors.New("wrong host name")
}
// 验证时需要的账号
resp := []byte(a.username)
// "auth login" 命令
return "LOGIN", resp, nil
}
/*
进一步进行验证输入密码
*/
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
// 如果服务器需要更多验证,报错
if more {
return []byte(a.password), nil
}
return nil, nil
}

+ 423
- 0
utils/mysmtp/smtp.go View File

@ -0,0 +1,423 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package smtp implements the Simple Mail Transfer Protocol as defined in RFC 5321.
// It also implements the following extensions:
// 8BITMIME RFC 1652
// AUTH RFC 2554
// STARTTLS RFC 3207
// Additional extensions may be handled by clients.
//
// The smtp package is frozen and is not accepting new features.
// Some external packages provide more functionality. See:
//
// https://godoc.org/?q=smtp
package mysmtp
import (
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/textproto"
"strings"
)
// A Client represents a client connection to an SMTP server.
type Client struct {
// Text is the textproto.Conn used by the Client. It is exported to allow for
// clients to add extensions.
Text *textproto.Conn
// keep a reference to the connection so it can be used to create a TLS
// connection later
conn net.Conn
// whether the Client is using TLS
tls bool
serverName string
// map of supported extensions
ext map[string]string
// supported auth mechanisms
auth []string
localName string // the name to use in HELO/EHLO
didHello bool // whether we've said HELO/EHLO
helloError error // the error from the hello
}
// Dial returns a new Client connected to an SMTP server at addr.
// The addr must include a port, as in "mail.example.com:smtp".
func Dial(addr string) (*Client, error) {
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
host, _, _ := net.SplitHostPort(addr)
return NewClient(conn, host)
}
// NewClient returns a new Client using an existing connection and host as a
// server name to be used when authenticating.
func NewClient(conn net.Conn, host string) (*Client, error) {
text := textproto.NewConn(conn)
_, _, err := text.ReadResponse(220)
if err != nil {
text.Close()
return nil, err
}
c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
_, c.tls = conn.(*tls.Conn)
return c, nil
}
// Close closes the connection.
func (c *Client) Close() error {
return c.Text.Close()
}
// hello runs a hello exchange if needed.
func (c *Client) hello() error {
if !c.didHello {
c.didHello = true
err := c.ehlo()
if err != nil {
c.helloError = c.helo()
}
}
return c.helloError
}
// Hello sends a HELO or EHLO to the server as the given host name.
// Calling this method is only necessary if the client needs control
// over the host name used. The client will introduce itself as "localhost"
// automatically otherwise. If Hello is called, it must be called before
// any of the other methods.
func (c *Client) Hello(localName string) error {
if err := validateLine(localName); err != nil {
return err
}
if c.didHello {
return errors.New("smtp: Hello called after other methods")
}
c.localName = localName
return c.hello()
}
// cmd is a convenience function that sends a command and returns the response
func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
id, err := c.Text.Cmd(format, args...)
if err != nil {
return 0, "", err
}
c.Text.StartResponse(id)
defer c.Text.EndResponse(id)
code, msg, err := c.Text.ReadResponse(expectCode)
return code, msg, err
}
// helo sends the HELO greeting to the server. It should be used only when the
// server does not support ehlo.
func (c *Client) helo() error {
c.ext = nil
_, _, err := c.cmd(250, "HELO %s", c.localName)
return err
}
// ehlo sends the EHLO (extended hello) greeting to the server. It
// should be the preferred greeting for servers that support it.
func (c *Client) ehlo() error {
_, msg, err := c.cmd(250, "EHLO %s", c.localName)
if err != nil {
return err
}
ext := make(map[string]string)
extList := strings.Split(msg, "\n")
if len(extList) > 1 {
extList = extList[1:]
for _, line := range extList {
args := strings.SplitN(line, " ", 2)
if len(args) > 1 {
ext[args[0]] = args[1]
} else {
ext[args[0]] = ""
}
}
}
if mechs, ok := ext["AUTH"]; ok {
c.auth = strings.Split(mechs, " ")
}
c.ext = ext
return err
}
// StartTLS sends the STARTTLS command and encrypts all further communication.
// Only servers that advertise the STARTTLS extension support this function.
func (c *Client) StartTLS(config *tls.Config) error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(220, "STARTTLS")
if err != nil {
return err
}
c.conn = tls.Client(c.conn, config)
c.Text = textproto.NewConn(c.conn)
c.tls = true
return c.ehlo()
}
// TLSConnectionState returns the client's TLS connection state.
// The return values are their zero values if StartTLS did
// not succeed.
func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool) {
tc, ok := c.conn.(*tls.Conn)
if !ok {
return
}
return tc.ConnectionState(), true
}
// Verify checks the validity of an email address on the server.
// If Verify returns nil, the address is valid. A non-nil return
// does not necessarily indicate an invalid address. Many servers
// will not verify addresses for security reasons.
func (c *Client) Verify(addr string) error {
if err := validateLine(addr); err != nil {
return err
}
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(250, "VRFY %s", addr)
return err
}
// Auth authenticates a client using the provided authentication mechanism.
// A failed authentication closes the connection.
// Only servers that advertise the AUTH extension support this function.
func (c *Client) Auth(a Auth) error {
if err := c.hello(); err != nil {
return err
}
encoding := base64.StdEncoding
mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth})
if err != nil {
c.Quit()
return err
}
resp64 := make([]byte, encoding.EncodedLen(len(resp)))
encoding.Encode(resp64, resp)
code, msg64, err := c.cmd(0, strings.TrimSpace(fmt.Sprintf("AUTH %s %s", mech, resp64)))
for err == nil {
var msg []byte
switch code {
case 334:
msg, err = encoding.DecodeString(msg64)
case 235:
// the last message isn't base64 because it isn't a challenge
msg = []byte(msg64)
default:
err = &textproto.Error{Code: code, Msg: msg64}
}
if err == nil {
resp, err = a.Next(msg, code == 334)
}
if err != nil {
// abort the AUTH
c.cmd(501, "*")
c.Quit()
break
}
if resp == nil {
break
}
resp64 = make([]byte, encoding.EncodedLen(len(resp)))
encoding.Encode(resp64, resp)
code, msg64, err = c.cmd(0, string(resp64))
}
return err
}
// Mail issues a MAIL command to the server using the provided email address.
// If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME
// parameter.
// This initiates a mail transaction and is followed by one or more Rcpt calls.
func (c *Client) Mail(from string) error {
if err := validateLine(from); err != nil {
return err
}
if err := c.hello(); err != nil {
return err
}
cmdStr := "MAIL FROM:<%s>"
if c.ext != nil {
if _, ok := c.ext["8BITMIME"]; ok {
cmdStr += " BODY=8BITMIME"
}
}
_, _, err := c.cmd(250, cmdStr, from)
return err
}
// Rcpt issues a RCPT command to the server using the provided email address.
// A call to Rcpt must be preceded by a call to Mail and may be followed by
// a Data call or another Rcpt call.
func (c *Client) Rcpt(to string) error {
if err := validateLine(to); err != nil {
return err
}
_, _, err := c.cmd(25, "RCPT TO:<%s>", to)
return err
}
type dataCloser struct {
c *Client
io.WriteCloser
}
func (d *dataCloser) Close() error {
d.WriteCloser.Close()
_, _, err := d.c.Text.ReadResponse(250)
return err
}
// Data issues a DATA command to the server and returns a writer that
// can be used to write the mail headers and body. The caller should
// close the writer before calling any more methods on c. A call to
// Data must be preceded by one or more calls to Rcpt.
func (c *Client) Data() (io.WriteCloser, error) {
_, _, err := c.cmd(354, "DATA")
if err != nil {
return nil, err
}
return &dataCloser{c, c.Text.DotWriter()}, nil
}
var testHookStartTLS func(*tls.Config) // nil, except for tests
// SendMail connects to the server at addr, switches to TLS if
// possible, authenticates with the optional mechanism a if possible,
// and then sends an email from address from, to addresses to, with
// message msg.
// The addr must include a port, as in "mail.example.com:smtp".
//
// The addresses in the to parameter are the SMTP RCPT addresses.
//
// The msg parameter should be an RFC 822-style email with headers
// first, a blank line, and then the message body. The lines of msg
// should be CRLF terminated. The msg headers should usually include
// fields such as "From", "To", "Subject", and "Cc". Sending "Bcc"
// messages is accomplished by including an email address in the to
// parameter but not including it in the msg headers.
//
// The SendMail function and the net/smtp package are low-level
// mechanisms and provide no support for DKIM signing, MIME
// attachments (see the mime/multipart package), or other mail
// functionality. Higher-level packages exist outside of the standard
// library.
func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
if err := validateLine(from); err != nil {
return err
}
for _, recp := range to {
if err := validateLine(recp); err != nil {
return err
}
}
c, err := Dial(addr)
if err != nil {
return err
}
defer c.Close()
if err = c.hello(); err != nil {
return err
}
if ok, _ := c.Extension("STARTTLS"); ok {
// 跳过证书验证
config := &tls.Config{ServerName: c.serverName, InsecureSkipVerify: true}
if testHookStartTLS != nil {
testHookStartTLS(config)
}
if err = c.StartTLS(config); err != nil {
return err
}
}
if err = c.Mail(from); err != nil {
return err
}
for _, addr := range to {
if err = c.Rcpt(addr); err != nil {
return err
}
}
w, err := c.Data()
if err != nil {
return err
}
_, err = w.Write(msg)
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
return c.Quit()
}
// Extension reports whether an extension is support by the server.
// The extension name is case-insensitive. If the extension is supported,
// Extension also returns a string that contains any parameters the
// server specifies for the extension.
func (c *Client) Extension(ext string) (bool, string) {
if err := c.hello(); err != nil {
return false, ""
}
if c.ext == nil {
return false, ""
}
ext = strings.ToUpper(ext)
param, ok := c.ext[ext]
return ok, param
}
// Reset sends the RSET command to the server, aborting the current mail
// transaction.
func (c *Client) Reset() error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(250, "RSET")
return err
}
// Noop sends the NOOP command to the server. It does nothing but check
// that the connection to the server is okay.
func (c *Client) Noop() error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(250, "NOOP")
return err
}
// Quit sends the QUIT command and closes the connection to the server.
func (c *Client) Quit() error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(221, "QUIT")
if err != nil {
return err
}
return c.Text.Close()
}
// validateLine checks to see if a line has CR or LF as per RFC 5321
func validateLine(line string) error {
if strings.ContainsAny(line, "\n\r") {
return errors.New("smtp: A line must not contain CR or LF")
}
return nil
}

+ 34
- 0
utils/reflect.go View File

@ -0,0 +1,34 @@
package utils
import (
"reflect"
)
/***
从map中获取反射对象
*/
func ReflectApi(index string, _map map[string]interface{}) *reflect.Value {
data := _map[index]
if data == nil {
return nil
}
refUser := reflect.ValueOf(data)
return &refUser
}
/***
调用反射方法 当反射对象为空 或者不存在反射方法 返回true 成功返回false
*/
func ReflectApiCall(class *reflect.Value, value []reflect.Value) (bool, []reflect.Value) {
defer func() {
if err := recover(); err != nil {
return
}
}()
if class == nil {
return true, nil
}
data := class.Call(value)
return false, data
}

Loading…
Cancel
Save