The SMS sending functionality is refactored using the Singleton pattern to ensure the REST SDK is initialized only once. The sms.py module wraps the cloud communication SDK:
# -*- coding: utf-8 -*-
import logging
from CCPRestSDK import REST
# Credentials and configuration
ACCOUNT_SID = '8aaf07085f9eb021015fb58c56d906e8'
ACCOUNT_TOKEN = 'e21ca96da61c49ee97e0fdf1ba47de5a'
APP_ID = '8aaf07085f9eb021015fb58c574106ef'
SERVER_IP = 'app.cloopen.com'
SERVER_PORT = '8883'
SDK_VERSION = '2013-12-26'
class SMSManager:
"""Singleton wrapper for cloud communication REST SDK."""
_instance = None
def __new__(cls):
if cls._instance is None:
instance = super().__new__(cls)
instance.sdk = REST(SERVER_IP, SERVER_PORT, SDK_VERSION)
instance.sdk.setAccount(ACCOUNT_SID, ACCOUNT_TOKEN)
instance.sdk.setAppId(APP_ID)
cls._instance = instance
return cls._instance
def dispatch_template_message(self, phone_number, payload, template_id):
"""Send a template-based SMS and return 0 on success, -1 on failure."""
try:
response = self.sdk.sendTemplateSMS(phone_number, payload, template_id)
except Exception as exc:
logging.error(exc)
raise
logging.debug(response)
return 0 if response.get('statusCode') == '000000' else -1
if __name__ == '__main__':
sender = SMSManager()
status = sender.dispatch_template_message('17610812003', ['1234', 5], 1)
print('Dispatch result:', status)
A response_code.py file defines standardized response codes and messages:
# -*- coding: utf-8 -*-
class StatusCode:
SUCCESS = '0'
DB_ERROR = '4001'
NO_RECORD = '4002'
RECORD_EXISTS = '4003'
INVALID_DATA = '4004'
SESSION_MISSING = '4101'
AUTH_FAIL = '4102'
BAD_PARAM = '4103'
USER_MISSING = '4104'
ROLE_ISSUE = '4105'
WRONG_PASSWORD = '4106'
BAD_REQUEST = '4201'
IP_BLOCKED = '4202'
THIRD_PARTY_FAIL = '4301'
IO_FAIL = '4302'
INTERNAL_ERROR = '4500'
UNKNOWN = '4501'
message_map = {
StatusCode.SUCCESS: u'Success',
StatusCode.DB_ERROR: u'Database query error',
StatusCode.NO_RECORD: u'No data found',
StatusCode.RECORD_EXISTS: u'Data already exists',
StatusCode.INVALID_DATA: u'Invalid data',
StatusCode.SESSION_MISSING: u'User not logged in',
StatusCode.AUTH_FAIL: u'Login failed',
StatusCode.BAD_PARAM: u'Parameter error',
StatusCode.USER_MISSING: u'User does not exist or is inactive',
StatusCode.ROLE_ISSUE: u'Incorrect user role',
StatusCode.WRONG_PASSWORD: u'Wrong password',
StatusCode.BAD_REQUEST: u'Illegal request or rate limit exceeded',
StatusCode.IP_BLOCKED: u'IP restricted',
StatusCode.THIRD_PARTY_FAIL: u'Third-party service error',
StatusCode.IO_FAIL: u'File read/write error',
StatusCode.INTERNAL_ERROR: u'Internal server error',
StatusCode.UNKNOWN: u'Unknown error',
}
The API endpoint for sending a verificatoin code is implemented in api_1_0/verify_code.py:
from flask import request, jsonify
from api_1_0 import api
from models import User
from sms import SMSManager
from redis_client import redis_store
from constants import SMS_CODE_REDIS_EXPIRES
import random
import logging
@api.route('/sms_codes/<regex("1[34578]\d{9}"):mobile>')
def send_verification_code(mobile):
input_image_code = request.args.get('image_code')
image_uuid = request.args.get('image_code_id')
if not all([input_image_code, image_uuid]):
return jsonify(errno=StatusCode.BAD_PARAM, errmsg='Incomplete parameters')
try:
stored_code = redis_store.get('image_code_' + image_uuid)
except Exception as exc:
logging.error(exc)
return jsonify(errno=StatusCode.DB_ERROR, errmsg='Failed to retrieve image code')
if stored_code is None:
return jsonify(errno=StatusCode.NO_RECORD, errmsg='Image code expired or invalid')
try:
redis_store.delete('image_code_' + image_uuid)
except Exception as exc:
logging.error(exc)
if stored_code.lower() != input_image_code.lower():
return jsonify(errno=StatusCode.INVALID_DATA, errmsg='Incorrect image code')
try:
existing_user = User.query.filter_by(mobile=mobile).first()
except Exception as exc:
logging.error(exc)
else:
if existing_user:
return jsonify(errno=StatusCode.RECORD_EXISTS, errmsg='Mobile already registered')
verification_code = '%06d' % random.randint(0, 999999)
try:
redis_store.setex('sms_code_' + mobile, SMS_CODE_REDIS_EXPIRES, verification_code)
except Exception as exc:
logging.error(exc)
return jsonify(errno=StatusCode.DB_ERROR, errmsg='Failed to save verification code')
try:
sms_service = SMSManager()
result = sms_service.dispatch_template_message(
mobile,
[verification_code, SMS_CODE_REDIS_EXPIRES // 60],
1
)
except Exception as exc:
logging.error(exc)
return jsonify(errno=StatusCode.THIRD_PARTY_FAIL, errmsg='SMS dispatch failed')
if result == 0:
return jsonify(errno=StatusCode.SUCCESS, errmsg='SMS sent successfully')
return jsonify(errno=StatusCode.THIRD_PARTY_FAIL, errmsg='SMS sending failed')
The frontend handles the API response and manages a countdown timer:
var requestData = {
image_code_id: imageCodeId,
image_code: imageCode
};
$.get('/api/v1_0/sms_codes/' + mobile, requestData, function (response) {
if (response.errno === '4004' || response.errno === '4002') {
$('#image-code-err span').html(response.errmsg);
$('#image-code-err').show();
$('.phonecode-a').attr('onclick', 'sendSMSCode();');
} else if (response.errno === '0') {
var button = $('.phonecode-a');
var seconds = 60;
var timer = setInterval(function () {
button.html(seconds + ' seconds');
if (seconds === 1) {
clearInterval(timer);
button.html('Get code');
$('.phonecode-a').attr('onclick', 'sendSMSCode();');
}
seconds -= 1;
}, 1000);
} else {
alert(response.errmsg);
}
});