send.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. package email
  2. import (
  3. "database/sql"
  4. "encoding/base64"
  5. "encoding/json"
  6. "github.com/Jinnrry/pmail/config"
  7. "github.com/Jinnrry/pmail/db"
  8. "github.com/Jinnrry/pmail/dto/parsemail"
  9. "github.com/Jinnrry/pmail/dto/response"
  10. "github.com/Jinnrry/pmail/hooks"
  11. "github.com/Jinnrry/pmail/hooks/framework"
  12. "github.com/Jinnrry/pmail/i18n"
  13. "github.com/Jinnrry/pmail/models"
  14. "github.com/Jinnrry/pmail/utils/array"
  15. "github.com/Jinnrry/pmail/utils/async"
  16. "github.com/Jinnrry/pmail/utils/context"
  17. "github.com/Jinnrry/pmail/utils/send"
  18. log "github.com/sirupsen/logrus"
  19. "github.com/spf13/cast"
  20. "io"
  21. "net/http"
  22. "strings"
  23. "time"
  24. )
  25. type sendRequest struct {
  26. ReplyTo []user `json:"reply_to"`
  27. From user `json:"from"`
  28. To []user `json:"to"`
  29. Bcc []user `json:"bcc"`
  30. Cc []user `json:"cc"`
  31. Subject string `json:"subject"`
  32. Text string `json:"text"` // Plaintext message (optional)
  33. HTML string `json:"html"` // Html message (optional)
  34. Sender user `json:"sender"` // override From as SMTP envelope sender (optional)
  35. ReadReceipt []string `json:"read_receipt"`
  36. Attachments []attachment `json:"attrs"`
  37. }
  38. type user struct {
  39. Name string `json:"name"`
  40. Email string `json:"email"`
  41. }
  42. type attachment struct {
  43. Name string `json:"name"`
  44. Data string `json:"data"`
  45. }
  46. func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
  47. reqBytes, err := io.ReadAll(req.Body)
  48. if err != nil {
  49. log.WithContext(ctx).Errorf("%+v", err)
  50. response.NewErrorResponse(response.ParamsError, "params error", err.Error()).FPrint(w)
  51. return
  52. }
  53. log.WithContext(ctx).Infof("发送邮件")
  54. var reqData sendRequest
  55. err = json.Unmarshal(reqBytes, &reqData)
  56. if err != nil {
  57. log.WithContext(ctx).Errorf("%+v", err)
  58. response.NewErrorResponse(response.ParamsError, "params error", err.Error()).FPrint(w)
  59. return
  60. }
  61. if reqData.From.Email != "" {
  62. infos := strings.Split(reqData.From.Email, "@")
  63. if len(infos) != 2 || !array.InArray(infos[1], config.Instance.Domains) {
  64. response.NewErrorResponse(response.ParamsError, "params error", "").FPrint(w)
  65. return
  66. }
  67. if !ctx.IsAdmin && infos[0] != ctx.UserAccount {
  68. response.NewErrorResponse(response.ParamsError, "params error", "").FPrint(w)
  69. return
  70. }
  71. }
  72. if reqData.From.Email == "" {
  73. reqData.From.Email = ctx.UserAccount + "@" + config.Instance.Domain
  74. }
  75. if reqData.From.Email == "" {
  76. response.NewErrorResponse(response.ParamsError, "发件人必填", "发件人必填").FPrint(w)
  77. return
  78. }
  79. if reqData.From.Name == "" {
  80. reqData.From.Name = ctx.UserName
  81. }
  82. if reqData.Subject == "" {
  83. response.NewErrorResponse(response.ParamsError, "邮件标题必填", "邮件标题必填").FPrint(w)
  84. return
  85. }
  86. if len(reqData.To) <= 0 {
  87. response.NewErrorResponse(response.ParamsError, "收件人必填", "收件人必填").FPrint(w)
  88. return
  89. }
  90. e := &parsemail.Email{}
  91. for _, to := range reqData.To {
  92. e.To = append(e.To, &parsemail.User{
  93. Name: to.Name,
  94. EmailAddress: to.Email,
  95. })
  96. }
  97. for _, bcc := range reqData.Bcc {
  98. e.Bcc = append(e.Bcc, &parsemail.User{
  99. Name: bcc.Name,
  100. EmailAddress: bcc.Email,
  101. })
  102. }
  103. for _, cc := range reqData.Cc {
  104. e.Cc = append(e.Cc, &parsemail.User{
  105. Name: cc.Name,
  106. EmailAddress: cc.Email,
  107. })
  108. }
  109. e.From = &parsemail.User{
  110. Name: reqData.From.Name,
  111. EmailAddress: reqData.From.Email,
  112. }
  113. if reqData.Sender.Email != "" {
  114. e.Sender = &parsemail.User{
  115. Name: reqData.Sender.Name,
  116. EmailAddress: reqData.Sender.Email,
  117. }
  118. } else {
  119. e.Sender = &parsemail.User{
  120. Name: reqData.From.Name,
  121. EmailAddress: reqData.From.Email,
  122. }
  123. }
  124. e.Text = []byte(reqData.Text)
  125. e.HTML = []byte(reqData.HTML)
  126. e.Subject = reqData.Subject
  127. for _, att := range reqData.Attachments {
  128. att.Data = strings.TrimPrefix(att.Data, "data:")
  129. infos := strings.Split(att.Data, ";")
  130. contentType := infos[0]
  131. content := strings.TrimPrefix(infos[1], "base64,")
  132. decoded, err := base64.StdEncoding.DecodeString(content)
  133. if err != nil {
  134. log.WithContext(ctx).Errorf("附件解码错误!%v", err)
  135. response.NewErrorResponse(response.ParamsError, i18n.GetText(ctx.Lang, "att_err"), err.Error()).FPrint(w)
  136. return
  137. }
  138. e.Attachments = append(e.Attachments, &parsemail.Attachment{
  139. Filename: att.Name,
  140. ContentType: contentType,
  141. Content: decoded,
  142. })
  143. }
  144. log.WithContext(ctx).Debugf("插件执行--SendBefore")
  145. for _, hook := range hooks.HookList {
  146. if hook == nil {
  147. continue
  148. }
  149. hook.SendBefore(ctx, e)
  150. }
  151. log.WithContext(ctx).Debugf("插件执行--SendBefore End")
  152. modelEmail := models.Email{
  153. Type: 1,
  154. Subject: e.Subject,
  155. ReplyTo: json2string(e.ReplyTo),
  156. FromName: e.From.Name,
  157. FromAddress: e.From.EmailAddress,
  158. To: json2string(e.To),
  159. Bcc: json2string(e.Bcc),
  160. Cc: json2string(e.Cc),
  161. Text: sql.NullString{String: string(e.Text), Valid: true},
  162. Html: sql.NullString{String: string(e.HTML), Valid: true},
  163. Sender: json2string(e.Sender),
  164. Attachments: json2string(e.Attachments),
  165. SPFCheck: 1,
  166. DKIMCheck: 1,
  167. SendUserID: ctx.UserID,
  168. SendDate: time.Now(),
  169. CronSendTime: time.Now(),
  170. Status: 1,
  171. CreateTime: time.Now(),
  172. }
  173. _, err = db.Instance.Insert(&modelEmail)
  174. if err != nil || modelEmail.Id <= 0 {
  175. log.Println("insert error:", err.Error())
  176. response.NewErrorResponse(response.ServerError, i18n.GetText(ctx.Lang, "send_fail"), err.Error()).FPrint(w)
  177. return
  178. }
  179. e.MessageId = cast.ToInt64(modelEmail.Id)
  180. async.New(ctx).Process(func(p any) {
  181. errMsg := ""
  182. err, sendErr := send.Send(ctx, e)
  183. log.WithContext(ctx).Debugf("插件执行--SendAfter")
  184. as2 := async.New(ctx)
  185. for _, hook := range hooks.HookList {
  186. if hook == nil {
  187. continue
  188. }
  189. as2.WaitProcess(func(hk any) {
  190. hk.(framework.EmailHook).SendAfter(ctx, e, sendErr)
  191. }, hook)
  192. }
  193. as2.Wait()
  194. log.WithContext(ctx).Debugf("插件执行--SendAfter")
  195. if err != nil {
  196. errMsg = err.Error()
  197. _, err := db.Instance.Exec(db.WithContext(ctx, "update email set status =2 ,error=? where id = ? "), errMsg, modelEmail.Id)
  198. if err != nil {
  199. log.WithContext(ctx).Errorf("sql Error :%+v", err)
  200. }
  201. ue := models.UserEmail{
  202. UserID: ctx.UserID,
  203. EmailID: modelEmail.Id,
  204. Status: 2,
  205. IsRead: 1,
  206. }
  207. db.Instance.Insert(&ue)
  208. } else {
  209. _, err := db.Instance.Exec(db.WithContext(ctx, "update email set status =1 where id = ? "), modelEmail.Id)
  210. if err != nil {
  211. log.WithContext(ctx).Errorf("sql Error :%+v", err)
  212. }
  213. ue := models.UserEmail{
  214. UserID: ctx.UserID,
  215. EmailID: modelEmail.Id,
  216. Status: 1,
  217. IsRead: 1,
  218. }
  219. db.Instance.Insert(&ue)
  220. }
  221. }, nil)
  222. response.NewSuccessResponse(i18n.GetText(ctx.Lang, "succ")).FPrint(w)
  223. }
  224. func json2string(d any) string {
  225. by, _ := json.Marshal(d)
  226. return string(by)
  227. }