yx-smart_sms 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +35 -0
  3. data/.rdoc_options +21 -0
  4. data/.travis.yml +9 -0
  5. data/CONTRIBUTING.md +32 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.lock +145 -0
  8. data/LICENSE +21 -0
  9. data/README.md +283 -0
  10. data/Rakefile +11 -0
  11. data/lib/generators/smart_sms/config_generator.rb +16 -0
  12. data/lib/generators/smart_sms/install_generator.rb +36 -0
  13. data/lib/generators/smart_sms/templates/add_uid_to_smart_sms_messages.rb +6 -0
  14. data/lib/generators/smart_sms/templates/create_smart_sms_messages.rb +19 -0
  15. data/lib/generators/smart_sms/templates/smart_sms_config.rb +15 -0
  16. data/lib/smart_sms.rb +35 -0
  17. data/lib/smart_sms/account.rb +25 -0
  18. data/lib/smart_sms/config.rb +49 -0
  19. data/lib/smart_sms/has_sms_verification.rb +149 -0
  20. data/lib/smart_sms/helpers/fake_sms.rb +31 -0
  21. data/lib/smart_sms/helpers/verification_code.rb +42 -0
  22. data/lib/smart_sms/message_service.rb +98 -0
  23. data/lib/smart_sms/model/message.rb +9 -0
  24. data/lib/smart_sms/request.rb +48 -0
  25. data/lib/smart_sms/template.rb +44 -0
  26. data/lib/smart_sms/version.rb +3 -0
  27. data/smart_sms.gemspec +35 -0
  28. data/spec/account_spec.rb +80 -0
  29. data/spec/config/config_spec.rb +172 -0
  30. data/spec/fake_app/active_record/config.rb +9 -0
  31. data/spec/fake_app/active_record/models.rb +44 -0
  32. data/spec/fake_app/initializers/smart_sms.rb +15 -0
  33. data/spec/fake_app/rails_app.rb +23 -0
  34. data/spec/has_sms_verificaton_spec.rb +275 -0
  35. data/spec/helpers/fake_sms_spec.rb +15 -0
  36. data/spec/helpers/verification_code_spec.rb +62 -0
  37. data/spec/smart_sms_spec.rb +261 -0
  38. data/spec/spec_helper.rb +26 -0
  39. data/spec/support/database_cleaner.rb +13 -0
  40. data/spec/template_spec.rb +168 -0
  41. metadata +264 -0
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec) do |spec|
8
+ spec.pattern = FileList['spec/**/*_spec.rb']
9
+ end
10
+
11
+ task default: 'spec:all'
@@ -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,6 @@
1
+ class AddUidToSmartSmsMessages < ActiveRecord::Migration
2
+ def change
3
+ add_column :smart_sms_messages, :uid, :string
4
+ add_index :smart_sms_messages, :uid
5
+ end
6
+ 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
@@ -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