package sms_service import ( "SCRM/models" base "SCRM/service" "SCRM/utils" "errors" "fmt" "math" "strings" "time" "github.com/astaxie/beego" "github.com/jinzhu/gorm" ) // 发送验证码短信 func SMSSendVerificationCode(mobile string) (string, error) { if len(mobile) == 0 { return "", errors.New("手机号为空") } code_str := utils.RandomNumberString(6) templateID, _ := beego.AppConfig.Int("sms_verification_code_templateid") _, _, _, err := singleSendMessageUseUCPaas(templateID, []string{code_str}, mobile) return code_str, err } // 成为机构管理员的邀请短信 func SMSSendInviteMobileToJoinOrgAdmin(name string, mobile string, password string) error { if len(mobile) == 0 { return errors.New("手机号为空") } if len(password) == 0 { _, _, _, err := singleSendMessageUseUCPaas(332784, []string{name, mobile}, mobile) return err } else { _, _, _, err := singleSendMessageUseUCPaas(332783, []string{name, mobile, password}, mobile) return err } } var ( SMSErrorFreeLimitInsufficient = errors.New("短信额度不足") ) // 发送自定义内容短信,content 不需要包括签名及“退订请回复TD”字样 /* 流程说明: 检查额度 -> 创建短信平台模板 -> 创建数据库短信模板(待审核状态) -> 创建发送批次(待审核状态) -> 扣除额度 -> finish */ func SMSOrgSendWithCustomContent(orgID int64, originContent string, mobiles []string) error { if len(mobiles) == 0 { return nil } autograph := "酷医聚客" content := fmt.Sprintf("%v退订请回复TD", originContent) fullContent := fmt.Sprintf("【%v】%v", autograph, content) if len(fullContent) > 500 { return errors.New("短信太长") } willConsumeLimit := GetWillConsumeLimit(fullContent, len(mobiles)) freeLimit, getFreeLimitErr := GetUserSMSFreeLimit(orgID, time.Now()) if getFreeLimitErr != nil { return getFreeLimitErr } if willConsumeLimit+freeLimit.UsedCount > freeLimit.TotalCount { return SMSErrorFreeLimitInsufficient } smsTplID, createSMSTplErr := createTemplate2UCPaas(autograph, content, 5) if createSMSTplErr != nil { return createSMSTplErr } template := models.SMSTemplate{ TemplateType: 2, TemplateID: int64(smsTplID), MessageType: 2, Content: content, Autograph: autograph, OrgID: orgID, Status: 2, CreateTime: time.Now().Unix(), ModifyTime: time.Now().Unix(), } tx := base.PatientWriteDB().Begin() if createTplErr := tx.Save(&template).Error; createTplErr != nil { tx.Rollback() return createTplErr } sendTime := time.Now() batch := &models.SMSBatch{ OrgID: orgID, Autograph: autograph, FullContent: fullContent, Mobiles: strings.Join(mobiles, ","), TemplateID: int64(smsTplID), SendTime: sendTime.Unix(), Status: models.SMSBatchStatusInReview, CreateTime: time.Now().Unix(), ModifyTime: time.Now().Unix(), } if createBatchErr := tx.Save(batch).Error; createBatchErr != nil { tx.Rollback() return createBatchErr } freeLimit.UsedCount += willConsumeLimit freeLimit.ModifyTime = time.Now().Unix() updateLimitErr := tx.Save(freeLimit).Error if updateLimitErr != nil { tx.Rollback() return updateLimitErr } tx.Commit() return nil } // 计算即将消耗的额度 (smsFullContent 包含签名在内) func GetWillConsumeLimit(smsFullContent string, mobileCount int) int { limitEachMessage := math.Ceil(float64(len(smsFullContent)) / 67.0) return int(limitEachMessage) * mobileCount } // 短信平台模板审核成功 /* 流程说明 更新数据库模板状态(有效) -> 更新对应发送批次状态(待发送) -> finish */ func SMSUCPaasTemplateApproved(templateID int64) error { tx := base.PatientWriteDB().Begin() updateTplErr := tx.Model(&models.SMSTemplate{}).Where("template_id = ?", templateID).Updates(map[string]interface{}{ "status": 1, "mtime": time.Now().Unix(), }).Error if updateTplErr != nil { tx.Rollback() return updateTplErr } updateBatchErr := tx.Model(&models.SMSBatch{}).Where("sms_template_id = ?", templateID).Updates(map[string]interface{}{ "status": models.SMSBatchStatusUnsend, "mtime": time.Now().Unix(), }).Error if updateBatchErr != nil { tx.Rollback() return updateBatchErr } tx.Commit() return nil } // 短信平台模板审核失败 /* 流程说明 更新数据库模板状态(无效) -> 更新对应发送批次状态(审核未通过) -> 返回发送当月的额度 -> finish */ func SMSUCPaasTemplateUnapproved(templateID int64) error { var batch models.SMSBatch getBatchErr := base.PatientReadDB().Model(&models.SMSBatch{}).Where("sms_template_id = ?", templateID).First(&batch).Error if getBatchErr != nil { if getBatchErr != gorm.ErrRecordNotFound { return getBatchErr } } tx := base.PatientWriteDB().Begin() updateTplErr := tx.Model(&models.SMSTemplate{}).Where("template_id = ?", templateID).Updates(map[string]interface{}{ "status": 0, "mtime": time.Now().Unix(), }).Error if updateTplErr != nil { tx.Rollback() return updateTplErr } if batch.ID > 0 { batch.Status = models.SMSBatchStatusUnapproved batch.ModifyTime = time.Now().Unix() updateBatchErr := tx.Save(&batch).Error if updateBatchErr != nil { tx.Rollback() return updateBatchErr } freeLimit, getFreeLimitErr := GetUserSMSFreeLimit(batch.OrgID, time.Unix(batch.SendTime, 0)) if getFreeLimitErr != nil { tx.Rollback() return getFreeLimitErr } mobiles := strings.Split(batch.Mobiles, ",") consumedLimit := GetWillConsumeLimit(batch.FullContent, len(mobiles)) freeLimit.UsedCount -= consumedLimit freeLimit.ModifyTime = time.Now().Unix() updateFreeLimitErr := tx.Save(freeLimit).Error if updateFreeLimitErr != nil { tx.Rollback() return updateFreeLimitErr } } tx.Commit() return nil } // 发送“未发送”批次的短信 // 备注:因短信平台每次群发限制 100 个号码,所以同一个批次可能需要分成多次发送 func SMSSendUnsendBatchRealTime() { batchs, getBatchErr := GetUnsendSMSBatch() if getBatchErr != nil { return } for _, batch := range batchs { tx := base.PatientWriteDB().Begin() isSendSuccess := false failMobileCount := 0 reports := []interface{}{} mobiles := strings.Split(batch.Mobiles, ",") subbatchCount := int(math.Ceil(float64(len(mobiles)) / 100.0)) for index := 0; index < subbatchCount; index++ { var subMobiles []string if len(mobiles)-index*100 >= 100 { subMobiles = mobiles[index*100 : 100] } else { subMobiles = mobiles[index*100:] } sendCount, report, sendErr := batchSendMessageUseUCPaas(int(batch.TemplateID), nil, subMobiles) if sendErr != nil { utils.ErrorLog("%v", sendErr) failMobileCount += len(subMobiles) } else { isSendSuccess = true // 只要子批次有一次发送成功,就认为这个批次发送成功 reports = append(reports, report) failMobileCount += len(subMobiles) - sendCount } } if isSendSuccess { batch.Status = models.SMSBatchStatusDidSend batch.ModifyTime = time.Now().Unix() updateBatchErr := tx.Save(batch).Error if updateBatchErr != nil { utils.ErrorLog("群发短信成功后 更新批次状态失败:%v", updateBatchErr) tx.Rollback() continue } valueStrs := make([]string, 0, len(reports)) values := make([]interface{}, 0, len(reports)) for _, item := range reports { json := item.(map[string]interface{}) valueStrs = append(valueStrs, "(?, ?, ?, ?, ?, ?, ?)") values = append(values, batch.ID) 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, time.Now().Unix()) values = append(values, time.Now().Unix()) } sql := fmt.Sprintf("INSERT INTO sgj_patient_sms_send_status (batch_id, mobile, status, code, msg, ctime, mtime) VALUES %v;", strings.Join(valueStrs, ", ")) if insertReportErr := tx.Exec(sql, values...).Error; insertReportErr != nil { utils.ErrorLog("群发短信成功后 插入短信发送状态失败: %v", insertReportErr) tx.Rollback() continue } if failMobileCount > 0 { freeLimit, getFreeLimitErr := GetUserSMSFreeLimit(batch.OrgID, time.Unix(batch.SendTime, 0)) if getFreeLimitErr == nil { freeLimit.UsedCount -= GetWillConsumeLimit(batch.FullContent, failMobileCount) freeLimit.ModifyTime = time.Now().Unix() updateLimitErr := tx.Save(freeLimit).Error if updateLimitErr != nil { utils.ErrorLog("群发短信成功后 返回额度失败: %v", updateLimitErr) tx.Rollback() continue } } } tx.Commit() } else { batch.Status = models.SMSBatchStatusSendFailed batch.ModifyTime = time.Now().Unix() updateBatchErr := tx.Save(batch).Error if updateBatchErr != nil { utils.ErrorLog("群发短信成功后 更新批次状态失败:%v", updateBatchErr) tx.Rollback() continue } freeLimit, getFreeLimitErr := GetUserSMSFreeLimit(batch.OrgID, time.Unix(batch.SendTime, 0)) if getFreeLimitErr == nil { freeLimit.UsedCount -= GetWillConsumeLimit(batch.FullContent, len(mobiles)) freeLimit.ModifyTime = time.Now().Unix() updateLimitErr := tx.Save(freeLimit).Error if updateLimitErr != nil { utils.ErrorLog("群发短信成功后 返回额度失败: %v", updateLimitErr) tx.Rollback() continue } } tx.Commit() } } }