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.
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