sso

sms_service.go 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. package service
  2. import (
  3. "bytes"
  4. "encoding/base64"
  5. "encoding/json"
  6. "fmt"
  7. "io/ioutil"
  8. "math"
  9. "math/rand"
  10. "net/http"
  11. "regexp"
  12. "strconv"
  13. "strings"
  14. "time"
  15. "SSO/models"
  16. "SSO/utils"
  17. "github.com/astaxie/beego"
  18. "github.com/jinzhu/gorm"
  19. )
  20. type SMSServiceError struct {
  21. Err string
  22. }
  23. func (e *SMSServiceError) Error() string {
  24. return e.Err
  25. }
  26. // 有如下两个外部可调用的接口:SendSMSUseTemplate、SendSMSWithCustomContent两个函数
  27. // 但是实际上我打算将 SendSMSUseTemplate 作为底层函数使用,不直接提供外界:
  28. // 这需要提供数个默认模板,通过为这几个模板创建独立便利函数,封装 SendSMSUseTemplate 的参数 defaultTemplateID 和 params
  29. // 发送验证码短信
  30. // 参数 aespass 是加密后的地址信息,用于限制频繁调用
  31. func SendVerificationCodeSMS(mobile string, aespass string) error {
  32. if len(mobile) == 0 {
  33. return &SMSServiceError{Err: "手机号为空"}
  34. }
  35. if err := checkVerificationCodeSMSLimit(aespass, mobile); err != nil {
  36. return err
  37. }
  38. var code_str string
  39. for i := 0; i < 6; i++ {
  40. rand.Seed(time.Now().UnixNano())
  41. code_str += strconv.Itoa(rand.Intn(10))
  42. }
  43. templateID, _ := beego.AppConfig.Int("sms_verification_code_templateid")
  44. utils.TraceLog("验证码为%v", code_str)
  45. _, _, err := batchSendMessage(templateID, []string{code_str}, []string{mobile})
  46. if err == nil {
  47. redisClient := RedisClient()
  48. defer redisClient.Close()
  49. cur_date := time.Now().Format("2006-01-02")
  50. redisClient.Set("code_msg_"+mobile, code_str, time.Minute*10)
  51. redisClient.Incr("code_msg_" + mobile + "_" + cur_date).Result()
  52. // 取出地址信息,因为上面已经验证过,这里就直接解密而不做错误判断了
  53. bytesPass, _ := base64.StdEncoding.DecodeString(aespass)
  54. tpass := utils.AESDecrypt(bytesPass)
  55. redisClient.Incr("ip:host_" + cur_date + "_" + tpass).Result()
  56. }
  57. return err
  58. }
  59. func checkVerificationCodeSMSLimit(aespass string, mobile string) error {
  60. redisClient := RedisClient()
  61. defer redisClient.Close()
  62. bytesPass, err := base64.StdEncoding.DecodeString(aespass)
  63. if err != nil {
  64. return &SMSServiceError{Err: "缺少关键参数"}
  65. }
  66. tpass := utils.AESDecrypt(bytesPass)
  67. if len(tpass) == 0 {
  68. return &SMSServiceError{Err: "缺少关键参数"}
  69. }
  70. cur_date := time.Now().Format("2006-01-02")
  71. add_redis, err := redisClient.Get("ip:host_" + cur_date + "_" + tpass).Result()
  72. if err != nil {
  73. return &SMSServiceError{Err: "缺少关键参数"}
  74. }
  75. ip_max_send_count, _ := beego.AppConfig.Int("ip_max_send_count")
  76. if add_count, _ := strconv.Atoi(add_redis); add_count >= ip_max_send_count {
  77. return &SMSServiceError{Err: "当前IP发送短信超过限制"}
  78. }
  79. moblie_count, _ := redisClient.Get("code_msg_" + mobile + "_" + cur_date).Result()
  80. moblie_count_int, _ := strconv.Atoi(moblie_count)
  81. if moblie_max, _ := beego.AppConfig.Int("moblie_max_send_count"); moblie_count_int >= moblie_max {
  82. return &SMSServiceError{Err: "当前手机号发送短信超过限制"}
  83. }
  84. return nil
  85. }
  86. // 指定模板群发短信
  87. func SendSMSUseTemplate(orgID int, orgShortName string, defaultTemplateID int, params []string, mobiles []string) error {
  88. if len(mobiles) == 0 {
  89. return nil
  90. }
  91. defaultTemplate, getDefaultTemplateErr := getDefaultTemplate(defaultTemplateID)
  92. if getDefaultTemplateErr != nil {
  93. utils.WarningLog("获取默认模板信息失败: %v", getDefaultTemplateErr)
  94. return getDefaultTemplateErr
  95. }
  96. smsContent, matchErr := getSMSFullContent(defaultTemplate, orgShortName, params)
  97. if matchErr != nil {
  98. return matchErr
  99. }
  100. if len(smsContent) > 500 {
  101. return &SMSServiceError{Err: "短信太长"}
  102. }
  103. // 若发送,是否会超过短信额度
  104. willConsumeLimit := getWillConsumeLimit(smsContent, len(mobiles))
  105. if isSMSCountOverLimit(orgID, willConsumeLimit) {
  106. utils.WarningLog("短信额度不足")
  107. return &SMSServiceError{"短信额度不足"}
  108. }
  109. sendTime := time.Now()
  110. batch := &models.SMSBatch{
  111. OrgId: orgID,
  112. Autograph: orgShortName,
  113. Params: strings.Join(params, ","),
  114. FullContent: smsContent,
  115. Mobiles: strings.Join(mobiles, ","),
  116. SendTime: sendTime.Unix(),
  117. Status: models.SMSBatchStatusUnsend,
  118. CreateTime: sendTime.Unix(),
  119. ModifyTime: sendTime.Unix(),
  120. }
  121. template, getTemplateErr := getSMSTemplateID(orgID, orgShortName, defaultTemplateID)
  122. if getTemplateErr != nil {
  123. return getTemplateErr
  124. }
  125. if template.TemplateId != 0 {
  126. if template.Status == 0 {
  127. return &SMSServiceError{Err: "短信模板无效"}
  128. }
  129. // 扣除短信额度
  130. if updateLimitErr := updateLimit(orgID, sendTime, willConsumeLimit); updateLimitErr != nil {
  131. return updateLimitErr
  132. }
  133. // 插入“未发送”状态的短信批次记录
  134. batch.TemplateId = template.TemplateId
  135. saveSMSBatch(batch, models.SMSBatchStatusUnsend)
  136. // 发送短信
  137. sendCount, report, sendErr := batchSendMessage(template.TemplateId, params, mobiles)
  138. // 根据发送结果,修改短信批次记录的状态,调整扣除额度
  139. if sendErr == nil { // 发送成功
  140. utils.InfoLog("发送了短信数量: %v", sendCount)
  141. // 修改短信批次记录的状态为“已发送”
  142. saveSMSBatch(batch, models.SMSBatchStatusDidSend)
  143. // 返回发送失败部分的额度
  144. if sendCount < willConsumeLimit {
  145. if updateLimitErr := updateLimit(orgID, sendTime, sendCount-willConsumeLimit); updateLimitErr != nil {
  146. // 记录更新失败的操作
  147. }
  148. }
  149. // 插入发送状态
  150. if batch.Id != 0 {
  151. saveSMSSendReport(batch.Id, report)
  152. } else {
  153. utils.ErrorLog("因为短信批次插入失败,导致无法插入发送状态,report: %v", report)
  154. }
  155. return nil
  156. } else { // 发送失败
  157. utils.ErrorLog("发送短信失败: %v", sendErr)
  158. // 修改短信批次记录的状态为“发送失败”
  159. saveSMSBatch(batch, models.SMSBatchStatusSendFailed)
  160. // 返回扣除的额度
  161. if updateLimitErr := updateLimit(orgID, sendTime, -willConsumeLimit); updateLimitErr != nil {
  162. // 记录更新失败的操作
  163. }
  164. return sendErr
  165. }
  166. } else {
  167. // 扣除短信额度
  168. if updateLimitErr := updateLimit(orgID, sendTime, willConsumeLimit); updateLimitErr != nil {
  169. return updateLimitErr
  170. }
  171. // 创建模板 成功:保存模板ID; 失败:需要返回扣除的额度
  172. if newTemplateID, createErr := createTemplateToSMSPlatform(orgShortName, defaultTemplate.Content, defaultTemplate.Type); createErr == nil { // 创建成功
  173. // 插入“待审核”状态的短信批次记录
  174. batch.TemplateId = newTemplateID
  175. saveSMSBatch(batch, models.SMSBatchStatusInReview)
  176. // 保存模板ID
  177. if saveErr := setSMSTemplateIDFor(orgID, orgShortName, defaultTemplateID, newTemplateID); saveErr != nil {
  178. utils.ErrorLog("保存模板ID失败: %v", saveErr)
  179. return saveErr
  180. } else {
  181. // 等待短信平台对模板的审核回调
  182. utils.TraceLog("等待短信平台对模板的审核回调")
  183. return nil
  184. }
  185. } else { // 创建失败
  186. utils.ErrorLog("短信平台创建模板失败: %v", createErr)
  187. // 需要返回扣除的额度
  188. if updateLimitErr := updateLimit(orgID, sendTime, -willConsumeLimit); updateLimitErr != nil {
  189. // 记录更新失败的操作
  190. }
  191. return createErr
  192. }
  193. }
  194. }
  195. // 发送自定义内容短信,content 不需要包括“退订请回复TD”字样
  196. func SendSMSWithCustomContent(orgID int, orgShortName string, content string, mobiles []string) error {
  197. if len(mobiles) == 0 {
  198. return nil
  199. }
  200. content = fmt.Sprintf("%v退订请回复TD", content)
  201. fullContent := fmt.Sprintf("【%v】%v", orgShortName, content)
  202. if len(fullContent) > 500 {
  203. return &SMSServiceError{Err: "短信太长"}
  204. }
  205. // 若发送,是否会超过短信额度
  206. willConsumeLimit := getWillConsumeLimit(fullContent, len(mobiles))
  207. if isSMSCountOverLimit(orgID, willConsumeLimit) {
  208. utils.WarningLog("短信额度不足")
  209. return &SMSServiceError{"短信额度不足"}
  210. }
  211. sendTime := time.Now()
  212. batch := &models.SMSBatch{
  213. OrgId: orgID,
  214. Autograph: orgShortName,
  215. Params: "",
  216. FullContent: fullContent,
  217. Mobiles: strings.Join(mobiles, ","),
  218. SendTime: sendTime.Unix(),
  219. Status: models.SMSBatchStatusUnsend,
  220. CreateTime: sendTime.Unix(),
  221. ModifyTime: sendTime.Unix(),
  222. }
  223. // 扣除短信额度
  224. if updateLimitErr := updateLimit(orgID, sendTime, willConsumeLimit); updateLimitErr != nil {
  225. return updateLimitErr
  226. }
  227. // 创建模板 成功:保存模板ID; 失败:需要返回扣除的额度
  228. if newTemplateID, createErr := createTemplateToSMSPlatform(orgShortName, content, 5); createErr == nil { // 创建成功
  229. // 插入“待审核”状态的短信批次记录
  230. batch.TemplateId = newTemplateID
  231. saveSMSBatch(batch, models.SMSBatchStatusInReview)
  232. // 保存模板ID
  233. if saveErr := setSMSTemplateIDFor(orgID, orgShortName, 0, newTemplateID); saveErr != nil {
  234. utils.ErrorLog("保存模板ID失败: %v", saveErr)
  235. return saveErr
  236. } else {
  237. // 等待短信平台对模板的审核回调
  238. utils.TraceLog("等待短信平台对模板的审核回调")
  239. return nil
  240. }
  241. } else { // 创建失败
  242. utils.ErrorLog("短信平台创建模板失败: %v", createErr)
  243. // 需要返回扣除的额度
  244. if updateLimitErr := updateLimit(orgID, sendTime, -willConsumeLimit); updateLimitErr != nil {
  245. // 记录更新失败的操作
  246. }
  247. return createErr
  248. }
  249. }
  250. // 短信批次重发
  251. func ResendSMSBatch(templateID int) error {
  252. batchs, getBatchErr := getInReviewSMSBatchWithTemplateID(templateID)
  253. if getBatchErr != nil {
  254. if getBatchErr != gorm.ErrRecordNotFound {
  255. utils.ErrorLog("获取短信批次失败: %v", getBatchErr)
  256. return getBatchErr
  257. } else {
  258. return nil
  259. }
  260. }
  261. for _, batch := range batchs {
  262. params := strings.Split(batch.Params, ",")
  263. mobiles := strings.Split(batch.Mobiles, ",")
  264. // 若发送,是否会超过短信额度
  265. willConsumeLimit := getWillConsumeLimit(batch.FullContent, len(mobiles))
  266. // 更新批次状态为"未发送"
  267. saveSMSBatch(batch, models.SMSBatchStatusUnsend)
  268. // 发送短信
  269. sendCount, report, sendErr := batchSendMessage(templateID, params, mobiles)
  270. // 根据发送结果,修改短信批次记录的状态,调整扣除额度
  271. if sendErr == nil { // 发送成功
  272. utils.InfoLog("发送了短信数量: %v", sendCount)
  273. // 修改短信批次记录的状态为“已发送”
  274. saveSMSBatch(batch, models.SMSBatchStatusDidSend)
  275. // 返回发送失败部分的额度
  276. if sendCount < willConsumeLimit {
  277. if updateLimitErr := updateLimit(batch.OrgId, time.Unix(batch.SendTime, 0), sendCount-willConsumeLimit); updateLimitErr != nil {
  278. // 记录更新失败的操作
  279. }
  280. }
  281. // 插入发送状态
  282. saveSMSSendReport(batch.Id, report)
  283. return nil
  284. } else { // 发送失败
  285. utils.ErrorLog("发送短信失败: %v", sendErr)
  286. // 修改短信批次记录的状态为“发送失败”
  287. saveSMSBatch(batch, models.SMSBatchStatusSendFailed)
  288. // 返回扣除的额度
  289. if updateLimitErr := updateLimit(batch.OrgId, time.Unix(batch.SendTime, 0), -willConsumeLimit); updateLimitErr != nil {
  290. // 记录更新失败的操作
  291. }
  292. }
  293. }
  294. return nil
  295. }
  296. func GetAllDefaultTemplates() []*models.SMSDefaultTemplate {
  297. var templates []*models.SMSDefaultTemplate
  298. err := readDb.Find(&templates).Error
  299. if err != nil {
  300. return make([]*models.SMSDefaultTemplate, 0)
  301. }
  302. return templates
  303. }
  304. // 更新"sgj_patient_sms_template_id"模板状态为无效
  305. func DisableTemplate(templateID int) error {
  306. tx := writeDb.Begin()
  307. if err := tx.Exec("UPDATE sgj_patient_sms_template_id SET status = ?, mtime = ? Where sms_template_id = ?;", 0, time.Now().Unix(), templateID).Error; err != nil {
  308. utils.ErrorLog("无效化模板失败: templateID: %v", templateID)
  309. tx.Rollback()
  310. // 记录更新失败的操作
  311. }
  312. return tx.Commit().Error
  313. }
  314. // 更新批次状态为”审核未通过“
  315. func SetSMSBatchUnapprovedWithTemplateID(templateID int) error {
  316. tx := writeDb.Begin()
  317. if err := tx.Exec("UPDATE sgj_patient_sms_batch SET status = ?, mtime = ? Where sms_template_id = ?;", models.SMSBatchStatusUnapproved, time.Now().Unix(), templateID).Error; err != nil {
  318. utils.ErrorLog("更新短信批次状态为”审核未通过“失败: templateID: %v", templateID)
  319. tx.Rollback()
  320. // 记录更新失败的操作
  321. }
  322. return tx.Commit().Error
  323. }
  324. // private methods
  325. // 获取数据库中 orgID & orgShortName & defaultTemplateID 对应的短信平台上的模板 ID,如果没有,会返回 0
  326. func getSMSTemplateID(orgID int, orgShortName string, defaultTemplateID int) (*models.SMSTemplateID, error) {
  327. var template models.SMSTemplateID
  328. err := readDb.Where("org_id = ? AND org_short_name = ? AND custom_template_id = ?", orgID, orgShortName, defaultTemplateID).First(&template).Error
  329. if err != nil {
  330. utils.WarningLog("getSMSTemplateID err: %v", err)
  331. return nil, err
  332. } else {
  333. return &template, nil
  334. }
  335. }
  336. // 将获取到的短信平台上的模板 ID插入数据库
  337. func setSMSTemplateIDFor(orgID int, orgShortName string, defaultTemplateID int, templateID int) error {
  338. tx := writeDb.Begin()
  339. now := time.Now().Unix()
  340. template := &models.SMSTemplateID{
  341. OrgId: orgID,
  342. OrgShortName: orgShortName,
  343. CustomTemplateId: defaultTemplateID,
  344. TemplateId: templateID,
  345. Status: 1,
  346. CreateTime: now,
  347. ModifyTime: now,
  348. }
  349. if err := tx.Create(template).Error; err != nil {
  350. tx.Rollback()
  351. }
  352. return tx.Commit().Error
  353. }
  354. // 获取默认模板
  355. func getDefaultTemplate(defaultTemplateID int) (*models.SMSDefaultTemplate, error) {
  356. var template models.SMSDefaultTemplate
  357. err := readDb.Where("id = ?", defaultTemplateID).First(&template).Error
  358. if err != nil {
  359. return nil, err
  360. } else {
  361. return &template, nil
  362. }
  363. }
  364. func getDefaultTemplateIDWithSMSTemplateID(smsTemplateID int) (int, error) {
  365. var template models.SMSTemplateID
  366. err := readDb.Where("sms_template_id = ?", smsTemplateID).First(template).Error
  367. if err != nil {
  368. return 0, err
  369. } else {
  370. return template.CustomTemplateId, nil
  371. }
  372. }
  373. // 判断短信参数是否含有非法参数
  374. func isSMSParamValid(params []string) bool {
  375. if params == nil {
  376. return true
  377. }
  378. for _, param := range params {
  379. if strings.Contains(param, ",") || strings.Contains(param, "】") || strings.Contains(param, "【") {
  380. return false
  381. }
  382. }
  383. return true
  384. }
  385. // 计算即将消耗的额度
  386. func getWillConsumeLimit(smsContent string, mobileCount int) int {
  387. limitEachMessage := math.Ceil(float64(len(smsContent)) / 67.0)
  388. return int(limitEachMessage) * mobileCount
  389. }
  390. // 拼接短信模板和参数为完整短信内容
  391. func getSMSFullContent(template *models.SMSDefaultTemplate, autograph string, params []string) (string, error) {
  392. reg, _ := regexp.Compile("{\\d}")
  393. placeholders := reg.FindAllString(template.Content, -1)
  394. if len(placeholders) != len(params) {
  395. utils.WarningLog("短信模板和参数不匹配")
  396. return template.Content, &SMSServiceError{"短信模板和参数不匹配"}
  397. }
  398. tplContent := template.Content
  399. for index, placeholder := range placeholders {
  400. tplContent = strings.Replace(tplContent, placeholder, params[index], 1)
  401. }
  402. smsContent := fmt.Sprintf("【%v】%v", autograph, tplContent)
  403. return smsContent, nil
  404. }
  405. // 向短信平台创建模板,返回新模板的 ID
  406. // autograph: 签名,即为机构简称
  407. // content: 模板内容
  408. // type_: 短信类型:0:通知短信、5:会员服务短信、4:验证码短信
  409. func createTemplateToSMSPlatform(autograph string, content string, type_ int) (int, error) {
  410. sms_api := beego.AppConfig.String("sms_baseUrl") + "addsmstemplate"
  411. appID, sid, token := getSMSConfig()
  412. params := make(map[string]interface{})
  413. params["appid"] = appID
  414. params["sid"] = sid
  415. params["token"] = token
  416. params["type"] = type_
  417. params["autograph"] = autograph
  418. params["content"] = content
  419. paramsBytes, _ := json.Marshal(params)
  420. resp, requestErr := http.Post(sms_api, "application/json", bytes.NewBuffer(paramsBytes))
  421. if requestErr != nil {
  422. utils.ErrorLog("短信平台增加模板接口调用失败: %v", requestErr)
  423. return 0, requestErr
  424. }
  425. defer resp.Body.Close()
  426. body, ioErr := ioutil.ReadAll(resp.Body)
  427. if ioErr != nil {
  428. utils.ErrorLog("短信平台增加模板接口返回数据读取失败: %v", ioErr)
  429. return 0, ioErr
  430. }
  431. var respJSON map[string]interface{}
  432. utils.InfoLog(string(body))
  433. if err := json.Unmarshal([]byte(string(body)), &respJSON); err != nil {
  434. utils.ErrorLog("短信平台增加模板接口返回数据解析JSON失败: %v", err)
  435. return 0, err
  436. }
  437. if respJSON["code"].(string) != "000000" {
  438. msg := respJSON["msg"].(string)
  439. utils.ErrorLog("短信平台增加模板接口请求失败: %v", msg)
  440. return 0, &SMSServiceError{"短信平台增加模板接口请求失败"}
  441. }
  442. templateID, _ := strconv.Atoi(respJSON["templateid"].(string))
  443. utils.SuccessLog("新短信模板 ID: %v", templateID)
  444. return templateID, nil
  445. }
  446. // 指定模板群发短信
  447. // 返回值为发送了 n 条短信、短信平台返回的 report 数组[{"code":"0", "msg":"OK", "smsid":"f96f79240e372587e9284cd580d8f953", "mobile":"18011984299", "count":"1"}]
  448. func batchSendMessage(templateID int, params []string, mobiles []string) (int, []interface{}, error) {
  449. sms_api := beego.AppConfig.String("sms_baseUrl") + "sendsms"
  450. mobileStr := strings.Join(mobiles, ",")
  451. appID, sid, token := getSMSConfig()
  452. requestParams := make(map[string]interface{})
  453. requestParams["appid"] = appID
  454. requestParams["sid"] = sid
  455. requestParams["token"] = token
  456. requestParams["templateid"] = strconv.Itoa(templateID)
  457. requestParams["mobile"] = mobileStr
  458. if params != nil && len(params) != 0 {
  459. paramStr := strings.Join(params, ",")
  460. requestParams["param"] = paramStr
  461. }
  462. paramsBytes, _ := json.Marshal(requestParams)
  463. resp, requestErr := http.Post(sms_api, "application/json", bytes.NewBuffer(paramsBytes))
  464. if requestErr != nil {
  465. utils.ErrorLog("短信平台模板群发接口调用失败: %v", requestErr)
  466. return 0, nil, requestErr
  467. }
  468. defer resp.Body.Close()
  469. body, ioErr := ioutil.ReadAll(resp.Body)
  470. if ioErr != nil {
  471. utils.ErrorLog("短信平台模板群发接口返回数据读取失败: %v", ioErr)
  472. return 0, nil, ioErr
  473. }
  474. var respJSON map[string]interface{}
  475. utils.InfoLog(string(body))
  476. if err := json.Unmarshal([]byte(string(body)), &respJSON); err != nil {
  477. utils.ErrorLog("短信平台模板群发接口返回数据解析JSON失败: %v", err)
  478. return 0, nil, err
  479. }
  480. if respJSON["code"].(string) != "000000" {
  481. msg := respJSON["msg"].(string)
  482. utils.ErrorLog("短信平台模板群发接口请求失败: %v", msg)
  483. return 0, nil, &SMSServiceError{"短信平台模板群发接口请求失败"}
  484. } else {
  485. utils.SuccessLog("短信发送成功 report: %v", respJSON["report"])
  486. if len(mobiles) > 1 {
  487. count, _ := strconv.Atoi(respJSON["count_sum"].(string))
  488. return count, respJSON["report"].([]interface{}), nil
  489. } else {
  490. return 1, nil, nil
  491. }
  492. }
  493. }
  494. // 判断短信额度是否充足
  495. func isSMSCountOverLimit(orgID int, willSendCount int) bool {
  496. freeLimit, getFreeErr := getUserSMSFreeLimit(orgID, time.Now())
  497. if getFreeErr != nil {
  498. return true
  499. }
  500. if willSendCount+freeLimit.UsedCount <= freeLimit.TotalCount {
  501. // 免费额度充足
  502. return false
  503. }
  504. // 免费额度不足时,需要加上已购额度来判断
  505. buyLimit, getBuyErr := getUserSMSBuyLimit(orgID)
  506. if getBuyErr != nil {
  507. return true
  508. }
  509. if buyLimit.UsedCount+freeLimit.UsedCount+willSendCount <= freeLimit.TotalCount+buyLimit.TotalCount {
  510. // 可用额度充足
  511. return false
  512. } else {
  513. return true
  514. }
  515. }
  516. // 获取短信平台信息
  517. func getSMSConfig() (string, string, string) {
  518. return beego.AppConfig.String("sms_appId"),
  519. beego.AppConfig.String("sms_sid"),
  520. beego.AppConfig.String("sms_token")
  521. }
  522. // 获取用户的免费短信额度
  523. func getUserSMSFreeLimit(orgID int, date time.Time) (*models.UserSMSFreeLimit, error) {
  524. month := date.Format("200601")
  525. var freeLimit models.UserSMSFreeLimit
  526. if readErr := readDb.Where("org_id = ? AND valid_month = ?", orgID, month).First(&freeLimit).Error; gorm.IsRecordNotFoundError(readErr) == true {
  527. // 创建
  528. now := time.Now().Unix()
  529. freeLimit = models.UserSMSFreeLimit{
  530. OrgId: orgID,
  531. TotalCount: 100,
  532. UsedCount: 0,
  533. ValidMonth: month,
  534. Status: 1,
  535. CreateTime: now,
  536. ModifyTime: now,
  537. }
  538. tx := writeDb.Begin()
  539. if createErr := tx.Create(&freeLimit).Error; createErr != nil {
  540. tx.Rollback()
  541. utils.ErrorLog("用户短信免费额度创建失败: %v", createErr)
  542. tx.Commit()
  543. return nil, createErr
  544. } else {
  545. tx.Commit()
  546. return &freeLimit, nil
  547. }
  548. } else if readErr != nil {
  549. utils.ErrorLog("获取用户短信免费额度信息失败: %v", readErr)
  550. return nil, readErr
  551. } else {
  552. return &freeLimit, nil
  553. }
  554. }
  555. // 获取用户的已购短信额度
  556. func getUserSMSBuyLimit(orgID int) (*models.UserSMSBuyLimit, error) {
  557. var buyLimit models.UserSMSBuyLimit
  558. if readErr := readDb.Where("org_id = ?", orgID).First(&buyLimit).Error; gorm.IsRecordNotFoundError(readErr) == true {
  559. // 创建
  560. now := time.Now().Unix()
  561. buyLimit = models.UserSMSBuyLimit{
  562. OrgId: orgID,
  563. TotalCount: 0,
  564. UsedCount: 0,
  565. Status: 1,
  566. CreateTime: now,
  567. ModifyTime: now,
  568. }
  569. tx := writeDb.Begin()
  570. if createErr := tx.Create(&buyLimit).Error; createErr != nil {
  571. tx.Rollback()
  572. utils.ErrorLog("用户短信已购额度创建失败: %v", createErr)
  573. tx.Commit()
  574. return nil, createErr
  575. } else {
  576. tx.Commit()
  577. return &buyLimit, nil
  578. }
  579. } else if readErr != nil {
  580. utils.ErrorLog("获取用户短信已购额度信息失败: %v", readErr)
  581. return nil, readErr
  582. } else {
  583. return &buyLimit, nil
  584. }
  585. }
  586. // 更新用户短信额度
  587. func updateLimit(orgID int, date time.Time, consumeLimit int) error {
  588. freeLimit, getFreeLimitErr := getUserSMSFreeLimit(orgID, date)
  589. if getFreeLimitErr != nil {
  590. return getFreeLimitErr
  591. }
  592. if freeLimit.UsedCount+consumeLimit <= freeLimit.TotalCount {
  593. freeLimit.UsedCount += consumeLimit
  594. freeLimit.ModifyTime = time.Now().Unix()
  595. tx := writeDb.Begin()
  596. if err := tx.Save(freeLimit).Error; err != nil {
  597. utils.ErrorLog("更新短信免费额度失败: %v\nOrgID: %v; 额度生效月份: %v; 总额度: %v; 已用额度: %v; 扣完后已用额度应当为: %v;", err, orgID, freeLimit.ValidMonth, freeLimit.TotalCount, freeLimit.UsedCount+consumeLimit, freeLimit.UsedCount)
  598. tx.Rollback()
  599. }
  600. return tx.Commit().Error
  601. } else {
  602. buyLimit, getBuyLimitErr := getUserSMSBuyLimit(orgID)
  603. if getBuyLimitErr != nil {
  604. return getBuyLimitErr
  605. }
  606. consumeFreeLimit := freeLimit.TotalCount - freeLimit.UsedCount
  607. consumeBuyLimit := consumeLimit - consumeFreeLimit
  608. now := time.Now().Unix()
  609. tx := writeDb.Begin()
  610. freeLimit.UsedCount = freeLimit.TotalCount
  611. freeLimit.ModifyTime = now
  612. updateFreeLimitErr := tx.Save(freeLimit)
  613. if updateFreeLimitErr != nil {
  614. utils.ErrorLog("更新短信免费额度失败: %v\nOrgID: %v; 额度生效月份: %v; 当前剩余免费额度: %v; 当前剩余已购额度: %v; 当前应当扣去额度: %v;", err, orgID, freeLimit.ValidMonth, freeLimit.UsedCount-consumeFreeLimit, buyLimit.TotalCount-buyLimit.UsedCount, consumeLimit)
  615. tx.Rollback()
  616. } else {
  617. buyLimit.UsedCount += consumeBuyLimit
  618. buyLimit.ModifyTime = now
  619. updateBuyLimitErr := tx.Save(buyLimit)
  620. if updateBuyLimitErr != nil {
  621. utils.ErrorLog("更新短信已购额度失败: %v\nOrgID: %v; 额度生效月份: %v; 当前剩余免费额度: %v; 当前剩余已购额度: %v; 当前应当扣去额度: %v;", err, orgID, freeLimit.ValidMonth, freeLimit.UsedCount-consumeFreeLimit, buyLimit.TotalCount-buyLimit.UsedCount, consumeLimit)
  622. tx.Rollback()
  623. }
  624. }
  625. return tx.Commit().Error
  626. }
  627. }
  628. // 创建/更新 短信批次
  629. func saveSMSBatch(batch *models.SMSBatch, status int8) error {
  630. batch.Status = status
  631. batch.ModifyTime = time.Now().Unix()
  632. if err := _saveSMSBatch(batch); err != nil {
  633. // 记录更新失败的操作
  634. return err
  635. } else {
  636. return nil
  637. }
  638. }
  639. func _saveSMSBatch(batch *models.SMSBatch) error {
  640. tx := writeDb.Begin()
  641. if err := tx.Save(batch).Error; err != nil {
  642. tx.Rollback()
  643. }
  644. return tx.Commit().Error
  645. }
  646. // 获取短信批次
  647. func getInReviewSMSBatchWithTemplateID(templateID int) ([]*models.SMSBatch, error) {
  648. var batchs []*models.SMSBatch
  649. err := readDb.Where("sms_template_id = ? AND status = ?", templateID, models.SMSBatchStatusInReview).Find(&batchs).Error
  650. if err != nil {
  651. return nil, err
  652. } else {
  653. return batchs, nil
  654. }
  655. }
  656. // 插入短信发送状态
  657. // report: [{"code":"000000","count":"1","mobile":"13632250447","msg":"OK","smsid":"8a978fe2eb3e69c3e5983b6e1cd33427"}]
  658. func saveSMSSendReport(batchID int, report []interface{}) error {
  659. if report == nil || len(report) == 0 {
  660. return nil
  661. }
  662. now := time.Now().Unix()
  663. valueStrs := make([]string, 0, len(report))
  664. values := make([]interface{}, 0, len(report))
  665. for _, item := range report {
  666. json := item.(map[string]interface{})
  667. valueStrs = append(valueStrs, "(?, ?, ?, ?, ?, ?, ?)")
  668. values = append(values, batchID)
  669. values = append(values, json["mobile"])
  670. if json["code"].(string) == "000000" {
  671. values = append(values, 1)
  672. } else {
  673. values = append(values, 0)
  674. }
  675. values = append(values, json["code"])
  676. values = append(values, json["msg"])
  677. values = append(values, now)
  678. values = append(values, now)
  679. }
  680. sql := fmt.Sprintf("INSERT INTO sgj_patient_sms_send_status (batch_id, mobile, status, code, msg, ctime, mtime) VALUES %v;", strings.Join(valueStrs, ", "))
  681. tx := writeDb.Begin()
  682. if err := tx.Exec(sql, values...).Error; err != nil {
  683. utils.ErrorLog("插入短信发送状态失败: %v", err)
  684. tx.Rollback()
  685. // 记录插入失败的操作
  686. }
  687. return tx.Commit().Error
  688. }