yx-smart_sms 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +35 -0
- data/.rdoc_options +21 -0
- data/.travis.yml +9 -0
- data/CONTRIBUTING.md +32 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +145 -0
- data/LICENSE +21 -0
- data/README.md +283 -0
- data/Rakefile +11 -0
- data/lib/generators/smart_sms/config_generator.rb +16 -0
- data/lib/generators/smart_sms/install_generator.rb +36 -0
- data/lib/generators/smart_sms/templates/add_uid_to_smart_sms_messages.rb +6 -0
- data/lib/generators/smart_sms/templates/create_smart_sms_messages.rb +19 -0
- data/lib/generators/smart_sms/templates/smart_sms_config.rb +15 -0
- data/lib/smart_sms.rb +35 -0
- data/lib/smart_sms/account.rb +25 -0
- data/lib/smart_sms/config.rb +49 -0
- data/lib/smart_sms/has_sms_verification.rb +149 -0
- data/lib/smart_sms/helpers/fake_sms.rb +31 -0
- data/lib/smart_sms/helpers/verification_code.rb +42 -0
- data/lib/smart_sms/message_service.rb +98 -0
- data/lib/smart_sms/model/message.rb +9 -0
- data/lib/smart_sms/request.rb +48 -0
- data/lib/smart_sms/template.rb +44 -0
- data/lib/smart_sms/version.rb +3 -0
- data/smart_sms.gemspec +35 -0
- data/spec/account_spec.rb +80 -0
- data/spec/config/config_spec.rb +172 -0
- data/spec/fake_app/active_record/config.rb +9 -0
- data/spec/fake_app/active_record/models.rb +44 -0
- data/spec/fake_app/initializers/smart_sms.rb +15 -0
- data/spec/fake_app/rails_app.rb +23 -0
- data/spec/has_sms_verificaton_spec.rb +275 -0
- data/spec/helpers/fake_sms_spec.rb +15 -0
- data/spec/helpers/verification_code_spec.rb +62 -0
- data/spec/smart_sms_spec.rb +261 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/database_cleaner.rb +13 -0
- data/spec/template_spec.rb +168 -0
- metadata +264 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module SmartSms
|
|
2
|
+
module Generators
|
|
3
|
+
class ConfigGenerator < Rails::Generators::Base
|
|
4
|
+
source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
|
|
5
|
+
|
|
6
|
+
desc <<-DESC
|
|
7
|
+
Description:
|
|
8
|
+
Copies SmartSMS configuration file to your application's initializer directory.
|
|
9
|
+
DESC
|
|
10
|
+
|
|
11
|
+
def copy_config_file
|
|
12
|
+
template 'smart_sms_config.rb', 'config/initializers/smart_sms_config.rb'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'rails/generators'
|
|
2
|
+
require 'rails/generators/active_record'
|
|
3
|
+
|
|
4
|
+
module SmartSms
|
|
5
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
6
|
+
include ::Rails::Generators::Migration
|
|
7
|
+
|
|
8
|
+
source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
|
|
9
|
+
|
|
10
|
+
desc <<-EOF
|
|
11
|
+
Generates (but does not run) a migration to add a messages table.
|
|
12
|
+
You need to set `store_sms_in_local` to `true` in your config file
|
|
13
|
+
before running this command
|
|
14
|
+
EOF
|
|
15
|
+
|
|
16
|
+
def create_migration_file
|
|
17
|
+
return unless SmartSMS.config.store_sms_in_local
|
|
18
|
+
add_smart_sms_migration('create_smart_sms_messages')
|
|
19
|
+
add_smart_sms_migration('add_uid_to_smart_sms_messages')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.next_migration_number(dirname)
|
|
23
|
+
::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
protected
|
|
27
|
+
|
|
28
|
+
def add_smart_sms_migration(template)
|
|
29
|
+
migration_dir = File.expand_path('db/migrate')
|
|
30
|
+
|
|
31
|
+
unless self.class.migration_exists?(migration_dir, template)
|
|
32
|
+
migration_template "#{template}.rb", "db/migrate/#{template}.rb"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class CreateSmartSmsMessages < ActiveRecord::Migration
|
|
2
|
+
def change
|
|
3
|
+
create_table :smart_sms_messages do |t|
|
|
4
|
+
t.string :sid
|
|
5
|
+
t.string :mobile
|
|
6
|
+
t.datetime :send_time
|
|
7
|
+
t.text :text
|
|
8
|
+
t.string :code
|
|
9
|
+
t.string :send_status
|
|
10
|
+
t.string :report_status
|
|
11
|
+
t.string :fee
|
|
12
|
+
t.datetime :user_receive_time
|
|
13
|
+
t.text :error_msg
|
|
14
|
+
t.belongs_to :smsable, polymorphic: true
|
|
15
|
+
end
|
|
16
|
+
add_index :smart_sms_messages, :sid
|
|
17
|
+
add_index :smart_sms_messages, [:smsable_id, :smsable_type]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
SmartSMS.configure do |config|
|
|
4
|
+
# config.api_key = nil
|
|
5
|
+
# config.api_version = :v1
|
|
6
|
+
# config.template_id = '2'
|
|
7
|
+
# config.template_value = [:code, :company]
|
|
8
|
+
# config.page_num = 1
|
|
9
|
+
# config.page_size = 20
|
|
10
|
+
# config.company = '云片网'
|
|
11
|
+
# config.expires_in = 1.hour
|
|
12
|
+
# config.default_interval = 1.day
|
|
13
|
+
# config.store_sms_in_local = false
|
|
14
|
+
# config.verification_code_algorithm = :simple
|
|
15
|
+
end
|
data/lib/smart_sms.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'smart_sms/config'
|
|
2
|
+
require 'smart_sms/request'
|
|
3
|
+
require 'smart_sms/template'
|
|
4
|
+
require 'smart_sms/helpers/fake_sms'
|
|
5
|
+
require 'smart_sms/helpers/verification_code'
|
|
6
|
+
require 'smart_sms/message_service'
|
|
7
|
+
require 'smart_sms/account'
|
|
8
|
+
|
|
9
|
+
unless defined? ActiveRecord
|
|
10
|
+
begin
|
|
11
|
+
require 'active_record'
|
|
12
|
+
rescue LoadError; end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module SmartSMS
|
|
16
|
+
include SmartSMS::MessageService
|
|
17
|
+
|
|
18
|
+
def self.active_record_protected_attributes?
|
|
19
|
+
@active_record_protected_attributes ||= ::ActiveRecord::VERSION::MAJOR < 4 || !!defined?(ProtectedAttributes)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Ensure `ProtectedAttributes` gem gets required if it is available before the `Message` class gets loaded in
|
|
24
|
+
unless SmartSMS.active_record_protected_attributes?
|
|
25
|
+
SmartSMS.send(:remove_instance_variable, :@active_record_protected_attributes)
|
|
26
|
+
begin
|
|
27
|
+
require 'protected_attributes'
|
|
28
|
+
rescue LoadError; end # will rescue if `ProtectedAttributes` gem is not available
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
require 'smart_sms/has_sms_verification'
|
|
32
|
+
|
|
33
|
+
ActiveSupport.on_load(:active_record) do
|
|
34
|
+
include SmartSMS::HasSmsVerification
|
|
35
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module SmartSMS
|
|
4
|
+
# Module that handle user information
|
|
5
|
+
#
|
|
6
|
+
module Account
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# 获取用户信息
|
|
10
|
+
#
|
|
11
|
+
def info
|
|
12
|
+
Request.post 'user/get.json'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# 设置用户信息
|
|
16
|
+
#
|
|
17
|
+
# * emergency_contact: 紧急联系人
|
|
18
|
+
# * emergency_mobile: 紧急联系人手机号
|
|
19
|
+
# * alarm_balance: 短信余额提醒阈值。一天只提示一次
|
|
20
|
+
#
|
|
21
|
+
def set(options = {})
|
|
22
|
+
Request.post 'user/set.json', options
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'active_support/configurable'
|
|
3
|
+
require 'active_support/core_ext'
|
|
4
|
+
|
|
5
|
+
module SmartSMS
|
|
6
|
+
# Configures global settings for SmartSMS
|
|
7
|
+
# SmartSMS.configure do |config|
|
|
8
|
+
# config.api_key = 'd63124354422b046081a44466'
|
|
9
|
+
# end
|
|
10
|
+
def self.configure(&block)
|
|
11
|
+
yield @config ||= SmartSMS::Configuration.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Global settings for SmartSMS
|
|
15
|
+
def self.config
|
|
16
|
+
@config
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Configuration class
|
|
20
|
+
#
|
|
21
|
+
class Configuration #:nodoc:
|
|
22
|
+
include ActiveSupport::Configurable
|
|
23
|
+
config_accessor :api_key # 授权 API KEY
|
|
24
|
+
config_accessor :api_version # API 的版本, 当前仅有v1
|
|
25
|
+
config_accessor :template_id # 指定发送信息时使用的模板
|
|
26
|
+
config_accessor :template_value # 用于指定信息文本中的可替换内容, 数组形势: [:code, :company]
|
|
27
|
+
config_accessor :page_num # 获取信息时, 指定默认的页数
|
|
28
|
+
config_accessor :page_size # 获取信息时, 一页包含信息数量
|
|
29
|
+
config_accessor :company # 默认公司名称
|
|
30
|
+
config_accessor :expires_in # 短信验证过期时间
|
|
31
|
+
config_accessor :default_interval # 查询短信时的默认时间段: end_time - start_time
|
|
32
|
+
config_accessor :store_sms_in_local # 是否存储SMS信息在本地: true or false
|
|
33
|
+
config_accessor :verification_code_algorithm # :simple, :middle, :complex
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
configure do |config|
|
|
37
|
+
config.api_key = nil
|
|
38
|
+
config.api_version = :v1
|
|
39
|
+
config.template_id = '2'
|
|
40
|
+
config.template_value = [:code, :company]
|
|
41
|
+
config.page_num = 1
|
|
42
|
+
config.page_size = 20
|
|
43
|
+
config.company = '云片网'
|
|
44
|
+
config.expires_in = 1.hour
|
|
45
|
+
config.default_interval = 1.day
|
|
46
|
+
config.store_sms_in_local = false
|
|
47
|
+
config.verification_code_algorithm = :simple
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'model/message'))
|
|
3
|
+
|
|
4
|
+
module SmartSMS
|
|
5
|
+
# Module that will be hooked into ActiveRecord to provide magic methods
|
|
6
|
+
#
|
|
7
|
+
module HasSmsVerification
|
|
8
|
+
def self.included(base)
|
|
9
|
+
base.send :extend, ClassMethods
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Class methods that will be extended
|
|
13
|
+
module ClassMethods
|
|
14
|
+
|
|
15
|
+
# 在您的Model里面声明这个方法, 以添加SMS短信验证功能
|
|
16
|
+
#
|
|
17
|
+
# * moible_column: mobile 绑定的字段, 用于发送短信, 默认 :phone
|
|
18
|
+
# * verification_column: 验证绑定的字段, 用于判断是否已验证, 默认 :verified_at
|
|
19
|
+
#
|
|
20
|
+
# Options:
|
|
21
|
+
# * :class_name 自定义的Message类名称. 默认是 `::SmartSMS::Message`
|
|
22
|
+
# * :messages 自定义的Message关联名称. 默认是 `:messages`.
|
|
23
|
+
#
|
|
24
|
+
def has_sms_verification(moible_column = :phone, verification_column = :verified_at, options = {})
|
|
25
|
+
send :include, InstanceMethods
|
|
26
|
+
|
|
27
|
+
# 用于判断是否已经验证的字段, Datetime 类型, 例如 :verified_at
|
|
28
|
+
class_attribute :sms_verification_column
|
|
29
|
+
self.sms_verification_column = verification_column
|
|
30
|
+
|
|
31
|
+
class_attribute :sms_mobile_column
|
|
32
|
+
self.sms_mobile_column = moible_column
|
|
33
|
+
|
|
34
|
+
class_attribute :verify_regexp
|
|
35
|
+
self.verify_regexp = /(【.+】|[^a-zA-Z0-9\.\-\+_])/ # 用于抽取校验码, 如若修改过模板, 可能需要修改这个这正则
|
|
36
|
+
|
|
37
|
+
if SmartSMS.config.store_sms_in_local
|
|
38
|
+
|
|
39
|
+
class_attribute :messages_association_name
|
|
40
|
+
self.messages_association_name = options[:messages] || :messages
|
|
41
|
+
|
|
42
|
+
class_attribute :message_class_name
|
|
43
|
+
self.message_class_name = options[:class_name] || '::SmartSMS::Message'
|
|
44
|
+
|
|
45
|
+
if ::ActiveRecord::VERSION::MAJOR >= 4 # Rails 4 的 `has_many` 中定义order lambda的新语法
|
|
46
|
+
has_many messages_association_name,
|
|
47
|
+
-> { order('send_time ASC') },
|
|
48
|
+
class_name: message_class_name,
|
|
49
|
+
as: :smsable
|
|
50
|
+
else
|
|
51
|
+
has_many messages_association_name,
|
|
52
|
+
class_name: message_class_name,
|
|
53
|
+
as: :smsable,
|
|
54
|
+
order: 'send_time ASC'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Instance methods
|
|
61
|
+
#
|
|
62
|
+
module InstanceMethods
|
|
63
|
+
# 非安全verify!方法, 验证成功后会存储成功的结果到数据表中
|
|
64
|
+
#
|
|
65
|
+
def verify!(code)
|
|
66
|
+
result = verify code
|
|
67
|
+
if result
|
|
68
|
+
send("#{self.class.sms_verification_column}=", Time.now)
|
|
69
|
+
save(validate: false)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# 安全verify方法, 用于校验短信验证码是否正确, 返回: true 或 false
|
|
74
|
+
#
|
|
75
|
+
def verify(code)
|
|
76
|
+
sms = latest_message
|
|
77
|
+
return false if sms.blank?
|
|
78
|
+
if SmartSMS.config.store_sms_in_local
|
|
79
|
+
sms.code == code.to_s
|
|
80
|
+
else
|
|
81
|
+
sms['text'].gsub(self.class.verify_regexp, '') == code.to_s
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# 判断是否已经验证成功
|
|
86
|
+
#
|
|
87
|
+
def verified?
|
|
88
|
+
verified_at.present?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def verified_at
|
|
92
|
+
self[self.class.sms_verification_column]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# 获取最新的一条有效短信记录
|
|
96
|
+
#
|
|
97
|
+
def latest_message
|
|
98
|
+
end_time = Time.now
|
|
99
|
+
start_time = end_time - SmartSMS.config.expires_in
|
|
100
|
+
if SmartSMS.config.store_sms_in_local
|
|
101
|
+
send(self.class.messages_association_name)
|
|
102
|
+
.where('send_time >= ? and send_time <= ?', start_time, end_time)
|
|
103
|
+
.last
|
|
104
|
+
else
|
|
105
|
+
result = SmartSMS.find(
|
|
106
|
+
start_time: start_time,
|
|
107
|
+
end_time: end_time,
|
|
108
|
+
mobile: send(self.class.sms_mobile_column),
|
|
109
|
+
page_size: 1
|
|
110
|
+
)
|
|
111
|
+
result['sms'].first
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# 发送短信至手机
|
|
116
|
+
#
|
|
117
|
+
def deliver(text = SmartSMS::VerificationCode.random)
|
|
118
|
+
result = SmartSMS.deliver send(self.class.sms_mobile_column), text
|
|
119
|
+
if result['code'] == 0
|
|
120
|
+
sms = SmartSMS.find_by_sid(result['result']['sid'])['sms']
|
|
121
|
+
save_or_return_message sms, text
|
|
122
|
+
else
|
|
123
|
+
errors.add :deliver, result
|
|
124
|
+
false
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def deliver_fake_sms(text = SmartSMS::VerificationCode.random)
|
|
129
|
+
mobile = send(self.class.sms_mobile_column)
|
|
130
|
+
company = SmartSMS.config.company
|
|
131
|
+
sms = SmartSMS::FakeSMS.build_fake_sms mobile, text, company
|
|
132
|
+
save_or_return_message sms, text
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
def save_or_return_message(sms, text)
|
|
138
|
+
if SmartSMS.config.store_sms_in_local
|
|
139
|
+
message = send(self.class.messages_association_name).build sms
|
|
140
|
+
message.code = text
|
|
141
|
+
message.save
|
|
142
|
+
else
|
|
143
|
+
sms
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module SmartSMS
|
|
4
|
+
# Module that contains methods to generate fake message
|
|
5
|
+
#
|
|
6
|
+
module FakeSMS
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# This will generate fake sms with all necessary attributes
|
|
10
|
+
#
|
|
11
|
+
# Options:
|
|
12
|
+
#
|
|
13
|
+
# * mobile: mobile number
|
|
14
|
+
# * code: verification code
|
|
15
|
+
# * company: assigned company, format is【company】
|
|
16
|
+
#
|
|
17
|
+
def build_fake_sms(mobile, code, company)
|
|
18
|
+
{
|
|
19
|
+
'sid' => SecureRandom.uuid,
|
|
20
|
+
'mobile' => mobile,
|
|
21
|
+
'send_time' => Time.zone.now,
|
|
22
|
+
'text' => "您的验证码是#{code}。如非本人操作,请忽略本短信【#{company}】",
|
|
23
|
+
'send_status' => 'SUCCESS',
|
|
24
|
+
'report_status' => 'UNKNOWN',
|
|
25
|
+
'fee' => 1,
|
|
26
|
+
'user_receive_time' => nil,
|
|
27
|
+
'error_msg' => nil
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'securerandom'
|
|
2
|
+
|
|
3
|
+
module SmartSMS
|
|
4
|
+
# This module provides some methods to generate random verification code
|
|
5
|
+
#
|
|
6
|
+
# Algorithm:
|
|
7
|
+
#
|
|
8
|
+
# * short: Generate short code with 4 numbers
|
|
9
|
+
# * simple: Generate simple code with 6 numbers
|
|
10
|
+
# * middle: Generate middle complex code of 6 charactors with mixed numbers and letters
|
|
11
|
+
# * complex: Generate complex code of 8 charactors with mixed numbers, letters or special charactors
|
|
12
|
+
module VerificationCode
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
REGISTERED_ALGORITHMS = [:short, :simple, :middle, :complex]
|
|
16
|
+
|
|
17
|
+
def random(algorithm = '')
|
|
18
|
+
algorithm = SmartSMS.config.verification_code_algorithm if algorithm.blank?
|
|
19
|
+
if REGISTERED_ALGORITHMS.include? algorithm
|
|
20
|
+
SmartSMS::VerificationCode.send algorithm
|
|
21
|
+
else
|
|
22
|
+
fail NoMethodError
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def short
|
|
27
|
+
SecureRandom.random_number.to_s.slice(-4..-1)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def simple
|
|
31
|
+
SecureRandom.random_number.to_s.slice(-6..-1)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def middle
|
|
35
|
+
SecureRandom.base64.gsub!(/[^0-9a-zA-Z]/, '').slice(1..6).downcase
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def complex
|
|
39
|
+
SecureRandom.base64.slice(1..8).downcase
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|