package service import ( "bytes" "encoding/base64" "encoding/json" "fmt" "io/ioutil" "math" "math/rand" "net/http" "regexp" "strconv" "strings" "time" "SSO/models" "SSO/utils" "github.com/astaxie/beego" "github.com/jinzhu/gorm" ) type SMSServiceError struct { Err string } func (e *SMSServiceError) Error() string { return e.Err } // 有如下两个外部可调用的接口:SendSMSUseTemplate、SendSMSWithCustomContent两个函数 // 但是实际上我打算将 SendSMSUseTemplate 作为底层函数使用,不直接提供外界: // 这需要提供数个默认模板,通过为这几个模板创建独立便利函数,封装 SendSMSUseTemplate 的参数 defaultTemplateID 和 params // 发送验证码短信 // 参数 aespass 是加密后的地址信息,用于限制频繁调用 func SendVerificationCodeSMS(mobile string, aespass string) error { if len(mobile) == 0 { return &SMSServiceError{Err: "手机号为空"} } if err := checkVerificationCodeSMSLimit(aespass, mobile); err != nil { return err } var code_str string for i := 0; i < 6; i++ { rand.Seed(time.Now().UnixNano()) code_str += strconv.Itoa(rand.Intn(10)) } templateID, _ := beego.AppConfig.Int("sms_verification_code_templateid") utils.TraceLog("验证码为%v", code_str) _, _, err := batchSendMessage(templateID, []string{code_str}, []string{mobile}) if err == nil { redisClient := RedisClient() defer redisClient.Close() cur_date := time.Now().Format("2006-01-02") redisClient.Set("code_msg_"+mobile, code_str, time.Minute*10) redisClient.Incr("code_msg_" + mobile + "_" + cur_date).Result() // 取出地址信息,因为上面已经验证过,这里就直接解密而不做错误判断了 bytesPass, _ := base64.StdEncoding.DecodeString(aespass) tpass := utils.AESDecrypt(bytesPass) redisClient.Incr("ip:host_" + cur_date + "_" + tpass).Result() } return err } func checkVerificationCodeSMSLimit(aespass string, mobile string) error { redisClient := RedisClient() defer redisClient.Close() bytesPass, err := base64.StdEncoding.DecodeString(aespass) if err != nil { return &SMSServiceError{Err: "缺少关键参数"} } tpass := utils.AESDecrypt(bytesPass) if len(tpass) == 0 { return &SMSServiceError{Err: "缺少关键参数"} } cur_date := time.Now().Format("2006-01-02") add_redis, err := redisClient.Get("ip:host_" + cur_date + "_" + tpass).Result() if err != nil { return &SMSServiceError{Err: "缺少关键参数"} } ip_max_send_count, _ := beego.AppConfig.Int("ip_max_send_count") if add_count, _ := strconv.Atoi(add_redis); add_count >= ip_max_send_count { return &SMSServiceError{Err: "当前IP发送短信超过限制"} } moblie_count, _ := redisClient.Get("code_msg_" + mobile + "_" + cur_date).Result() moblie_count_int, _ := strconv.Atoi(moblie_count) if moblie_max, _ := beego.AppConfig.Int("moblie_max_send_count"); moblie_count_int >= moblie_max { return &SMSServiceError{Err: "当前手机号发送短信超过限制"} } return nil } // 指定模板群发短信 func SendSMSUseTemplate(orgID int, orgShortName string, defaultTemplateID int, params []string, mobiles []string) error { if len(mobiles) == 0 { return nil } defaultTemplate, getDefaultTemplateErr := getDefaultTemplate(defaultTemplateID) if getDefaultTemplateErr != nil { utils.WarningLog("获取默认模板信息失败: %v", getDefaultTemplateErr) return getDefaultTemplateErr } smsContent, matchErr := getSMSFullContent(defaultTemplate, orgShortName, params) if matchErr != nil { return matchErr } if len(smsContent) > 500 { return &SMSServiceError{Err: "短信太长"} } // 若发送,是否会超过短信额度 willConsumeLimit := getWillConsumeLimit(smsContent, len(mobiles)) if isSMSCountOverLimit(orgID, willConsumeLimit) { utils.WarningLog("短信额度不足") return &SMSServiceError{"短信额度不足"} } sendTime := time.Now() batch := &models.SMSBatch{ OrgId: orgID, Autograph: orgShortName, Params: strings.Join(params, ","), FullContent: smsContent, Mobiles: strings.Join(mobiles, ","), SendTime: sendTime.Unix(), Status: models.SMSBatchStatusUnsend, CreateTime: sendTime.Unix(), ModifyTime: sendTime.Unix(), } template, getTemplateErr := getSMSTemplateID(orgID, orgShortName, defaultTemplateID) if getTemplateErr != nil { return getTemplateErr } if template.TemplateId != 0 { if template.Status == 0 { return &SMSServiceError{Err: "短信模板无效"} } // 扣除短信额度 if updateLimitErr := updateLimit(orgID, sendTime, willConsumeLimit); updateLimitErr != nil { return updateLimitErr } // 插入“未发送”状态的短信批次记录 batch.TemplateId = template.TemplateId saveSMSBatch(batch, models.SMSBatchStatusUnsend) // 发送短信 sendCount, report, sendErr := batchSendMessage(template.TemplateId, params, mobiles) // 根据发送结果,修改短信批次记录的状态,调整扣除额度 if sendErr == nil { // 发送成功 utils.InfoLog("发送了短信数量: %v", sendCount) // 修改短信批次记录的状态为“已发送” saveSMSBatch(batch, models.SMSBatchStatusDidSend) // 返回发送失败部分的额度 if sendCount < willConsumeLimit { if updateLimitErr := updateLimit(orgID, sendTime, sendCount-willConsumeLimit); updateLimitErr != nil { // 记录更新失败的操作 } } // 插入发送状态 if batch.Id != 0 { saveSMSSendReport(batch.Id, report) } else { utils.ErrorLog("因为短信批次插入失败,导致无法插入发送状态,report: %v", report) } return nil } else { // 发送失败 utils.ErrorLog("发送短信失败: %v", sendErr) // 修改短信批次记录的状态为“发送失败” saveSMSBatch(batch, models.SMSBatchStatusSendFailed) // 返回扣除的额度 if updateLimitErr := updateLimit(orgID, sendTime, -willConsumeLimit); updateLimitErr != nil { // 记录更新失败的操作 } return sendErr } } else { // 扣除短信额度 if updateLimitErr := updateLimit(orgID, sendTime, willConsumeLimit); updateLimitErr != nil { return updateLimitErr } // 创建模板 成功:保存模板ID; 失败:需要返回扣除的额度 if newTemplateID, createErr := createTemplateToSMSPlatform(orgShortName, defaultTemplate.Content, defaultTemplate.Type); createErr == nil { // 创建成功 // 插入“待审核”状态的短信批次记录 batch.TemplateId = newTemplateID saveSMSBatch(batch, models.SMSBatchStatusInReview) // 保存模板ID if saveErr := setSMSTemplateIDFor(orgID, orgShortName, defaultTemplateID, newTemplateID); saveErr != nil { utils.ErrorLog("保存模板ID失败: %v", saveErr) return saveErr } else { // 等待短信平台对模板的审核回调 utils.TraceLog("等待短信平台对模板的审核回调") return nil } } else { // 创建失败 utils.ErrorLog("短信平台创建模板失败: %v", createErr) // 需要返回扣除的额度 if updateLimitErr := updateLimit(orgID, sendTime, -willConsumeLimit); updateLimitErr != nil { // 记录更新失败的操作 } return createErr } } } // 发送自定义内容短信,content 不需要包括“退订请回复TD”字样 func SendSMSWithCustomContent(orgID int, orgShortName string, content string, mobiles []string) error { if len(mobiles) == 0 { return nil } content = fmt.Sprintf("%v退订请回复TD", content) fullContent := fmt.Sprintf("【%v】%v", orgShortName, content) if len(fullContent) > 500 { return &SMSServiceError{Err: "短信太长"} } // 若发送,是否会超过短信额度 willConsumeLimit := getWillConsumeLimit(fullContent, len(mobiles)) if isSMSCountOverLimit(orgID, willConsumeLimit) { utils.WarningLog("短信额度不足") return &SMSServiceError{"短信额度不足"} } sendTime := time.Now() batch := &models.SMSBatch{ OrgId: orgID, Autograph: orgShortName, Params: "", FullContent: fullContent, Mobiles: strings.Join(mobiles, ","), SendTime: sendTime.Unix(), Status: models.SMSBatchStatusUnsend, CreateTime: sendTime.Unix(), ModifyTime: sendTime.Unix(), } // 扣除短信额度 if updateLimitErr := updateLimit(orgID, sendTime, willConsumeLimit); updateLimitErr != nil { return updateLimitErr } // 创建模板 成功:保存模板ID; 失败:需要返回扣除的额度 if newTemplateID, createErr := createTemplateToSMSPlatform(orgShortName, content, 5); createErr == nil { // 创建成功 // 插入“待审核”状态的短信批次记录 batch.TemplateId = newTemplateID saveSMSBatch(batch, models.SMSBatchStatusInReview) // 保存模板ID if saveErr := setSMSTemplateIDFor(orgID, orgShortName, 0, newTemplateID); saveErr != nil { utils.ErrorLog("保存模板ID失败: %v", saveErr) return saveErr } else { // 等待短信平台对模板的审核回调 utils.TraceLog("等待短信平台对模板的审核回调") return nil } } else { // 创建失败 utils.ErrorLog("短信平台创建模板失败: %v", createErr) // 需要返回扣除的额度 if updateLimitErr := updateLimit(orgID, sendTime, -willConsumeLimit); updateLimitErr != nil { // 记录更新失败的操作 } return createErr } } // 短信批次重发 func ResendSMSBatch(templateID int) error { batchs, getBatchErr := getInReviewSMSBatchWithTemplateID(templateID) if getBatchErr != nil { if getBatchErr != gorm.ErrRecordNotFound { utils.ErrorLog("获取短信批次失败: %v", getBatchErr) return getBatchErr } else { return nil } } for _, batch := range batchs { params := strings.Split(batch.Params, ",") mobiles := strings.Split(batch.Mobiles, ",") // 若发送,是否会超过短信额度 willConsumeLimit := getWillConsumeLimit(batch.FullContent, len(mobiles)) // 更新批次状态为"未发送" saveSMSBatch(batch, models.SMSBatchStatusUnsend) // 发送短信 sendCount, report, sendErr := batchSendMessage(templateID, params, mobiles) // 根据发送结果,修改短信批次记录的状态,调整扣除额度 if sendErr == nil { // 发送成功 utils.InfoLog("发送了短信数量: %v", sendCount) // 修改短信批次记录的状态为“已发送” saveSMSBatch(batch, models.SMSBatchStatusDidSend) // 返回发送失败部分的额度 if sendCount < willConsumeLimit { if updateLimitErr := updateLimit(batch.OrgId, time.Unix(batch.SendTime, 0), sendCount-willConsumeLimit); updateLimitErr != nil { // 记录更新失败的操作 } } // 插入发送状态 saveSMSSendReport(batch.Id, report) return nil } else { // 发送失败 utils.ErrorLog("发送短信失败: %v", sendErr) // 修改短信批次记录的状态为“发送失败” saveSMSBatch(batch, models.SMSBatchStatusSendFailed) // 返回扣除的额度 if updateLimitErr := updateLimit(batch.OrgId, time.Unix(batch.SendTime, 0), -willConsumeLimit); updateLimitErr != nil { // 记录更新失败的操作 } } } return nil } func GetAllDefaultTemplates() []*models.SMSDefaultTemplate { var templates []*models.SMSDefaultTemplate err := readDb.Find(&templates).Error if err != nil { return make([]*models.SMSDefaultTemplate, 0) } return templates } // 更新"sgj_patient_sms_template_id"模板状态为无效 func DisableTemplate(templateID int) error { tx := writeDb.Begin() 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 { utils.ErrorLog("无效化模板失败: templateID: %v", templateID) tx.Rollback() // 记录更新失败的操作 } return tx.Commit().Error } // 更新批次状态为”审核未通过“ func SetSMSBatchUnapprovedWithTemplateID(templateID int) error { tx := writeDb.Begin() 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 { utils.ErrorLog("更新短信批次状态为”审核未通过“失败: templateID: %v", templateID) tx.Rollback() // 记录更新失败的操作 } return tx.Commit().Error } // private methods // 获取数据库中 orgID & orgShortName & defaultTemplateID 对应的短信平台上的模板 ID,如果没有,会返回 0 func getSMSTemplateID(orgID int, orgShortName string, defaultTemplateID int) (*models.SMSTemplateID, error) { var template models.SMSTemplateID err := readDb.Where("org_id = ? AND org_short_name = ? AND custom_template_id = ?", orgID, orgShortName, defaultTemplateID).First(&template).Error if err != nil { utils.WarningLog("getSMSTemplateID err: %v", err) return nil, err } else { return &template, nil } } // 将获取到的短信平台上的模板 ID插入数据库 func setSMSTemplateIDFor(orgID int, orgShortName string, defaultTemplateID int, templateID int) error { tx := writeDb.Begin() now := time.Now().Unix() template := &models.SMSTemplateID{ OrgId: orgID, OrgShortName: orgShortName, CustomTemplateId: defaultTemplateID, TemplateId: templateID, Status: 1, CreateTime: now, ModifyTime: now, } if err := tx.Create(template).Error; err != nil { tx.Rollback() } return tx.Commit().Error } // 获取默认模板 func getDefaultTemplate(defaultTemplateID int) (*models.SMSDefaultTemplate, error) { var template models.SMSDefaultTemplate err := readDb.Where("id = ?", defaultTemplateID).First(&template).Error if err != nil { return nil, err } else { return &template, nil } } func getDefaultTemplateIDWithSMSTemplateID(smsTemplateID int) (int, error) { var template models.SMSTemplateID err := readDb.Where("sms_template_id = ?", smsTemplateID).First(template).Error if err != nil { return 0, err } else { return template.CustomTemplateId, nil } } // 判断短信参数是否含有非法参数 func isSMSParamValid(params []string) bool { if params == nil { return true } for _, param := range params { if strings.Contains(param, ",") || strings.Contains(param, "】") || strings.Contains(param, "【") { return false } } return true } // 计算即将消耗的额度 func getWillConsumeLimit(smsContent string, mobileCount int) int { limitEachMessage := math.Ceil(float64(len(smsContent)) / 67.0) return int(limitEachMessage) * mobileCount } // 拼接短信模板和参数为完整短信内容 func getSMSFullContent(template *models.SMSDefaultTemplate, autograph string, params []string) (string, error) { reg, _ := regexp.Compile("{\\d}") placeholders := reg.FindAllString(template.Content, -1) if len(placeholders) != len(params) { utils.WarningLog("短信模板和参数不匹配") return template.Content, &SMSServiceError{"短信模板和参数不匹配"} } tplContent := template.Content for index, placeholder := range placeholders { tplContent = strings.Replace(tplContent, placeholder, params[index], 1) } smsContent := fmt.Sprintf("【%v】%v", autograph, tplContent) return smsContent, nil } // 向短信平台创建模板,返回新模板的 ID // autograph: 签名,即为机构简称 // content: 模板内容 // type_: 短信类型:0:通知短信、5:会员服务短信、4:验证码短信 func createTemplateToSMSPlatform(autograph string, content string, type_ int) (int, error) { sms_api := beego.AppConfig.String("sms_baseUrl") + "addsmstemplate" appID, sid, token := getSMSConfig() params := make(map[string]interface{}) params["appid"] = appID params["sid"] = sid params["token"] = token params["type"] = type_ params["autograph"] = autograph params["content"] = content paramsBytes, _ := json.Marshal(params) resp, requestErr := http.Post(sms_api, "application/json", bytes.NewBuffer(paramsBytes)) if requestErr != nil { utils.ErrorLog("短信平台增加模板接口调用失败: %v", requestErr) return 0, requestErr } defer resp.Body.Close() body, ioErr := ioutil.ReadAll(resp.Body) if ioErr != nil { utils.ErrorLog("短信平台增加模板接口返回数据读取失败: %v", ioErr) return 0, ioErr } var respJSON map[string]interface{} utils.InfoLog(string(body)) if err := json.Unmarshal([]byte(string(body)), &respJSON); err != nil { utils.ErrorLog("短信平台增加模板接口返回数据解析JSON失败: %v", err) return 0, err } if respJSON["code"].(string) != "000000" { msg := respJSON["msg"].(string) utils.ErrorLog("短信平台增加模板接口请求失败: %v", msg) return 0, &SMSServiceError{"短信平台增加模板接口请求失败"} } templateID, _ := strconv.Atoi(respJSON["templateid"].(string)) utils.SuccessLog("新短信模板 ID: %v", templateID) return templateID, nil } // 指定模板群发短信 // 返回值为发送了 n 条短信、短信平台返回的 report 数组[{"code":"0", "msg":"OK", "smsid":"f96f79240e372587e9284cd580d8f953", "mobile":"18011984299", "count":"1"}] func batchSendMessage(templateID int, params []string, mobiles []string) (int, []interface{}, error) { sms_api := beego.AppConfig.String("sms_baseUrl") + "sendsms" mobileStr := strings.Join(mobiles, ",") appID, sid, token := getSMSConfig() requestParams := make(map[string]interface{}) requestParams["appid"] = appID requestParams["sid"] = sid requestParams["token"] = token requestParams["templateid"] = strconv.Itoa(templateID) requestParams["mobile"] = mobileStr if params != nil && len(params) != 0 { paramStr := strings.Join(params, ",") requestParams["param"] = paramStr } paramsBytes, _ := json.Marshal(requestParams) resp, requestErr := http.Post(sms_api, "application/json", bytes.NewBuffer(paramsBytes)) if requestErr != nil { utils.ErrorLog("短信平台模板群发接口调用失败: %v", requestErr) return 0, nil, requestErr } defer resp.Body.Close() body, ioErr := ioutil.ReadAll(resp.Body) if ioErr != nil { utils.ErrorLog("短信平台模板群发接口返回数据读取失败: %v", ioErr) return 0, nil, ioErr } var respJSON map[string]interface{} utils.InfoLog(string(body)) if err := json.Unmarshal([]byte(string(body)), &respJSON); err != nil { utils.ErrorLog("短信平台模板群发接口返回数据解析JSON失败: %v", err) return 0, nil, err } if respJSON["code"].(string) != "000000" { msg := respJSON["msg"].(string) utils.ErrorLog("短信平台模板群发接口请求失败: %v", msg) return 0, nil, &SMSServiceError{"短信平台模板群发接口请求失败"} } else { utils.SuccessLog("短信发送成功 report: %v", respJSON["report"]) if len(mobiles) > 1 { count, _ := strconv.Atoi(respJSON["count_sum"].(string)) return count, respJSON["report"].([]interface{}), nil } else { return 1, nil, nil } } } // 判断短信额度是否充足 func isSMSCountOverLimit(orgID int, willSendCount int) bool { freeLimit, getFreeErr := getUserSMSFreeLimit(orgID, time.Now()) if getFreeErr != nil { return true } if willSendCount+freeLimit.UsedCount <= freeLimit.TotalCount { // 免费额度充足 return false } // 免费额度不足时,需要加上已购额度来判断 buyLimit, getBuyErr := getUserSMSBuyLimit(orgID) if getBuyErr != nil { return true } if buyLimit.UsedCount+freeLimit.UsedCount+willSendCount <= freeLimit.TotalCount+buyLimit.TotalCount { // 可用额度充足 return false } else { return true } } // 获取短信平台信息 func getSMSConfig() (string, string, string) { return beego.AppConfig.String("sms_appId"), beego.AppConfig.String("sms_sid"), beego.AppConfig.String("sms_token") } // 获取用户的免费短信额度 func getUserSMSFreeLimit(orgID int, date time.Time) (*models.UserSMSFreeLimit, error) { month := date.Format("200601") var freeLimit models.UserSMSFreeLimit if readErr := readDb.Where("org_id = ? AND valid_month = ?", orgID, month).First(&freeLimit).Error; gorm.IsRecordNotFoundError(readErr) == true { // 创建 now := time.Now().Unix() freeLimit = models.UserSMSFreeLimit{ OrgId: orgID, TotalCount: 100, UsedCount: 0, ValidMonth: month, Status: 1, CreateTime: now, ModifyTime: now, } tx := writeDb.Begin() if createErr := tx.Create(&freeLimit).Error; createErr != nil { tx.Rollback() utils.ErrorLog("用户短信免费额度创建失败: %v", createErr) tx.Commit() return nil, createErr } else { tx.Commit() return &freeLimit, nil } } else if readErr != nil { utils.ErrorLog("获取用户短信免费额度信息失败: %v", readErr) return nil, readErr } else { return &freeLimit, nil } } // 获取用户的已购短信额度 func getUserSMSBuyLimit(orgID int) (*models.UserSMSBuyLimit, error) { var buyLimit models.UserSMSBuyLimit if readErr := readDb.Where("org_id = ?", orgID).First(&buyLimit).Error; gorm.IsRecordNotFoundError(readErr) == true { // 创建 now := time.Now().Unix() buyLimit = models.UserSMSBuyLimit{ OrgId: orgID, TotalCount: 0, UsedCount: 0, Status: 1, CreateTime: now, ModifyTime: now, } tx := writeDb.Begin() if createErr := tx.Create(&buyLimit).Error; createErr != nil { tx.Rollback() utils.ErrorLog("用户短信已购额度创建失败: %v", createErr) tx.Commit() return nil, createErr } else { tx.Commit() return &buyLimit, nil } } else if readErr != nil { utils.ErrorLog("获取用户短信已购额度信息失败: %v", readErr) return nil, readErr } else { return &buyLimit, nil } } // 更新用户短信额度 func updateLimit(orgID int, date time.Time, consumeLimit int) error { freeLimit, getFreeLimitErr := getUserSMSFreeLimit(orgID, date) if getFreeLimitErr != nil { return getFreeLimitErr } if freeLimit.UsedCount+consumeLimit <= freeLimit.TotalCount { freeLimit.UsedCount += consumeLimit freeLimit.ModifyTime = time.Now().Unix() tx := writeDb.Begin() if err := tx.Save(freeLimit).Error; err != nil { utils.ErrorLog("更新短信免费额度失败: %v\nOrgID: %v; 额度生效月份: %v; 总额度: %v; 已用额度: %v; 扣完后已用额度应当为: %v;", err, orgID, freeLimit.ValidMonth, freeLimit.TotalCount, freeLimit.UsedCount+consumeLimit, freeLimit.UsedCount) tx.Rollback() } return tx.Commit().Error } else { buyLimit, getBuyLimitErr := getUserSMSBuyLimit(orgID) if getBuyLimitErr != nil { return getBuyLimitErr } consumeFreeLimit := freeLimit.TotalCount - freeLimit.UsedCount consumeBuyLimit := consumeLimit - consumeFreeLimit now := time.Now().Unix() tx := writeDb.Begin() freeLimit.UsedCount = freeLimit.TotalCount freeLimit.ModifyTime = now updateFreeLimitErr := tx.Save(freeLimit) if updateFreeLimitErr != nil { utils.ErrorLog("更新短信免费额度失败: %v\nOrgID: %v; 额度生效月份: %v; 当前剩余免费额度: %v; 当前剩余已购额度: %v; 当前应当扣去额度: %v;", err, orgID, freeLimit.ValidMonth, freeLimit.UsedCount-consumeFreeLimit, buyLimit.TotalCount-buyLimit.UsedCount, consumeLimit) tx.Rollback() } else { buyLimit.UsedCount += consumeBuyLimit buyLimit.ModifyTime = now updateBuyLimitErr := tx.Save(buyLimit) if updateBuyLimitErr != nil { utils.ErrorLog("更新短信已购额度失败: %v\nOrgID: %v; 额度生效月份: %v; 当前剩余免费额度: %v; 当前剩余已购额度: %v; 当前应当扣去额度: %v;", err, orgID, freeLimit.ValidMonth, freeLimit.UsedCount-consumeFreeLimit, buyLimit.TotalCount-buyLimit.UsedCount, consumeLimit) tx.Rollback() } } return tx.Commit().Error } } // 创建/更新 短信批次 func saveSMSBatch(batch *models.SMSBatch, status int8) error { batch.Status = status batch.ModifyTime = time.Now().Unix() if err := _saveSMSBatch(batch); err != nil { // 记录更新失败的操作 return err } else { return nil } } func _saveSMSBatch(batch *models.SMSBatch) error { tx := writeDb.Begin() if err := tx.Save(batch).Error; err != nil { tx.Rollback() } return tx.Commit().Error } // 获取短信批次 func getInReviewSMSBatchWithTemplateID(templateID int) ([]*models.SMSBatch, error) { var batchs []*models.SMSBatch err := readDb.Where("sms_template_id = ? AND status = ?", templateID, models.SMSBatchStatusInReview).Find(&batchs).Error if err != nil { return nil, err } else { return batchs, nil } } // 插入短信发送状态 // report: [{"code":"000000","count":"1","mobile":"13632250447","msg":"OK","smsid":"8a978fe2eb3e69c3e5983b6e1cd33427"}] func saveSMSSendReport(batchID int, report []interface{}) error { if report == nil || len(report) == 0 { return nil } now := time.Now().Unix() valueStrs := make([]string, 0, len(report)) values := make([]interface{}, 0, len(report)) for _, item := range report { json := item.(map[string]interface{}) valueStrs = append(valueStrs, "(?, ?, ?, ?, ?, ?, ?)") values = append(values, batchID) values = append(values, json["mobile"]) if json["code"].(string) == "000000" { values = append(values, 1) } else { values = append(values, 0) } values = append(values, json["code"]) values = append(values, json["msg"]) values = append(values, now) values = append(values, now) } sql := fmt.Sprintf("INSERT INTO sgj_patient_sms_send_status (batch_id, mobile, status, code, msg, ctime, mtime) VALUES %v;", strings.Join(valueStrs, ", ")) tx := writeDb.Begin() if err := tx.Exec(sql, values...).Error; err != nil { utils.ErrorLog("插入短信发送状态失败: %v", err) tx.Rollback() // 记录插入失败的操作 } return tx.Commit().Error }