├── .gitignore ├── .rubocop.yml ├── Capfile ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── app ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── application_controller.rb │ ├── base_controller.rb │ ├── concerns │ │ └── .keep │ ├── coupons_controller.rb │ ├── districts_controller.rb │ ├── home_slides_controller.rb │ ├── manage_features_controller.rb │ ├── my_assets_controller.rb │ ├── orders_controller.rb │ ├── product_qr_codes_controller.rb │ ├── products_controller.rb │ ├── send_validation_codes_controller.rb │ ├── sessions_controller.rb │ └── wechat │ │ └── payments_controller.rb ├── helpers │ └── base_helper.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── concerns │ │ └── .keep │ ├── customer.rb │ ├── encryptor.rb │ ├── https_support.rb │ ├── order.rb │ ├── paper_trail │ │ └── version.rb │ ├── product.rb │ ├── profile.rb │ ├── user.rb │ ├── verification.rb │ ├── wechat_payment_manager.rb │ └── wechat_user.rb ├── serializers │ ├── baye │ │ ├── coupon_serializer.rb │ │ ├── customer_asset_serializer.rb │ │ └── slide_serializer.rb │ ├── district_serializer.rb │ ├── product_serializer.rb │ └── profile_serializer.rb ├── views │ └── layouts │ │ ├── mailer.html.erb │ │ └── mailer.text.erb └── workers │ └── verify_sms_send_worker.rb ├── bin ├── bundle ├── rails ├── rake ├── setup ├── spring └── update ├── config.ru ├── config ├── application.rb ├── application.yml.sample ├── boot.rb ├── cable.yml ├── database.yml.sample ├── deploy.rb ├── deploy │ └── production.rb ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── active_model_serializers.rb │ ├── ahoy.rb │ ├── application_controller_renderer.rb │ ├── backtrace_silencers.rb │ ├── carrierwave.rb │ ├── cl2009.rb │ ├── exception_notification.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── new_framework_defaults.rb │ ├── paper_trail.rb │ ├── redis.rb │ ├── sidekiq.rb │ ├── swagger_docs.rb │ ├── wrap_parameters.rb │ └── wx_pay.rb ├── locales │ ├── en.yml │ └── zh-CN.yml ├── puma-web.rb ├── puma.rb ├── redis.yml.sample ├── routes.rb ├── secrets.yml ├── sidekiq.yml.sample ├── spring.rb └── wechat.yml.sample ├── lib ├── tasks │ └── .keep └── wxbiz_data_crypt.rb ├── log └── .keep ├── public ├── favicon.ico ├── images │ ├── 3_asset_info.png │ ├── 3_assets.png │ ├── 53_asset_info.png │ ├── 53_assets.png │ ├── 5_asset_info.png │ └── 5_assets.png └── robots.txt ├── rapi.sublime-project ├── test ├── controllers │ ├── .keep │ └── sessions_controller_test.rb ├── fixtures │ ├── .keep │ ├── customers.yml │ ├── files │ │ └── .keep │ └── wechat_users.yml ├── integration │ └── .keep ├── mailers │ └── .keep ├── models │ └── .keep └── test_helper.rb └── tmp └── .keep /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | config/database.yml 16 | /public/swagger_doc/* 17 | 18 | # Ignore Byebug command history file. 19 | .byebug_history 20 | *.sublime-workspace 21 | # Ignore application configuration 22 | /config/application.yml 23 | /config/database.yml 24 | /config/sidekiq.yml 25 | /config/wechat.yml 26 | /config/application.yml 27 | /config/application.yml.dev 28 | /config/environments.yml 29 | /config/wechat_open_platform.yml 30 | /config/wechat_public_platform.yml 31 | /config/wechat_public_platform.yml.dev 32 | /config/wechat_public_platform.yml.staging 33 | /config/sidekiq.yml 34 | /config/redis.yml 35 | /config/newrelic.yml 36 | dump.rdb 37 | /db -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.3 3 | 4 | Rails: 5 | Enabled: true 6 | Exclude: 7 | - db/schema.rb 8 | 9 | ClassAndModuleChildren: 10 | Enabled: false 11 | 12 | ClassLength: 13 | Enabled: false 14 | 15 | Documentation: 16 | Enabled: false 17 | 18 | Metrics/LineLength: 19 | Max: 120 20 | 21 | Metrics/AbcSize: 22 | Max: 37 23 | 24 | Metrics/ClassLength: 25 | Max: 150 26 | 27 | Metrics/MethodLength: 28 | Max: 20 29 | 30 | Style/IndentationWidth: 31 | Width: 2 32 | 33 | Style/UnneededPercentQ: 34 | Enabled: false 35 | -------------------------------------------------------------------------------- /Capfile: -------------------------------------------------------------------------------- 1 | # Load DSL and set up stages 2 | require 'capistrano/setup' 3 | 4 | # Include default deployment tasks 5 | require 'capistrano/deploy' 6 | 7 | # Include tasks from other gems included in your Gemfile 8 | # 9 | # For documentation on these, see for example: 10 | # 11 | # https://github.com/capistrano/rvm 12 | # https://github.com/capistrano/rbenv 13 | # https://github.com/capistrano/chruby 14 | # https://github.com/capistrano/bundler 15 | # https://github.com/capistrano/rails 16 | # https://github.com/capistrano/passenger 17 | # 18 | require 'capistrano/rvm' 19 | # require 'capistrano/rbenv' 20 | # require 'capistrano/chruby' 21 | require 'capistrano/bundler' 22 | # require 'capistrano/rails/assets' 23 | # require 'capistrano/rails/migrations' 24 | # require 'capistrano/passenger' 25 | require 'capistrano/puma' 26 | install_plugin Capistrano::Puma # Default puma tasks 27 | install_plugin Capistrano::Puma::Workers # if you want to control the workers (in cluster mode) 28 | require 'capistrano/scm/git' 29 | install_plugin Capistrano::SCM::Git 30 | require 'capistrano/sidekiq' 31 | 32 | # Load custom tasks from `lib/capistrano/tasks` if you have any defined 33 | Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } 34 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 4 | gem 'rails', '~> 5.0.5' 5 | # Use mysql as the database for Active Record 6 | gem 'mysql2', '~> 0.4.10' 7 | # Use Puma as the app server 8 | gem 'puma', '~> 3.0' 9 | 10 | gem 'figaro' 11 | # Baye core logic, to run locally: 12 | # $ bundle config --local local.baye-core /Users/guochunzhong/git/landlord/baye-core/ 13 | gem 'baye-core', git: 'git@git.coding.net:ericguo/landlord_baye-core.git', branch: :master 14 | 15 | # Build JSON APIs with ease. Read more: https://github.com/rails-api/active_model_serializers 16 | gem 'active_model_serializers', '~> 0.10.2' 17 | 18 | gem 'ahoy_matey', '~> 1.6.1' 19 | 20 | # $ bundle config --local local.cl2009 /Users/guochunzhong/git/landlord/cl2009/ 21 | gem 'cl2009', git: 'git@git.coding.net:ericguo/cl2009.git', branch: :master 22 | 23 | gem 'sidekiq' 24 | # Generates swagger-ui json files for Rails APIs with a simple DSL 25 | gem 'swagger-docs' 26 | 27 | gem 'redis' 28 | # Use ActiveModel has_secure_password 29 | gem 'bcrypt', '~> 3.1.11' 30 | gem 'wx_pay' 31 | 32 | gem 'wechat' 33 | 34 | # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible 35 | gem 'rack-cors', require: 'rack/cors' 36 | 37 | group :production, :staging do 38 | gem 'exception_notification' 39 | end 40 | 41 | group :development, :test do 42 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 43 | gem 'byebug', platform: :mri 44 | gem 'pry' 45 | 46 | gem 'pry-byebug' 47 | end 48 | 49 | group :development do 50 | gem 'listen' 51 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 52 | gem 'spring' 53 | gem 'spring-watcher-listen', '~> 2.0.0' 54 | 55 | # Use Capistrano for deployment 56 | gem 'capistrano-rails' 57 | gem 'capistrano-rvm' 58 | gem 'capistrano3-puma' 59 | gem 'capistrano-sidekiq', require: false 60 | end 61 | 62 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 63 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 64 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git@git.coding.net:ericguo/cl2009.git 3 | revision: 3529347277dde9ff59c0680fdda485933490cca8 4 | branch: master 5 | specs: 6 | cl2009 (0.2.0) 7 | redis (~> 4.0) 8 | 9 | GIT 10 | remote: git@git.coding.net:ericguo/landlord_baye-core.git 11 | revision: 07d467db69d11661accb4169c5520658ddb6d96b 12 | branch: master 13 | specs: 14 | baye-core (0.1.0) 15 | activerecord (>= 5.0.7) 16 | ahoy_matey (~> 1.6.1) 17 | base32 18 | bcrypt (~> 3.1.11) 19 | carrierwave-aliyun (~> 0.9.0) 20 | elasticsearch-model (~> 5.0) 21 | elasticsearch-rails (~> 5.0) 22 | kakurenbo-puti (~> 0.5.0) 23 | kaminari 24 | mini_magick (~> 4.9.2) 25 | paper_trail (~> 8.1.0) 26 | sidekiq (~> 5.2.5) 27 | state_machines-activemodel (~> 0.5.0) 28 | state_machines-activerecord (~> 0.5.0) 29 | 30 | GEM 31 | remote: https://rubygems.org/ 32 | specs: 33 | actioncable (5.0.7.1) 34 | actionpack (= 5.0.7.1) 35 | nio4r (>= 1.2, < 3.0) 36 | websocket-driver (~> 0.6.1) 37 | actionmailer (5.0.7.1) 38 | actionpack (= 5.0.7.1) 39 | actionview (= 5.0.7.1) 40 | activejob (= 5.0.7.1) 41 | mail (~> 2.5, >= 2.5.4) 42 | rails-dom-testing (~> 2.0) 43 | actionpack (5.0.7.1) 44 | actionview (= 5.0.7.1) 45 | activesupport (= 5.0.7.1) 46 | rack (~> 2.0) 47 | rack-test (~> 0.6.3) 48 | rails-dom-testing (~> 2.0) 49 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 50 | actionview (5.0.7.1) 51 | activesupport (= 5.0.7.1) 52 | builder (~> 3.1) 53 | erubis (~> 2.7.0) 54 | rails-dom-testing (~> 2.0) 55 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 56 | active_model_serializers (0.10.8) 57 | actionpack (>= 4.1, < 6) 58 | activemodel (>= 4.1, < 6) 59 | case_transform (>= 0.2) 60 | jsonapi-renderer (>= 0.1.1.beta1, < 0.3) 61 | activejob (5.0.7.1) 62 | activesupport (= 5.0.7.1) 63 | globalid (>= 0.3.6) 64 | activemodel (5.0.7.1) 65 | activesupport (= 5.0.7.1) 66 | activerecord (5.0.7.1) 67 | activemodel (= 5.0.7.1) 68 | activesupport (= 5.0.7.1) 69 | arel (~> 7.0) 70 | activesupport (5.0.7.1) 71 | concurrent-ruby (~> 1.0, >= 1.0.2) 72 | i18n (>= 0.7, < 2) 73 | minitest (~> 5.1) 74 | tzinfo (~> 1.1) 75 | addressable (2.5.2) 76 | public_suffix (>= 2.0.2, < 4.0) 77 | ahoy_matey (1.6.1) 78 | addressable 79 | browser (~> 2.0) 80 | geocoder 81 | rack-attack (< 6) 82 | railties 83 | referer-parser (>= 0.3.0) 84 | request_store 85 | safely_block (>= 0.1.1) 86 | user_agent_parser 87 | uuidtools 88 | airbrussh (1.3.1) 89 | sshkit (>= 1.6.1, != 1.7.0) 90 | aliyun-oss-sdk (0.1.8) 91 | addressable 92 | gyoku 93 | httparty 94 | arel (7.1.4) 95 | base32 (0.3.2) 96 | bcrypt (3.1.12) 97 | browser (2.5.3) 98 | builder (3.2.3) 99 | byebug (10.0.2) 100 | capistrano (3.11.0) 101 | airbrussh (>= 1.0.0) 102 | i18n 103 | rake (>= 10.0.0) 104 | sshkit (>= 1.9.0) 105 | capistrano-bundler (1.5.0) 106 | capistrano (~> 3.1) 107 | capistrano-rails (1.4.0) 108 | capistrano (~> 3.1) 109 | capistrano-bundler (~> 1.1) 110 | capistrano-rvm (0.1.2) 111 | capistrano (~> 3.0) 112 | sshkit (~> 1.2) 113 | capistrano-sidekiq (1.0.2) 114 | capistrano (>= 3.9.0) 115 | sidekiq (>= 3.4) 116 | capistrano3-puma (3.1.1) 117 | capistrano (~> 3.7) 118 | capistrano-bundler 119 | puma (~> 3.4) 120 | carrierwave (1.3.1) 121 | activemodel (>= 4.0.0) 122 | activesupport (>= 4.0.0) 123 | mime-types (>= 1.16) 124 | carrierwave-aliyun (0.9.0) 125 | aliyun-oss-sdk (>= 0.1.6) 126 | carrierwave (>= 0.5.7) 127 | case_transform (0.2) 128 | activesupport 129 | coderay (1.1.2) 130 | concurrent-ruby (1.1.4) 131 | connection_pool (2.2.2) 132 | crass (1.0.4) 133 | domain_name (0.5.20180417) 134 | unf (>= 0.0.5, < 1.0.0) 135 | elasticsearch (5.0.5) 136 | elasticsearch-api (= 5.0.5) 137 | elasticsearch-transport (= 5.0.5) 138 | elasticsearch-api (5.0.5) 139 | multi_json 140 | elasticsearch-model (5.1.0) 141 | activesupport (> 3) 142 | elasticsearch (~> 5) 143 | hashie 144 | elasticsearch-rails (5.1.0) 145 | elasticsearch-transport (5.0.5) 146 | faraday 147 | multi_json 148 | errbase (0.1.1) 149 | erubis (2.7.0) 150 | exception_notification (4.3.0) 151 | actionmailer (>= 4.0, < 6) 152 | activesupport (>= 4.0, < 6) 153 | faraday (0.15.4) 154 | multipart-post (>= 1.2, < 3) 155 | ffi (1.10.0) 156 | figaro (1.1.1) 157 | thor (~> 0.14) 158 | geocoder (1.5.0) 159 | globalid (0.4.1) 160 | activesupport (>= 4.2.0) 161 | gyoku (1.3.1) 162 | builder (>= 2.1.2) 163 | hashie (3.6.0) 164 | http (3.3.0) 165 | addressable (~> 2.3) 166 | http-cookie (~> 1.0) 167 | http-form_data (~> 2.0) 168 | http_parser.rb (~> 0.6.0) 169 | http-cookie (1.0.3) 170 | domain_name (~> 0.5) 171 | http-form_data (2.1.1) 172 | http_parser.rb (0.6.0) 173 | httparty (0.16.3) 174 | mime-types (~> 3.0) 175 | multi_xml (>= 0.5.2) 176 | i18n (1.5.1) 177 | concurrent-ruby (~> 1.0) 178 | jsonapi-renderer (0.2.0) 179 | kakurenbo-puti (0.5.0) 180 | activerecord (>= 4.1.0) 181 | kaminari (1.1.1) 182 | activesupport (>= 4.1.0) 183 | kaminari-actionview (= 1.1.1) 184 | kaminari-activerecord (= 1.1.1) 185 | kaminari-core (= 1.1.1) 186 | kaminari-actionview (1.1.1) 187 | actionview 188 | kaminari-core (= 1.1.1) 189 | kaminari-activerecord (1.1.1) 190 | activerecord 191 | kaminari-core (= 1.1.1) 192 | kaminari-core (1.1.1) 193 | listen (3.1.5) 194 | rb-fsevent (~> 0.9, >= 0.9.4) 195 | rb-inotify (~> 0.9, >= 0.9.7) 196 | ruby_dep (~> 1.2) 197 | loofah (2.2.3) 198 | crass (~> 1.0.2) 199 | nokogiri (>= 1.5.9) 200 | mail (2.7.1) 201 | mini_mime (>= 0.1.1) 202 | method_source (0.9.2) 203 | mime-types (3.2.2) 204 | mime-types-data (~> 3.2015) 205 | mime-types-data (3.2018.0812) 206 | mini_magick (4.9.2) 207 | mini_mime (1.0.1) 208 | mini_portile2 (2.4.0) 209 | minitest (5.11.3) 210 | multi_json (1.13.1) 211 | multi_xml (0.6.0) 212 | multipart-post (2.0.0) 213 | mysql2 (0.4.10) 214 | net-scp (1.2.1) 215 | net-ssh (>= 2.6.5) 216 | net-ssh (5.1.0) 217 | netrc (0.11.0) 218 | nio4r (2.3.1) 219 | nokogiri (1.10.0) 220 | mini_portile2 (~> 2.4.0) 221 | paper_trail (8.1.2) 222 | activerecord (>= 4.2, < 5.2) 223 | request_store (~> 1.1) 224 | pry (0.12.2) 225 | coderay (~> 1.1.0) 226 | method_source (~> 0.9.0) 227 | pry-byebug (3.6.0) 228 | byebug (~> 10.0) 229 | pry (~> 0.10) 230 | public_suffix (3.0.3) 231 | puma (3.12.0) 232 | rack (2.0.6) 233 | rack-attack (5.4.2) 234 | rack (>= 1.0, < 3) 235 | rack-cors (1.0.2) 236 | rack-protection (2.0.5) 237 | rack 238 | rack-test (0.6.3) 239 | rack (>= 1.0) 240 | rails (5.0.7.1) 241 | actioncable (= 5.0.7.1) 242 | actionmailer (= 5.0.7.1) 243 | actionpack (= 5.0.7.1) 244 | actionview (= 5.0.7.1) 245 | activejob (= 5.0.7.1) 246 | activemodel (= 5.0.7.1) 247 | activerecord (= 5.0.7.1) 248 | activesupport (= 5.0.7.1) 249 | bundler (>= 1.3.0) 250 | railties (= 5.0.7.1) 251 | sprockets-rails (>= 2.0.0) 252 | rails-dom-testing (2.0.3) 253 | activesupport (>= 4.2.0) 254 | nokogiri (>= 1.6) 255 | rails-html-sanitizer (1.0.4) 256 | loofah (~> 2.2, >= 2.2.2) 257 | railties (5.0.7.1) 258 | actionpack (= 5.0.7.1) 259 | activesupport (= 5.0.7.1) 260 | method_source 261 | rake (>= 0.8.7) 262 | thor (>= 0.18.1, < 2.0) 263 | rake (12.3.2) 264 | rb-fsevent (0.10.3) 265 | rb-inotify (0.10.0) 266 | ffi (~> 1.0) 267 | redis (4.1.0) 268 | referer-parser (0.3.0) 269 | request_store (1.4.1) 270 | rack (>= 1.4) 271 | rest-client (2.0.2) 272 | http-cookie (>= 1.0.2, < 2.0) 273 | mime-types (>= 1.16, < 4.0) 274 | netrc (~> 0.8) 275 | ruby_dep (1.5.0) 276 | safely_block (0.2.1) 277 | errbase 278 | sidekiq (5.2.5) 279 | connection_pool (~> 2.2, >= 2.2.2) 280 | rack (>= 1.5.0) 281 | rack-protection (>= 1.5.0) 282 | redis (>= 3.3.5, < 5) 283 | spring (2.0.2) 284 | activesupport (>= 4.2) 285 | spring-watcher-listen (2.0.1) 286 | listen (>= 2.7, < 4.0) 287 | spring (>= 1.2, < 3.0) 288 | sprockets (3.7.2) 289 | concurrent-ruby (~> 1.0) 290 | rack (> 1, < 3) 291 | sprockets-rails (3.2.1) 292 | actionpack (>= 4.0) 293 | activesupport (>= 4.0) 294 | sprockets (>= 3.0.0) 295 | sshkit (1.18.0) 296 | net-scp (>= 1.1.2) 297 | net-ssh (>= 2.8.0) 298 | state_machines (0.5.0) 299 | state_machines-activemodel (0.5.1) 300 | activemodel (>= 4.1, < 6.0) 301 | state_machines (>= 0.5.0) 302 | state_machines-activerecord (0.5.2) 303 | activerecord (>= 4.1, < 6.0) 304 | state_machines-activemodel (>= 0.5.0) 305 | swagger-docs (0.2.9) 306 | activesupport (>= 3) 307 | rails (>= 3) 308 | thor (0.20.3) 309 | thread_safe (0.3.6) 310 | tzinfo (1.2.5) 311 | thread_safe (~> 0.1) 312 | unf (0.1.4) 313 | unf_ext 314 | unf_ext (0.0.7.5) 315 | user_agent_parser (2.5.1) 316 | uuidtools (2.1.5) 317 | websocket-driver (0.6.5) 318 | websocket-extensions (>= 0.1.0) 319 | websocket-extensions (0.1.3) 320 | wechat (0.10.3) 321 | activesupport (>= 3.2, < 6) 322 | http (>= 1.0.4, < 4) 323 | nokogiri (>= 1.6.0) 324 | thor 325 | wx_pay (0.18.0) 326 | activesupport (>= 3.2) 327 | rest-client (>= 2.0.0) 328 | 329 | PLATFORMS 330 | ruby 331 | 332 | DEPENDENCIES 333 | active_model_serializers (~> 0.10.2) 334 | ahoy_matey (~> 1.6.1) 335 | baye-core! 336 | bcrypt (~> 3.1.11) 337 | byebug 338 | capistrano-rails 339 | capistrano-rvm 340 | capistrano-sidekiq 341 | capistrano3-puma 342 | cl2009! 343 | exception_notification 344 | figaro 345 | listen 346 | mysql2 (~> 0.4.10) 347 | pry 348 | pry-byebug 349 | puma (~> 3.0) 350 | rack-cors 351 | rails (~> 5.0.5) 352 | redis 353 | sidekiq 354 | spring 355 | spring-watcher-listen (~> 2.0.0) 356 | swagger-docs 357 | tzinfo-data 358 | wechat 359 | wx_pay 360 | 361 | BUNDLED WITH 362 | 1.17.3 363 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This Rails project is a backend reference source for [wechat mall applet](https://github.com/bayetech/wechat_mall_applet), it need baye internal db to running, so normally, you can not running in your dev env, but only can reference. 4 | 5 | # To generate swagger docs 6 | 7 | ```bash 8 | bundle exec rake swagger:docs RAILS_ENV=production 9 | ``` 10 | 11 | # Known issue 12 | 13 | Need change [basePath](https://github.com/richhollis/swagger-docs/pull/144) of `swagger_doc/api-docs.json` from empty to `http://localhost:3000/swagger_doc` in file `public/swagger_doc/api-docs.json`, then using `http://localhost:3000/swagger_doc/api-docs.json` to access. 14 | 15 | staging env change to 16 | 17 | ```json 18 | "basePath": "https://rapi-staging.bayekeji.com/swagger_doc", 19 | ``` 20 | 21 | Using `https://rapi-staging.bayekeji.com/swagger_doc/api-docs.json` to access. 22 | 23 | [RAPI swagger UI site](https://swagger-ui.bayekeji.com/?url=https://rapi-staging.bayekeji.com/swagger_doc/api-docs.json#!/products/Products_index) -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < BaseController 2 | before_action :authenticate_customer! 3 | 4 | private 5 | 6 | def authenticate_customer! 7 | return render json: { return_status: 401, retrun_message: 'Unauthorized' }, status: 401 if current_customer.blank? 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/base_controller.rb: -------------------------------------------------------------------------------- 1 | class BaseController < ActionController::API 2 | include BaseHelper 3 | end 4 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/coupons_controller.rb: -------------------------------------------------------------------------------- 1 | class CouponsController < ApplicationController 2 | def list 3 | product_ids = params[:product_ids]&.split(',') 4 | products_order_quantities = params[:products_order_quantities]&.split(',') 5 | 6 | if product_ids.present? && products_order_quantities.present? 7 | @coupons = current_customer.get_available_coupons_from_order_items(product_ids, products_order_quantities) 8 | else 9 | @coupons = current_customer.coupons.valid_unused_coupons 10 | end 11 | @coupons = @coupons.joins(:coupon_defination).where('coupon_definations.coupon_type = ?', 5) if params[:coupon_type] == 'pick_up_good' 12 | @coupons = @coupons.joins(:coupon_defination).where('coupon_definations.coupon_type = ?', 6) if params[:coupon_type] == 'gift' 13 | render json: @coupons 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/controllers/districts_controller.rb: -------------------------------------------------------------------------------- 1 | class DistrictsController < BaseController 2 | def cities 3 | data = Baye::District.cities(params[:province]) 4 | render json: data 5 | end 6 | 7 | def counties 8 | data = Baye::District.counties(params[:province], params[:city]) 9 | render json: data 10 | end 11 | 12 | def districts 13 | data = Baye::District.cities(params[:province]) if params[:province].present? 14 | data = Baye::District.counties(params[:province], params[:city]) if params[:city].present? 15 | data = Baye::District.provinces if params[:province].nil? && params[:city].nil? 16 | render json: data 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/controllers/home_slides_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeSlidesController < BaseController 2 | swagger_controller :home_slides, 'Bayetech Store Home Slides' 3 | 4 | swagger_api :index do 5 | summary 'Fetches all store index home slide items' 6 | notes 'This lists all enabled Store position slide items' 7 | end 8 | 9 | def index 10 | @slides = Baye::Slide.enabled.where(position: 'Store').order('sort asc') 11 | render json: @slides 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/manage_features_controller.rb: -------------------------------------------------------------------------------- 1 | class ManageFeaturesController < BaseController 2 | # 用于开关某些 feature,因为审核较慢,而前端功能通常没有那么复杂 3 | 4 | def index 5 | h = { 6 | enableGuanDao: false, #启用管到产品的购买 7 | enableNewFlag: false #将首页调整为正式的产品数量,而不是为了审核 8 | } 9 | render json: h 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/my_assets_controller.rb: -------------------------------------------------------------------------------- 1 | class MyAssetsController < ApplicationController 2 | def index 3 | products = Product.asset.where(product_id: nil).common 4 | 5 | # 这三张轮播图是 wechat 项目里的 assets 6 | image_hash = {} 7 | image_hash['TF-WUYI'] = "images/asset_TF-WUYI.jpg" 8 | image_hash['RF-WUCHANG'] = "images/asset_RF-WUCHANG.jpg" 9 | image_hash['100100300100'] = "images/asset_100100300100.jpg" 10 | 11 | arr = products.map do |product| 12 | image = image_hash[product.sku] 13 | next if image.blank? 14 | inventory = product.relation_products.map do |relation_product| 15 | inventory = current_customer.inventories.where(product_id: relation_product.id).first 16 | stock = inventory.present? ? inventory.quantity : 0 17 | relation_product.name + stock.to_s + relation_product.unit + " " 18 | end 19 | asset_count = current_customer.assets.where(product_id: product.id).count || 0 20 | 21 | Profile.new(id: product.id, 22 | sku: product.sku, 23 | inventory: "库存:#{inventory.join}", 24 | name: product.name, 25 | asset_count: "#{asset_count}亩", 26 | image: image) 27 | end.compact 28 | 29 | render json: arr 30 | end 31 | 32 | def show 33 | product = Product.find_by(sku: params[:sku]) 34 | 35 | my_asset = current_customer.assets.find_by(product_id: product.id) 36 | 37 | render json: my_asset, include: ['product'] 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /app/controllers/orders_controller.rb: -------------------------------------------------------------------------------- 1 | class OrdersController < ApplicationController 2 | swagger_controller :orders, 'Bayetech Store Order' 3 | 4 | swagger_api :create_order do 5 | summary 'create order' 6 | notes 'create order' 7 | end 8 | 9 | def create_applet_order 10 | return nil if params[:order_items].blank? 11 | 12 | orders = [] 13 | params[:order_items].group_by do |oi| 14 | product = Product.find_by(uid: oi[:product_uid]) 15 | "#{product.store_id}_#{product.shippment_id}" 16 | end.each do |k, v| 17 | order = {} 18 | order[:store_id] = k.split('_').first 19 | order[:customer_id] = current_customer.id 20 | # order[:ship_address_id] = ship_address.id 21 | order[:type] = Order.name 22 | order[:state] = :waiting_for_pay 23 | order[:province] = params[:province] 24 | order[:city] = params[:city] 25 | order[:county] = params[:county] 26 | order[:detail_address] = params[:detail_address] 27 | order[:customer_name] = params[:customer_name] 28 | order[:customer_mobile] = params[:customer_mobile] 29 | order[:category] = '商品' 30 | order[:order_from] = params[:order_from] 31 | 32 | order[:order_items_attributes] = v.map do |oi| 33 | product = Product.find_by(uid: oi[:product_uid]) 34 | order[:shippment_type] = oi[:shippment_type] unless product.shippment.try(:is_pinkage?) 35 | 36 | item = { 37 | 'quantity' => oi[:quantity], 38 | 'customer_id' => current_customer.id, 39 | 'product_id' => product.id, 40 | 'product_price' => product.product_price(current_customer), 41 | 'product_name' => product.name, 42 | 'product_unit' => product.unit, 43 | 'product_weight' => product.weight, 44 | 'sub_total' => oi[:quantity] * product.product_price(current_customer), 45 | 'external_content' => oi[:external_content] 46 | } 47 | if (oi[:external_content].present? || oi[:inside_content].present?) && current_customer.account_type != '巴爷' 48 | item['customization_cost'] = 9 * oi[:quantity].to_i 49 | else 50 | item['customization_cost'] = 0 51 | end 52 | item 53 | end 54 | orders << order 55 | end 56 | orders = current_customer.orders.build orders 57 | return nil if orders.blank? 58 | 59 | orders.each do |order| 60 | order.product_amount = order.order_items.inject(0) { |sum, i| sum + i.sub_total } 61 | order.quantity = order.order_items.inject(0) { |sum, i| sum + i.quantity } 62 | order.weight = order.order_items.inject(0) { |sum, i| sum + i.product_weight } 63 | order.amount = order.product_amount 64 | order.save! 65 | end 66 | 67 | @payment = current_customer.payments.build 68 | @payment.orders = orders 69 | @payment.pay_from = 'WECHAT' 70 | @payment.coin_quantity = 0 71 | @payment.ip = request.remote_ip 72 | @payment.expired_at = Time.current + 1.day 73 | @payment.amount = orders.map(&:amount).sum 74 | @payment.save 75 | 76 | # use coupon 77 | if params[:coupon_code].present? 78 | coupon = Baye::Coupon.find_by(code: params[:coupon_code]) 79 | if coupon.present? && @payment.can_use_coupon?(coupon) 80 | @payment.add_coupon(coupon) 81 | @payment.use_coupon 82 | @payment.save 83 | coupon.update(customer_id: current_customer.id, binding_customer_at: Time.current) 84 | else 85 | return render status: 403, json: { code: 4001, msg: '该优惠券无法使用!' } 86 | end 87 | else 88 | @payment.save 89 | end 90 | 91 | # WARN: FOR TEST 92 | @payment.update(amount: 0.01) if Rails.env.staging? 93 | 94 | # 微信支付 95 | manager = WechatPaymentManager.new 96 | data = manager.register_for_jsapi(@payment, current_wechat_user) 97 | return render status: 403, json: { code: 4005, msg: 'data.prepay_id is blank' } unless data&.prepay_id&.content 98 | 99 | wechat_payment = Baye::WechatPayment.new 100 | wechat_payment.prepay_id = data.prepay_id.content 101 | wechat_payment.request_data = data.to_s 102 | @payment.wechat_payment = wechat_payment 103 | 104 | unless @payment.save 105 | Rails.logger.debug "无法更新支付:#{@payment.errors.full_messages}." 106 | return nil 107 | end 108 | 109 | @hash = manager.build_hash_for_jsapi_call(wechat_payment.prepay_id) 110 | @hash[:signature] = manager.build_signature(@hash) 111 | return render json: { hash: @hash } 112 | rescue ActiveRecord::RecordInvalid => e 113 | return render status: 403, json: { code: 4007, msg: e.message } 114 | end 115 | 116 | private 117 | 118 | def payment_params 119 | params.permit(:token, payment: [ :amount, 120 | orders_attributes: [ :detail_address, :province, :city, :county, 121 | order_items_attributes: [:product_id, :quantity, :external_content]] 122 | ]) 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /app/controllers/product_qr_codes_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductQrCodesController < BaseController 2 | VERSION = 1 3 | 4 | 5 | # WARN: deprecate method! 6 | def image 7 | return render status: 403 8 | 9 | file_name = check_page_path 10 | return render status: 403, json: { errcode: 1, msg: '这个小程序地址不在rapi白名单中' } if file_name.nil? 11 | 12 | file_path = Rails.root.join('public', 'page_qr_codes', file_name) 13 | unless File.exist?(file_path) 14 | file = Wechat.api.wxa_create_qrcode(params[:path]).open 15 | IO.binwrite(file_path, file.read) 16 | end 17 | 18 | url = request.base_url + '/page_qr_codes/' + file_name 19 | render json: { url: url } 20 | end 21 | 22 | def render_pay_qr_image 23 | key = 'pay_' + SecureRandom.hex(8) 24 | # Cart 是 tabBar 可能不能带参数了 25 | qr_image_url = "pages/cart/cart?ticket=#{key}" 26 | temp_info = params.to_json 27 | 28 | $redis.set(key, temp_info) 29 | $redis.expire(key, 7200) 30 | 31 | file_base_name = "#{key}.jpg" 32 | file_path = Rails.root.join('public', 'page_qr_codes', file_base_name) 33 | file = Wechat.api.wxa_create_qrcode(qr_image_url).open 34 | IO.binwrite(file_path, file.read) 35 | 36 | url = request.base_url + '/page_qr_codes/' + file_base_name 37 | render json: { url: url } 38 | end 39 | 40 | private 41 | 42 | def check_page_path 43 | case params[:path] 44 | when %r{pages/show_product/show_product\?id=(\d+)&share=1\z} 45 | "show_product_#{$1}_share_v#{VERSION}.jpg" 46 | when %r{pages/index/index\z} 47 | "index_v#{VERSION}.jpg" 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /app/controllers/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < BaseController 2 | swagger_controller :products, 'Bayetech Product' 3 | 4 | swagger_api :index do 5 | summary 'Fetches all Product items' 6 | notes 'This lists all on_sale common is_search and enabled products' 7 | end 8 | 9 | # {icon: "../../images/icon-new-list1.png", name: "资产", typeId: 0} 10 | # {icon: "../../images/icon-new-list2.png", name: "直销", typeId: 1} 11 | # {icon: "../../images/icon-new-list3.png", name: "甄选", typeId: 2} 12 | # {icon: "../../images/icon-new-list4.png", name: "管到", typeId: 3} 13 | def index 14 | @products = Product.includes(:description_of_detail).includes(:images).on_sale.common.is_search.enabled 15 | @products = case params[:type] 16 | when "0" 17 | @products.subscription_goods 18 | when "1" 19 | # 4 食品 20 | # 6 大米 21 | # 7 茶叶 22 | # 15 白酒 23 | @products.where(category_id: [4, 6, 7, 15]) 24 | when "2" 25 | @products.where(category_id: 17) 26 | when "3" 27 | @products.where(category_id: 18, product_id: nil) 28 | else 29 | @products.where(flag: [1, 3, 4, 5]) 30 | end 31 | 32 | render json: @products 33 | end 34 | 35 | def show 36 | products = Product.includes(:description_of_detail).includes(:images).on_sale.common.is_search.enabled 37 | @product = products.find(params[:id]) 38 | render json: @product 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /app/controllers/send_validation_codes_controller.rb: -------------------------------------------------------------------------------- 1 | class SendValidationCodesController < BaseController 2 | 3 | def send_message 4 | mobile = params[:mobile] 5 | render json: { code: 20005, message: '手机号不存在' }.to_json and return if mobile.blank? 6 | render json: { code: 20004, message: '手机格式不对' }.to_json and return unless (mobile =~ /\A1[3-9][0-9]\d{8}\z/) 7 | render json: { code: 20003, message: '发送频率过多' }.to_json and return unless Verification.can_send?(mobile) 8 | render json: { code: 20001, message: '发送成功' }.to_json and return if Verification.send_message!(mobile) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require Rails.root.join('lib', 'wxbiz_data_crypt') 3 | 4 | class SessionsController < BaseController 5 | # 打开应用时就获取用户敏感信息 6 | # 这里不返回 @token, @customer 的 id 更新也是 nil 7 | def wechat_user_type 8 | update_wechat_user_token unless Rails.env.staging? 9 | return render json: { wechat_user_type: 'normal' } 10 | 11 | rescue DevDomainError, NoAppIdError => e 12 | Rails.logger.info "appdev=======#{e.message} #{request.remote_ip}" 13 | return render json: { wechat_user_type: 'normal' } 14 | end 15 | 16 | def login 17 | @mobile = params[:mobile] 18 | @token = request.headers['Authorization'] 19 | @customer = nil 20 | 21 | if token_valid? 22 | token_login 23 | return render status: 403, json: { code: 7, msg: 'token登录出错' } if @customer.nil? 24 | else 25 | return render status: 403, json: { code: 4, msg: '需要填写手机号码' } if @mobile.blank? 26 | 27 | case login_type 28 | when :password 29 | password_login 30 | return render status: 403, json: { code: 6, msg: '密码不正确或该账号没有绑定' } if @customer.nil? 31 | when :mobile_code 32 | mobilecode_login 33 | return render status: 403, json: { code: 5, msg: '手机验证码不正确!' } if @customer.nil? 34 | end 35 | begin 36 | update_wechat_user_token 37 | rescue DevDomainError, NoAppIdError => e 38 | return render status: 403, json: { code: 9, msg: e.message } 39 | end 40 | end 41 | render json: { token: @token, customer: @customer.slice(:name, :mobile, :baye_rank, :id, :account_type) } 42 | end 43 | 44 | def logout 45 | # TODO: code not used 46 | # 但 code 每次 token 登录也会变 47 | param_code = params[:code] 48 | return render status: 500 unless current_wechat_user 49 | 50 | current_wechat_user.expire! 51 | render status: 200 52 | end 53 | 54 | private 55 | 56 | def login_type 57 | return :password if params[:password].present? 58 | return :mobile_code if params[:mobile_code].present? 59 | end 60 | 61 | def password_login 62 | c = Customer.find_by mobile: @mobile 63 | @customer = c if c&.valid_password?(params[:password]) 64 | end 65 | 66 | def mobilecode_login 67 | return nil unless Verification.validate?(@mobile, params[:mobile_code]) 68 | @customer = Customer.find_by(mobile: @mobile) 69 | @customer = Customer.new.register!(mobile: @mobile, name: params[:name]) unless @customer 70 | end 71 | 72 | def token_login 73 | return nil unless current_wechat_user 74 | current_wechat_user.update!(wx_code: params[:code]) 75 | 76 | @customer = Customer.find(current_wechat_user.customer_id) 77 | end 78 | 79 | # params[:code], @customer, @token 80 | def update_wechat_user_token 81 | @token = 'wx_' + SecureRandom.hex(20) 82 | body = cached_wx_session_key(params[:code]) 83 | sensitive_data = decrypt(body['session_key']) 84 | 85 | wechat_user = find_exist_wechat_user(sensitive_data, body) || WechatUser.new 86 | wechat_user.update_token(body, @customer, @token, params[:code]) 87 | wechat_user.update_info(sensitive_data) 88 | wechat_user 89 | end 90 | 91 | def find_exist_wechat_user(data, body) 92 | if WechatUser.where(union_open_id: data['unionId']).count > 1 93 | WechatUser.find_by(union_open_id: data['unionId'], open_id: body['openid']) 94 | else 95 | WechatUser.find_by(union_open_id: data['unionId']) 96 | end 97 | end 98 | 99 | def token_valid? 100 | return false if @token.blank? 101 | current_wechat_user 102 | end 103 | 104 | def cached_wx_session_key(code) 105 | raise NoAppIdError if code == 'the code is a mock one' 106 | key = "wxcode_#{code}" 107 | sessions = $redis.get(key) 108 | if sessions.blank? 109 | raise DevDomainError if Rails.env.development? 110 | sessions = wx_get_session_key(code) 111 | 112 | $redis.set(key, sessions) 113 | $redis.expire(key, 3600 * 6) 114 | else 115 | Rails.logger.debug "=== session key read from redis: #{sessions}" 116 | end 117 | JSON.parse(sessions) 118 | end 119 | 120 | def wx_get_session_key(code) 121 | uri = URI('https://api.weixin.qq.com/sns/jscode2session') 122 | params = { appid: ENV['weapplet_app_id'], secret: ENV['weapplet_secret'], js_code: code, grant_type: 'authorization_code' } 123 | uri.query = URI.encode_www_form(params) 124 | resp = Net::HTTP.get_response(uri) 125 | if resp.is_a?(Net::HTTPSuccess) && !resp.body['errcode'] 126 | return resp.body 127 | else 128 | raise("wx 请求没有 Success #{resp&.body}") 129 | end 130 | end 131 | 132 | def decrypt(session_key) 133 | app_id = ENV['weapplet_app_id'] 134 | encrypted_data = params[:encrypted][:encryptedData] 135 | iv = params[:encrypted][:iv] 136 | 137 | pc = WXBizDataCrypt.new(app_id, session_key) 138 | pc.decrypt(encrypted_data, iv) 139 | end 140 | end 141 | 142 | class NoAppIdError < StandardError 143 | attr_reader :message 144 | def initialize(message = nil) 145 | @message = message || '由于没有添加AppId,微信的登录无法实现,所以不能登录。' 146 | end 147 | end 148 | 149 | class DevDomainError < StandardError 150 | attr_reader :message 151 | def initialize(message = nil) 152 | @message = message || '本地的 RAPI 没有域名,无法获取向微信获取 session key' 153 | end 154 | end 155 | -------------------------------------------------------------------------------- /app/controllers/wechat/payments_controller.rb: -------------------------------------------------------------------------------- 1 | class Wechat::PaymentsController < ActionController::Base 2 | protect_from_forgery with: :null_session 3 | skip_before_action :verify_authenticity_token 4 | 5 | def notify 6 | data = request.body.read 7 | result = Hash.from_xml(data)['xml'] 8 | if WechatPaymentManager.new.paid?(data) && WxPay::Sign.verify?(result) 9 | back_data = Nokogiri::Slop(data).root 10 | payment = Baye::Payment.find_by(uid: back_data.out_trade_no.content) 11 | unless payment.paid? 12 | payment.wechat_payment.callback_data = data 13 | payment.wechat_payment.transaction_no = data.scan(/\(.*)\<\/transaction_id\>/).flatten.first 14 | payment.wechat_payment.succeeded_at = Time.current 15 | 16 | if payment.pay! 17 | return render :xml => {return_code: "SUCCESS"}.to_xml(root: 'xml', dasherize: false) 18 | else 19 | return render :xml => {return_code: "FAIL", return_msg: "签名失败"}.to_xml(root: 'xml', dasherize: false) 20 | end 21 | Rails.logger.debug "订单#{payment.id}支付完成." 22 | end 23 | end 24 | render nothing: true 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/helpers/base_helper.rb: -------------------------------------------------------------------------------- 1 | module BaseHelper 2 | def current_customer 3 | applet_customer 4 | end 5 | 6 | def current_wechat_user 7 | applet_user(request.headers['Authorization']) 8 | end 9 | 10 | def applet_customer 11 | @applet_customer ||= current_wechat_user&.customer 12 | end 13 | 14 | def applet_user(token) 15 | @applet_user ||= WechatUser.find_by_token_valid(token) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/customer.rb: -------------------------------------------------------------------------------- 1 | class Customer < ApplicationRecord 2 | include Baye::Uid 3 | include Baye::No 4 | include Baye::CustomerConcern 5 | extend Encryptor 6 | 7 | attr_accessor :stretches 8 | @stretches = 10 9 | attr_accessor :pepper 10 | @pepper = nil 11 | 12 | mount_uploader :avatar, CustomerAvatarUploader 13 | 14 | validates :name, presence: true, length: { in: 1..45 } 15 | validates :mobile, uniqueness: true, presence: true, format: { with: /\A1[3-9][0-9]\d{8}\z|BYCE-\w+/} 16 | validates :coin_balance, numericality: { only_integer: true }, allow_blank: true 17 | validates :free_coin_balance, numericality: { only_integer: true }, allow_blank: true 18 | 19 | has_many :inventory_changes, class_name: Baye::CustomerInventoryChange 20 | has_many :inventories, class_name: Baye::CustomerInventory 21 | has_many :orders 22 | has_many :payments, class_name: Baye::Payment 23 | has_many :wechat_users 24 | has_many :coupons, class_name: Baye::Coupon 25 | 26 | before_create :set_customer_account_type, :set_badge_value, :set_nickname, :set_reference_code, :set_agent_value 27 | 28 | def get_available_coupons_from_order_items(product_ids, products_order_quantities) 29 | products = Product.where(id: product_ids) 30 | product_category_ids = products.collect(&:category_id).uniq 31 | product_store_ids = products.collect(&:store_id).uniq 32 | shippment_ids = products.collect(&:shippment_id).uniq 33 | free_express_ids = Baye::Shippment.where(is_pinkage: true).ids 34 | allow_use_express_save_coupon = (product_store_ids.count == 1 && (shippment_ids - free_express_ids).count == 1) 35 | 36 | total_consume = 0 37 | products.zip(products_order_quantities) { |p, qty| total_consume += p.product_price(self) * qty.to_i } 38 | 39 | ac = coupons.joins(:coupon_defination) 40 | ac = ac.joins('LEFT JOIN coupon_defination_categories ON coupon_defination_categories.coupon_defination_id = coupon_definations.id') 41 | .joins('LEFT JOIN coupon_defination_products ON coupon_defination_products.coupon_defination_id = coupon_definations.id') 42 | ac = ac.where('coupon_defination_categories.product_category_id IN (?) AND coupon_definations.filteration_type IN (?)', product_category_ids, 43 | %w(2,4)) 44 | .or(ac.where('coupon_defination_products.product_id IN (?) AND coupon_definations.filteration_type IN (?)', product_ids, %w(1,3))) 45 | 46 | ac = ac.order(id: :desc).where(state: 'unused') 47 | .where('coupon_definations.begin_time < ?', Time.current) 48 | .where('coupon_definations.end_time > ?', Time.current) 49 | .where('coupon_definations.least_cost <= ?', total_consume) 50 | ac.where.not('coupon_definations.coupon_type = ?', Baye::CouponDefination.coupon_types['express_save']) unless allow_use_express_save_coupon 51 | ac.uniq 52 | end 53 | 54 | def valid_password?(password) 55 | klass = Struct.new(:pepper, :stretches).new(pepper, stretches) 56 | Encryptor.compare(klass, self.encrypted_password, password) 57 | end 58 | 59 | def password_digest(password) 60 | klass = Struct.new(:pepper, :stretches).new(pepper, stretches) 61 | Encryptor.digest(klass, password) 62 | end 63 | 64 | def register!(opt = {}) 65 | self.coin_balance = 0 66 | self.mobile = opt[:mobile] 67 | self.name = opt[:name] || ("BY-" + Time.current.strftime('%y%m%d%H%M%S%L')) 68 | self.password = opt[:password] || SecureRandom.hex 69 | save 70 | self 71 | end 72 | 73 | def password=(password) 74 | self.encrypted_password = password_digest(password) 75 | end 76 | 77 | def need_pay_coin_balance(coin_quantity) 78 | total_coin_balance.zero? ? 0 : ((coin_balance.to_i / total_coin_balance) * coin_quantity).to_i 79 | end 80 | 81 | def need_pay_free_coin_balance(coin_quantity) 82 | coin_quantity - need_pay_coin_balance(coin_quantity) 83 | end 84 | 85 | private 86 | 87 | def set_customer_account_type 88 | self.account_type = '潜在巴爷' 89 | end 90 | 91 | def set_badge_value 92 | if self.wine_badge.nil? 93 | self.wine_badge = false 94 | end 95 | if self.tea_badge.nil? 96 | self.tea_badge = false 97 | end 98 | if self.rice_badge.nil? 99 | self.rice_badge = false 100 | end 101 | true 102 | end 103 | 104 | def set_agent_value 105 | if self.agent.nil? 106 | self.agent = false 107 | end 108 | true 109 | end 110 | 111 | def set_nickname 112 | if !self.name.nil? 113 | self.nickname = self.name 114 | end 115 | true 116 | end 117 | 118 | def set_reference_code 119 | if self.reference_code.nil? 120 | o = %w(0 1 2 3 4 5 6 7 8 9 A B C D E F G H J K M N P Q R S T U V W X Y Z) 121 | begin 122 | self.reference_code = (0...4).map { o[rand(o.length)] }.join 123 | end while self.class.exists?(reference_code: reference_code) 124 | end 125 | true 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /app/models/encryptor.rb: -------------------------------------------------------------------------------- 1 | require 'bcrypt' 2 | 3 | module Encryptor 4 | def self.digest(klass, password) 5 | if klass.pepper.present? 6 | password = "#{password}#{klass.pepper}" 7 | end 8 | ::BCrypt::Password.create(password, cost: klass.stretches).to_s 9 | end 10 | 11 | def self.compare(klass, encrypted_password, password) 12 | return false if encrypted_password.blank? 13 | bcrypt = ::BCrypt::Password.new(encrypted_password) 14 | if klass.pepper.present? 15 | password = "#{password}#{klass.pepper}" 16 | end 17 | password = ::BCrypt::Engine.hash_secret(password, bcrypt.salt) 18 | secure_compare(password, encrypted_password) 19 | end 20 | 21 | def self.secure_compare(a, b) 22 | return false if a.blank? || b.blank? || a.bytesize != b.bytesize 23 | l = a.unpack "C#{a.bytesize}" 24 | 25 | res = 0 26 | b.each_byte { |byte| res |= byte ^ l.shift } 27 | res == 0 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/models/https_support.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'net/https' 3 | require 'uri' 4 | 5 | module HttpsSupport 6 | def get_https_client(uri) 7 | http = Net::HTTP.new(uri.host, uri.port) 8 | http.use_ssl = true 9 | if Rails.env.development? 10 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 11 | else 12 | http.verify_mode = OpenSSL::SSL::VERIFY_PEER 13 | end 14 | http.cert_store = OpenSSL::X509::Store.new 15 | http.cert_store.set_default_paths 16 | http 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/models/order.rb: -------------------------------------------------------------------------------- 1 | class Order < ApplicationRecord 2 | attr_accessor :invoice_title, :inner_info, :outer_info 3 | include Baye::Uid 4 | include Baye::No 5 | include Baye::OrderConcern 6 | 7 | validates :customer, presence: true 8 | validates :customer_name, presence: true, if: :require_address? 9 | validates :customer_mobile, presence: true, format: { with: /\A1[3-9][0-9]\d{8}\z/ }, if: :require_address? 10 | validates :province, presence: true, if: :require_address? 11 | validates :city, presence: true, if: :require_address? 12 | validates :county, presence: true, if: :require_address? 13 | validates :detail_address, presence: true, if: :require_address? 14 | validates :type, presence: true 15 | validate :check_order_items 16 | validate :check_inventory, if: -> { category.in?(%w(礼品卡 电子礼品卡)) } 17 | validates :inner_info, length: { maximum: 4 } 18 | validates :outer_info, length: { maximum: 20 } 19 | 20 | visitable :visit, class_name: Baye::Visit 21 | 22 | belongs_to :customer 23 | has_many :order_items, -> { order(:id) }, class_name: Baye::OrderItem 24 | belongs_to :payment, class_name: Baye::Payment, autosave: true 25 | 26 | accepts_nested_attributes_for :order_items, allow_destroy: true 27 | 28 | def final_amount 29 | couponed_amount.present? ? couponed_amount : amount 30 | end 31 | 32 | def distribute_coin_pay_from_order_to_order_item(coin_pay_rate, free_coin_pay_rate) 33 | # distribute coin pay to all order items 34 | order_items.each do |order_item| 35 | order_item.distributed_free_coin_pay_amount = order_item.final_amount * free_coin_pay_rate 36 | 37 | if order_items.last.id == order_item.id 38 | order_item.distributed_coin_pay_amount = distributed_coin_pay_amount - order_items.limit(order_items.count - 1).sum(:distributed_coin_pay_amount) 39 | else 40 | order_item.distributed_coin_pay_amount = order_item.final_amount * coin_pay_rate 41 | end 42 | 43 | order_item.save 44 | end 45 | end 46 | 47 | def free_coin_pay_amount 48 | return 0 if order_items.size.zero? 49 | order_items.inject(0) { |acc, elem| acc + (elem.distributed_free_coin_pay_amount.present? ? elem.distributed_free_coin_pay_amount.to_f : 0) } 50 | end 51 | 52 | private 53 | 54 | def require_address? 55 | return false if category == '定金' 56 | return true if category == '商品' 57 | false 58 | end 59 | 60 | def check_order_items 61 | if self.order_items.blank? && !self.order_id.nil? 62 | self.errors.add(:items_null_error, "请添加商品") 63 | return false 64 | end 65 | end 66 | 67 | def check_inventory 68 | order_items.each do |item| 69 | product = Product.find_by id: item.linked_product_id 70 | next unless item.use_inventory 71 | inventory_product = product.linked_inventory(item.product_id) 72 | total_inventory_quantity = customer.inventories.find_by(product_id: inventory_product.id).quantity 73 | using_inventory_quantity = product.linked_package(item.product_id).capacity * item.quantity 74 | self.errors.add(:not_enough_inventory, "#{inventory_product.name}库存不足") if using_inventory_quantity > total_inventory_quantity 75 | return false 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /app/models/paper_trail/version.rb: -------------------------------------------------------------------------------- 1 | module PaperTrail 2 | class Version < ApplicationRecord 3 | include PaperTrail::VersionConcern 4 | establish_connection :baye_trail_versions 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ApplicationRecord 2 | mount_uploader :promotion_image, Baye::ProductPromotionImageUploader 3 | include Baye::Enable 4 | 5 | validates :sku, presence: true, uniqueness: { scope: :store_id }, length: { minimum: 1, maximum: 191 } 6 | validates :name, presence: true, length: { minimum: 1, maximum: 191 }, if: :is_product_id_blank? 7 | validates :category, presence: true, if: :is_product_id_blank? 8 | # validates :description, presence: true, length: { maximum: 65534 } 9 | validates :price, presence: true, numericality: { greater_than_or_equal_to: 0.00 }, if: :is_product_id_blank? 10 | validates :member_price, presence: true, numericality: { greater_than_or_equal_to: 0.00 }, if: :is_product_id_blank? 11 | validates :baye_price, presence: true, numericality: { greater_than_or_equal_to: 0.00 }, if: :is_product_id_blank? 12 | validates :quantity, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 1 }, if: :is_product_id_blank? 13 | # validates :coin_limited, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 } 14 | 15 | validates :unit, presence: true, if: :is_product_id_blank? 16 | validates :weight, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, if: :is_product_id_blank? 17 | validates :stock, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, if: :is_product_id_blank? 18 | 19 | validates :store_id, :shippment_id, presence: true, if: -> { product_id.blank? } 20 | 21 | 22 | has_many :images, class_name: Baye::ProductImage, dependent: :destroy 23 | has_many :relation_products, foreign_key: :relation_product_id, class_name: Product 24 | has_one :description_of_detail, -> { where key: 'detail' }, class_name: Baye::ProductDescription 25 | belongs_to :category, class_name: Baye::ProductCategory 26 | belongs_to :shippment, class_name: Baye::Shippment, optional: false 27 | 28 | scope :on_sale, -> { where('(? between sale_time and removed_time) or (sale_time is null and removed_time> ?) or (removed_time is null and sale_time< ?) or (sale_time is null and removed_time is null)', Time.current, Time.current, Time.current) } 29 | scope :common, -> { where('type is null') } 30 | scope :is_search, -> { where(is_search: true) } 31 | scope :asset, -> { where(asset: true) } 32 | scope :subscription_goods, -> { where(unit: '订金') } 33 | 34 | scope :popularity_products, -> { where(flag: 1) } # 火爆 35 | scope :new_products, -> { where(flag: 3) } # 新品 36 | scope :hot_products, -> { where(flag: 4) } # 最热 37 | scope :promotions, -> { where(flag: 5) } # 促销 38 | 39 | enum commission_type: { cash: 0, rate: 1 } 40 | 41 | def image_url 42 | images.first.file.url if images.present? 43 | end 44 | 45 | def is_gift_card? 46 | category.try(:name) == '礼品卡' 47 | end 48 | 49 | # product_price is different in baye-core 50 | def product_price(customer) 51 | return self.member_price if customer.nil? || customer.account_type == '游客' || customer.account_type == '潜在巴爷' 52 | return self.baye_price if customer.account_type == '巴爷' 53 | end 54 | 55 | def promotion_url 56 | promotion_image.try(:file).try(:url) || '' 57 | end 58 | 59 | def is_product_id_blank? 60 | self.product_id.blank? 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /app/models/profile.rb: -------------------------------------------------------------------------------- 1 | class Profile < ActiveModelSerializers::Model 2 | attr_accessor :id, :sku, :name, :image, :inventory, :asset_count 3 | end 4 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | end 3 | -------------------------------------------------------------------------------- /app/models/verification.rb: -------------------------------------------------------------------------------- 1 | class Verification < ApplicationRecord 2 | validates :mobile, presence: true, format: { with: /\A1[3-9][0-9]\d{8}\z/ } 3 | validates :expire_at, presence: true 4 | validates :code, presence: true 5 | 6 | def self.send_message!(mobile) 7 | verification = Verification.new(mobile: mobile, code: generate_code, expire_at: Time.current + 10.minutes) 8 | verification.save 9 | 10 | VerifySmsSendWorker.perform_async(verification.id) 11 | end 12 | 13 | def self.can_send?(mobile) 14 | #一个小时之内发送的频率为8 15 | created_at_lq = Time.current - 1.hour 16 | count = Verification.where(mobile: mobile).where('created_at > ? and created_at < ?', created_at_lq, Time.current).count 17 | return false if count > 8 18 | true 19 | end 20 | 21 | def self.validate?(mobile, code) 22 | return Verification.where('expire_at > :now', now: Time.current).where(mobile: mobile, code: code).exists? 23 | end 24 | 25 | private 26 | 27 | def self.generate_code 28 | rand(9).to_s + rand(9).to_s + rand(9).to_s + rand(9).to_s 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/models/wechat_payment_manager.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'json' 3 | require 'openssl' 4 | require 'digest/sha1' 5 | require 'digest/md5' 6 | require 'erb' 7 | 8 | class WechatPaymentManager 9 | include HttpsSupport 10 | 11 | def register_for_app(payment) 12 | uri = URI("https://api.mch.weixin.qq.com/pay/unifiedorder") 13 | http = get_https_client(uri) 14 | request = Net::HTTP::Post.new(uri, initheader = { 'Content-Type' => 'application/xml' }) 15 | request.body = build_data_for_app(payment) 16 | Rails.logger.debug request.body 17 | res = http.request(request) 18 | Rails.logger.debug res.body 19 | Nokogiri::Slop(res.body).root if res.is_a?(Net::HTTPSuccess) 20 | end 21 | 22 | def register_for_jsapi(payment, wechat_user) 23 | uri = URI("https://api.mch.weixin.qq.com/pay/unifiedorder") 24 | http = get_https_client(uri) 25 | request = Net::HTTP::Post.new(uri, initheader = { 'Content-Type' => 'application/xml' }) 26 | request.body = build_data_for_jsapi(payment, wechat_user) 27 | Rails.logger.debug request.body 28 | res = http.request(request) 29 | Rails.logger.debug res.body 30 | Nokogiri::Slop(res.body).root if res.is_a?(Net::HTTPSuccess) 31 | end 32 | 33 | def build_data_for_app(payment) 34 | template =<<-EOF 35 | 36 | %{appid} 37 | %{mch_id} 38 | %{nonce_str} 39 | %{sign} 40 | %{body} 41 | %{out_trade_no} 42 | %{total_fee} 43 | %{spbill_create_ip} 44 | %{notify_url} 45 | %{trade_type} 46 | %{time_start} 47 | %{time_expire} 48 | 49 | EOF 50 | 51 | hash = { 52 | appid: ENV['weapplet_app_id'], 53 | mch_id: ENV['WECHAT_STORE_ID'], 54 | nonce_str: SecureRandom.hex(10), 55 | body: get_payment_body(payment), 56 | out_trade_no: payment.uid, 57 | total_fee: (payment.wx_pay_amount * 100).to_i, 58 | spbill_create_ip: payment.ip, 59 | notify_url: "#{ENV['server_url']}/wechat/payments/#{payment.uid}/notify", 60 | trade_type: 'APP', 61 | time_start: Time.current.strftime('%Y%m%d%H%M%S'), 62 | time_expire: (Time.current + 1.day).strftime('%Y%m%d%H%M%S'), 63 | } 64 | 65 | hash[:sign] = build_signature(hash) 66 | 67 | template.gsub(/\s|\n/, '') % hash 68 | end 69 | 70 | def build_data_for_jsapi(payment, wechat_user) 71 | template =<<-EOF 72 | 73 | %{appid} 74 | %{mch_id} 75 | %{nonce_str} 76 | %{sign} 77 | %{body} 78 | %{out_trade_no} 79 | %{total_fee} 80 | %{spbill_create_ip} 81 | %{notify_url} 82 | %{trade_type} 83 | %{openid} 84 | %{time_start} 85 | %{time_expire} 86 | 87 | EOF 88 | 89 | hash = { 90 | appid: ENV['weapplet_app_id'], 91 | mch_id: ENV['WECHAT_STORE_ID'], 92 | nonce_str: SecureRandom.hex(10), 93 | body: get_payment_body(payment), 94 | out_trade_no: payment.uid, 95 | total_fee: (payment.wx_pay_amount * 100).to_i, 96 | spbill_create_ip: payment.ip, 97 | notify_url: "#{ENV['server_url']}/wechat/payments/#{payment.uid}/notify", 98 | trade_type: 'JSAPI', 99 | time_start: Time.current.strftime('%Y%m%d%H%M%S'), 100 | time_expire: (Time.current + 1.day).strftime('%Y%m%d%H%M%S'), 101 | openid: wechat_user.open_id 102 | } 103 | 104 | hash[:sign] = build_signature(hash) 105 | 106 | template.gsub(/\s|\n/, '') % hash 107 | end 108 | 109 | def build_hash_for_jsapi_call(prepay_id) 110 | hash = {} 111 | hash[:timeStamp] = Time.current.to_i.to_s 112 | hash[:nonceStr] = SecureRandom.hex(10) 113 | hash[:package] = "prepay_id=#{prepay_id}" 114 | hash[:signType] = "MD5" 115 | hash[:appId] = ENV['weapplet_app_id'] 116 | hash 117 | end 118 | 119 | def build_hash_for_app_call(prepay_id) 120 | hash = {} 121 | hash[:timestamp] = Time.current.to_i.to_s 122 | hash[:noncestr] = SecureRandom.hex(10) 123 | hash[:prepayid] = "#{prepay_id}" 124 | hash[:appid] = ENV['weapplet_app_id'] 125 | hash[:partnerid] = ENV['WECHAT_STORE_ID'] 126 | hash[:package] = 'Sign=WXPay' 127 | hash 128 | end 129 | 130 | def paid?(data) 131 | back_data = Nokogiri::Slop(data).root 132 | if back_data.result_code.content == 'SUCCESS' 133 | hash = {} 134 | back_data.elements.each do |element| 135 | hash[element.name] = element.content 136 | end 137 | hash.delete('sign') 138 | # Rails.logger.debug "计算出签名:#{build_signature(hash)},CALLBACK签名#{back_data.sign.content}" 139 | return true if build_signature(hash) == back_data.sign.content 140 | end 141 | return false 142 | end 143 | 144 | def build_signature(hash) 145 | origin = hash.sort.to_h.map { |k, v| "#{k}=#{v}" }.join('&') 146 | temp = "#{origin}&key=#{ENV['WECHAT_STORE_KEY']}" 147 | Rails.logger.debug temp 148 | Digest::MD5.hexdigest(temp).upcase 149 | end 150 | 151 | def get_payment_body(payment) 152 | return '巴爷科技商品订单' unless payment.orders.nil? 153 | return 'UNKNOWN' 154 | end 155 | end 156 | -------------------------------------------------------------------------------- /app/models/wechat_user.rb: -------------------------------------------------------------------------------- 1 | class WechatUser < ApplicationRecord 2 | belongs_to :customer 3 | validates :open_id, presence: true, uniqueness: true 4 | 5 | def self.find_by_token_valid(token) 6 | WechatUser.where(client_token: token).where("expired_at > ?", Time.now).first 7 | end 8 | 9 | def subscribe 10 | self.subscribed_at = Time.current 11 | self.unsubscribed_at = nil 12 | save 13 | end 14 | 15 | def unsubscribe 16 | self.unsubscribed_at = Time.current 17 | save 18 | end 19 | 20 | def subscribed? 21 | if unsubscribed_at.nil? && subscribed_at.present? 22 | true 23 | else 24 | false 25 | end 26 | end 27 | 28 | def bind_customer!(customer) 29 | self.customer = customer 30 | save 31 | end 32 | 33 | def expire! 34 | self.expired_at = Time.now - 1 35 | save 36 | end 37 | 38 | def update_token(body, customer, token, wx_code) 39 | self.session_key = body['session_key'] 40 | self.customer_id = customer&.id 41 | self.expired_at = 7.days.from_now 42 | self.client_token = token 43 | self.wx_code = wx_code 44 | self.app_id = ENV['weapplet_app_id'] 45 | self.open_id = body['openid'] 46 | save! 47 | end 48 | 49 | def update_info(user_info) 50 | self.nickname = user_info['nickName'] 51 | self.gender = user_info['gender'] 52 | self.country = user_info['country'] 53 | self.city = user_info['city'] 54 | self.province = user_info['province'] 55 | self.avatar = user_info['avatarUrl'] 56 | self.union_open_id = user_info['unionId'] 57 | save 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /app/serializers/baye/coupon_serializer.rb: -------------------------------------------------------------------------------- 1 | module Baye 2 | class CouponSerializer < ActiveModel::Serializer 3 | attributes :code, :state, :uid, :coupon_value 4 | attributes :title, :sub_title, :product_category_title, :end_time, :left_image 5 | 6 | def title 7 | object.coupon_defination.title 8 | end 9 | 10 | def sub_title 11 | object.coupon_defination.sub_title 12 | end 13 | 14 | def product_category_title 15 | object.coupon_defination.product_category_title 16 | end 17 | 18 | def end_time 19 | object.coupon_defination.end_time.strftime('%y-%m-%d') 20 | end 21 | 22 | def left_image 23 | if object.overdue? 24 | nil 25 | else 26 | object.coupon_defination.left_side_image.url 27 | end 28 | end 29 | end 30 | end -------------------------------------------------------------------------------- /app/serializers/baye/customer_asset_serializer.rb: -------------------------------------------------------------------------------- 1 | module Baye 2 | class CustomerAssetSerializer < ActiveModel::Serializer 3 | attributes :no, :product_id, :product_icon, :sub_products, :inventory_changes 4 | belongs_to :product 5 | 6 | def no 7 | Baye::CustomerAsset.where(customer_id: object.customer_id, product_id: object.product_id).map{|asset| asset.no}.join(",") 8 | end 9 | 10 | def product_icon 11 | ENV['server_url'] + "/images/#{object.product_id}_asset_info.png" 12 | end 13 | 14 | def sub_products 15 | arr = [] 16 | 17 | object.product.relation_products.each do |sub_product| 18 | arr << { name: sub_product.name, stock: object.customer.inventory_changes.find_by(product_id: sub_product).quantity / 1000.0 } 19 | end 20 | 21 | arr 22 | end 23 | 24 | def inventory_changes 25 | sub_product_ids = object.product.relation_products.map(&:id) 26 | result = object.customer.inventory_changes.where(product_id: sub_product_ids).order('created_at desc').group_by do |ic| 27 | ic.created_at.year 28 | end 29 | result 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/serializers/baye/slide_serializer.rb: -------------------------------------------------------------------------------- 1 | module Baye 2 | class SlideSerializer < ActiveModel::Serializer 3 | attributes :img, :url 4 | 5 | def img 6 | object.image.url 7 | end 8 | 9 | def url 10 | object.product_id ? "/pages/show_product/show_product?id=#{object.product_id}" : '' 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/serializers/district_serializer.rb: -------------------------------------------------------------------------------- 1 | class DistrictSerializer < ActiveModel::Serializer 2 | 3 | end -------------------------------------------------------------------------------- /app/serializers/product_serializer.rb: -------------------------------------------------------------------------------- 1 | class ProductSerializer < ActiveModel::Serializer 2 | attributes :id, :name, :sku, :image_url, :uid, :unit, :promotion_url, :category_id 3 | 4 | attribute :flag do 5 | case object.flag 6 | when 1 7 | '火爆' 8 | when 3 9 | '新品' 10 | when 4 11 | '最热' 12 | when 5 13 | '促销' 14 | end 15 | end 16 | 17 | attribute :desc do 18 | html = object.description_of_detail.content 19 | html.scan(//).map do |img| 20 | next unless img =~ /alt="Image"/ 21 | width = img.match(/width="(\d+)"/)[1] 22 | height = img.match(/height="(\d+)"/)[1] 23 | src = img.match(/src="(.+?)"/)[1] 24 | [src, width, height] 25 | end 26 | end 27 | 28 | attribute :baye_price do 29 | object.baye_price 30 | end 31 | 32 | attribute :member_price do 33 | object.member_price 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/serializers/profile_serializer.rb: -------------------------------------------------------------------------------- 1 | class ProfileSerializer < ActiveModel::Serializer 2 | attributes :id, :sku, :name, :image, :inventory, :asset_count 3 | end -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/workers/verify_sms_send_worker.rb: -------------------------------------------------------------------------------- 1 | class VerifySmsSendWorker 2 | include Sidekiq::Worker 3 | 4 | def perform(verification_id) 5 | Rails.logger.debug "开始发送 #{verification_id} 验证码 at #{Time.current}" 6 | item = Verification.find(verification_id) 7 | 8 | content = "您的验证码是【#{item.code}】。" 9 | SmsNotifyWorker.perform_async(item.mobile, content, 'UserRegistration') 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | # puts "\n== Copying sample files ==" 22 | # unless File.exist?('config/database.yml') 23 | # cp 'config/database.yml.sample', 'config/database.yml' 24 | # end 25 | 26 | puts "\n== Preparing database ==" 27 | system! 'bin/rails db:setup' 28 | 29 | puts "\n== Removing old logs and tempfiles ==" 30 | system! 'bin/rails log:clear tmp:clear' 31 | 32 | puts "\n== Restarting application server ==" 33 | system! 'bin/rails restart' 34 | end 35 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | if spring = lockfile.specs.detect { |spec| spec.name == "spring" } 12 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 13 | gem 'spring', spring.version 14 | require 'spring/binstub' 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | puts "\n== Updating database ==" 22 | system! 'bin/rails db:migrate' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! 'bin/rails log:clear tmp:clear' 26 | 27 | puts "\n== Restarting application server ==" 28 | system! 'bin/rails restart' 29 | end 30 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | require "active_job/railtie" 7 | require "active_record/railtie" 8 | require "action_controller/railtie" 9 | require "action_mailer/railtie" 10 | require "action_view/railtie" 11 | require "action_cable/engine" 12 | # require "sprockets/railtie" 13 | require "rails/test_unit/railtie" 14 | 15 | # Require the gems listed in Gemfile, including any gems 16 | # you've limited to :test, :development, or :production. 17 | Bundler.require(*Rails.groups) 18 | 19 | module LandlordRapi 20 | class Application < Rails::Application 21 | # Rails 5 22 | config.middleware.insert_before 0, Rack::Cors do 23 | allow do 24 | origins ['http://localhost:3000', 'https://swagger-ui.bayekeji.com'] 25 | resource '*', headers: :any, methods: [:get, :post, :options] 26 | end 27 | end 28 | 29 | # Settings in config/environments/* take precedence over those specified here. 30 | # Application configuration should go into files in config/initializers 31 | # -- all .rb files in that directory are automatically loaded. 32 | config.time_zone = 'Beijing' 33 | config.active_record.default_timezone = :local 34 | 35 | # Only loads a smaller set of middleware suitable for API only apps. 36 | # Middleware like session, flash, cookies can be added back manually. 37 | # Skip views, helpers and assets when generating a new resource. 38 | config.api_only = true 39 | config.i18n.default_locale = 'zh-CN' 40 | config.i18n.available_locales = ['zh-CN', 'en'] 41 | config.i18n.fallbacks = true 42 | end 43 | end 44 | 45 | I18n.config.enforce_available_locales = false 46 | I18n.locale = 'zh-CN' 47 | -------------------------------------------------------------------------------- /config/application.yml.sample: -------------------------------------------------------------------------------- 1 | # Add configuration values here, as shown below. 2 | # 3 | # pusher_app_id: "2954" 4 | # pusher_key: 7381a978f7dd7f9a1117 5 | # pusher_secret: abdc3b896a0ffb85d373 6 | # stripe_api_key: sk_test_2J0l093xOyW72XUYJHE4Dv2r 7 | # stripe_publishable_key: pk_test_ro9jV5SNwGb1yYlQfzG17LHK 8 | # 9 | # production: 10 | # stripe_api_key: sk_live_EeHnL644i6zo4Iyq4v1KdV9H 11 | # stripe_publishable_key: pk_live_9lcthxpSIHbGwmdO941O1XVU 12 | 13 | ALIYUN_ACCESS_ID: '' 14 | ALIYUN_ACCESS_KEY: '' 15 | ALIYUN_BUCKET: '' 16 | ALIYUN_OSS_HOST: '' 17 | 18 | weapplet_app_id: '' 19 | weapplet_secret: '' 20 | WECHAT_STORE_ID: '' 21 | WECHAT_STORE_KEY: '' 22 | 23 | 24 | development: 25 | server_url: http://localhost:3000/ 26 | 27 | staging: 28 | server_url: http://rapi-staging.bayekeji.com 29 | 30 | production: 31 | server_url: http://rapi.bayekeji.com 32 | 33 | test: 34 | server_url: http://localhost:3000/ 35 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://localhost:6379/1 10 | -------------------------------------------------------------------------------- /config/database.yml.sample: -------------------------------------------------------------------------------- 1 | # MySQL. Versions 5.0 and up are supported. 2 | # 3 | # Install the MySQL driver 4 | # gem install mysql2 5 | # 6 | # Ensure the MySQL gem is defined in your Gemfile 7 | # gem 'mysql2' 8 | # 9 | # And be sure to use new-style password hashing: 10 | # http://dev.mysql.com/doc/refman/5.7/en/old-client.html 11 | # 12 | default: &default 13 | adapter: mysql2 14 | encoding: utf8mb4 15 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 16 | username: root 17 | password: 18 | host: 127.0.0.1 19 | 20 | development: 21 | <<: *default 22 | database: baye_backend_dev 23 | 24 | # Warning: The database defined as "test" will be erased and 25 | # re-generated from your development database when you run "rake". 26 | # Do not set this db to the same as development or production. 27 | test: 28 | <<: *default 29 | database: baye_backend_test 30 | 31 | # As with config/secrets.yml, you never want to store sensitive information, 32 | # like your database password, in your source code. If your source code is 33 | # ever seen by anyone, they now have access to your database. 34 | # 35 | # Instead, provide the password as a unix environment variable when you boot 36 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database 37 | # for a full rundown on how to provide these environment variables in a 38 | # production deployment. 39 | # 40 | # On Heroku and other platform providers, you may have a full connection URL 41 | # available as an environment variable. For example: 42 | # 43 | # DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase" 44 | # 45 | # You can use this database configuration with: 46 | # 47 | # production: 48 | # url: <%= ENV['DATABASE_URL'] %> 49 | # 50 | production: 51 | <<: *default 52 | database: baye_backend_dev 53 | password: <%= ENV['LANDLORD_RAPI_DATABASE_PASSWORD'] %> 54 | -------------------------------------------------------------------------------- /config/deploy.rb: -------------------------------------------------------------------------------- 1 | # config valid only for current version of Capistrano 2 | lock '~> 3.10.0' 3 | 4 | set :application, 'rapi' 5 | set :repo_url, 'git@github.com:bayetech/wechat_mall_applet_backend.git' 6 | 7 | # puma 8 | set :puma_role, :app 9 | set :puma_init_active_record, true 10 | set :puma_config_file, 'config/puma-web.rb' 11 | # Default branch is :master 12 | # ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp 13 | 14 | # Default deploy_to directory is /var/www/my_app_name 15 | set :deploy_to, '/data/www/rapi' 16 | 17 | # Default value for :scm is :git 18 | # set :scm, :git 19 | 20 | # Default value for :format is :airbrussh. 21 | # set :format, :airbrussh 22 | 23 | # You can configure the Airbrussh format using :format_options. 24 | # These are the defaults. 25 | # set :format_options, command_output: true, log_file: 'log/capistrano.log', color: :auto, truncate: :auto 26 | 27 | # Default value for :pty is false 28 | # set :pty, true 29 | 30 | # Default value for :linked_files is [] 31 | append :linked_files, 'config/database.yml', 'config/secrets.yml', 'config/redis.yml', 32 | 'config/application.yml', 'config/sidekiq.yml', 'config/wechat.yml' 33 | 34 | # Default value for linked_dirs is [] 35 | append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/system', 'public/swagger_doc', 'public/page_qr_codes' 36 | 37 | # Default value for default_env is {} 38 | # set :default_env, { path: "/opt/ruby/bin:$PATH" } 39 | 40 | # Default value for keep_releases is 5 41 | # set :keep_releases, 5 42 | -------------------------------------------------------------------------------- /config/deploy/production.rb: -------------------------------------------------------------------------------- 1 | # server-based syntax 2 | # ====================== 3 | # Defines a single server with a list of roles and multiple properties. 4 | # You can define all roles on a single server, or split them: 5 | 6 | # server 'example.com', user: 'deploy', roles: %w{app db web}, my_property: :my_value 7 | server 'only.bayekeji.com', user: 'rapi', roles: %w(app web) 8 | # server 'db.example.com', user: 'deploy', roles: %w{db} 9 | 10 | 11 | 12 | # role-based syntax 13 | # ================== 14 | 15 | # Defines a role with one or multiple servers. The primary server in each 16 | # group is considered to be the first unless any hosts have the primary 17 | # property set. Specify the username and a domain or IP for the server. 18 | # Don't use `:all`, it's a meta role. 19 | 20 | # role :app, %w{deploy@example.com}, my_property: :my_value 21 | # role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value 22 | # role :db, %w{deploy@example.com} 23 | 24 | 25 | 26 | # Configuration 27 | # ============= 28 | # You can set any configuration variable like in config/deploy.rb 29 | # These variables are then only loaded and set in this stage. 30 | # For available Capistrano configuration variables see the documentation page. 31 | # http://capistranorb.com/documentation/getting-started/configuration/ 32 | # Feel free to add new variables to customise your setup. 33 | set :branch, 'master' 34 | set :rails_env, 'production' 35 | 36 | # Custom SSH Options 37 | # ================== 38 | # You may pass any option but keep in mind that net/ssh understands a 39 | # limited set of options, consult the Net::SSH documentation. 40 | # http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start 41 | # 42 | # Global options 43 | # -------------- 44 | # set :ssh_options, { 45 | # keys: %w(/home/rlisowski/.ssh/id_rsa), 46 | # forward_agent: false, 47 | # auth_methods: %w(password) 48 | # } 49 | # 50 | # The server-based syntax can be used to override options: 51 | # ------------------------------------ 52 | # server 'example.com', 53 | # user: 'user_name', 54 | # roles: %w{web app}, 55 | # ssh_options: { 56 | # user: 'user_name', # overrides user setting above 57 | # keys: %w(/home/user_name/.ssh/id_rsa), 58 | # forward_agent: false, 59 | # auth_methods: %w(publickey password) 60 | # # password: 'please use keys' 61 | # } 62 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | if Rails.root.join('tmp/caching-dev.txt').exist? 17 | config.action_controller.perform_caching = true 18 | 19 | config.cache_store = :memory_store 20 | config.public_file_server.headers = { 21 | 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" 22 | } 23 | else 24 | config.action_controller.perform_caching = false 25 | 26 | config.cache_store = :null_store 27 | end 28 | 29 | # Don't care if the mailer can't send. 30 | config.action_mailer.raise_delivery_errors = false 31 | 32 | config.action_mailer.perform_caching = false 33 | 34 | # Print deprecation notices to the Rails logger. 35 | config.active_support.deprecation = :log 36 | 37 | # Raise an error on page load if there are pending migrations. 38 | config.active_record.migration_error = :page_load 39 | 40 | 41 | # Raises error for missing translations 42 | # config.action_view.raise_on_missing_translations = true 43 | 44 | # Use an evented file watcher to asynchronously detect changes in source code, 45 | # routes, locales, etc. This feature depends on the listen gem. 46 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 47 | end 48 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = true 15 | config.action_controller.perform_caching = true 16 | 17 | # Disable serving static files from the `/public` folder by default since 18 | # Apache or NGINX already handles this. 19 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 20 | 21 | 22 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 23 | # config.action_controller.asset_host = 'http://assets.example.com' 24 | 25 | # Specifies the header that your server uses for sending files. 26 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 27 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 28 | 29 | # Mount Action Cable outside main process or domain 30 | # config.action_cable.mount_path = nil 31 | # config.action_cable.url = 'wss://example.com/cable' 32 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 33 | 34 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 35 | # config.force_ssl = true 36 | 37 | # Use the lowest log level to ensure availability of diagnostic information 38 | # when problems arise. 39 | config.log_level = :debug 40 | 41 | # Prepend all log lines with the following tags. 42 | config.log_tags = [ :request_id ] 43 | 44 | # Use a different cache store in production. 45 | # config.cache_store = :mem_cache_store 46 | 47 | # Use a real queuing backend for Active Job (and separate queues per environment) 48 | # config.active_job.queue_adapter = :resque 49 | # config.active_job.queue_name_prefix = "landlord_rapi_#{Rails.env}" 50 | 51 | # Ignore bad email addresses and do not raise email delivery errors. 52 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 53 | config.action_mailer.raise_delivery_errors = true 54 | config.action_mailer.perform_deliveries = true 55 | config.action_mailer.delivery_method = :smtp 56 | config.action_mailer.smtp_settings = { 57 | address: '127.0.0.1', 58 | port: 25, 59 | enable_starttls_auto: false, 60 | } 61 | 62 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 63 | # the I18n.default_locale when a translation cannot be found). 64 | config.i18n.fallbacks = true 65 | 66 | # Send deprecation notices to registered listeners. 67 | config.active_support.deprecation = :notify 68 | 69 | # Use default logging formatter so that PID and timestamp are not suppressed. 70 | config.log_formatter = ::Logger::Formatter.new 71 | 72 | # Use a different logger for distributed setups. 73 | # require 'syslog/logger' 74 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 75 | 76 | if ENV["RAILS_LOG_TO_STDOUT"].present? 77 | logger = ActiveSupport::Logger.new(STDOUT) 78 | logger.formatter = config.log_formatter 79 | config.logger = ActiveSupport::TaggedLogging.new(logger) 80 | end 81 | 82 | # Do not dump schema after migrations. 83 | config.active_record.dump_schema_after_migration = false 84 | end 85 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | config.action_mailer.perform_caching = false 31 | 32 | # Tell Action Mailer not to deliver emails to the real world. 33 | # The :test delivery method accumulates sent emails in the 34 | # ActionMailer::Base.deliveries array. 35 | config.action_mailer.delivery_method = :test 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /config/initializers/active_model_serializers.rb: -------------------------------------------------------------------------------- 1 | ActiveModelSerializers.config.adapter = :json_api 2 | -------------------------------------------------------------------------------- /config/initializers/ahoy.rb: -------------------------------------------------------------------------------- 1 | module Ahoy 2 | self.protect_from_forgery = false 3 | class Store < Ahoy::Stores::ActiveRecordTokenStore 4 | def visit_model 5 | Baye::Visit 6 | end 7 | 8 | def event_model 9 | Baye::Event 10 | end 11 | 12 | def user 13 | controller.current_customer 14 | end 15 | 16 | def track_visit(options, &block) 17 | super do |visit| 18 | visit.platform = 'rapi' 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ApplicationController.renderer.defaults.merge!( 4 | # http_host: 'example.org', 5 | # https: false 6 | # ) 7 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/carrierwave.rb: -------------------------------------------------------------------------------- 1 | CarrierWave.configure do |config| 2 | config.storage = (Rails.env.test? ? :file : :aliyun) 3 | config.enable_processing = false if Rails.env.test? 4 | config.aliyun_access_id = ENV['ALIYUN_ACCESS_ID'] 5 | config.aliyun_access_key = ENV['ALIYUN_ACCESS_KEY'] 6 | # 你需要在 Aliyum OSS 上面提前创建一个 Bucket 7 | config.aliyun_bucket = ENV['ALIYUN_BUCKET'] || 'baye-media' 8 | # 是否使用内部连接,true - 使用 Aliyun 局域网的方式访问 false - 外部网络访问 9 | config.aliyun_internal = false 10 | # 配置存储的地区数据中心,默认: cn-hangzhou 11 | config.aliyun_area = 'cn-shanghai' 12 | end 13 | -------------------------------------------------------------------------------- /config/initializers/cl2009.rb: -------------------------------------------------------------------------------- 1 | require Rails.root.join("config", 'initializers', 'redis.rb') 2 | 3 | #cl2009 4 | Cl2009.setup do |config| 5 | config.server = 'http://222.73.117.156' 6 | config.account = ENV['CL_ACCOUNT'] 7 | config.password = ENV['CL_PASSWORD'] 8 | config.redis = $redis 9 | end 10 | -------------------------------------------------------------------------------- /config/initializers/exception_notification.rb: -------------------------------------------------------------------------------- 1 | if Rails.env.production? || Rails.env.staging? 2 | require 'exception_notification/rails' 3 | require 'exception_notification/sidekiq' 4 | 5 | ExceptionNotification.configure do |config| 6 | # Ignore additional exception types. 7 | # ActiveRecord::RecordNotFound, AbstractController::ActionNotFound and ActionController::RoutingError are already added. 8 | # config.ignored_exceptions += %w{ActionView::TemplateError CustomError} 9 | 10 | # Adds a condition to decide when an exception must be ignored or not. 11 | # The ignore_if method can be invoked multiple times to add extra conditions. 12 | # config.ignore_if do |exception, options| 13 | # not Rails.env.production? 14 | # end 15 | 16 | # Notifiers ================================================================= 17 | 18 | # Email notifier sends notifications by email. 19 | config.add_notifier :email, 20 | email_prefix: "[RAPI #{Rails.env}]", 21 | sender_address: %("Rails Exception Notifier" ), 22 | exception_recipients: %w(guochunzhong@bayekeji.com) 23 | 24 | # Campfire notifier sends notifications to your Campfire room. Requires 'tinder' gem. 25 | # config.add_notifier :campfire, { 26 | # :subdomain => 'my_subdomain', 27 | # :token => 'my_token', 28 | # :room_name => 'my_room' 29 | # } 30 | 31 | # HipChat notifier sends notifications to your HipChat room. Requires 'hipchat' gem. 32 | # config.add_notifier :hipchat, { 33 | # :api_token => 'my_token', 34 | # :room_name => 'my_room' 35 | # } 36 | 37 | # Webhook notifier sends notifications over HTTP protocol. Requires 'httparty' gem. 38 | # config.add_notifier :webhook, { 39 | # :url => 'http://example.com:5555/hubot/path', 40 | # :http_method => :post 41 | # } 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/new_framework_defaults.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains migration options to ease your Rails 5.0 upgrade. 4 | # 5 | # Read the Guide for Upgrading Ruby on Rails for more info on each option. 6 | 7 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. 8 | # Previous versions had false. 9 | ActiveSupport.to_time_preserves_timezone = true 10 | 11 | # Require `belongs_to` associations by default. Previous versions had false. 12 | Rails.application.config.active_record.belongs_to_required_by_default = true 13 | 14 | # Do not halt callback chains when a callback returns false. Previous versions had true. 15 | ActiveSupport.halt_callback_chains_on_return_false = false 16 | 17 | # Configure SSL options to enable HSTS with subdomains. Previous versions had false. 18 | Rails.application.config.ssl_options = { hsts: { subdomains: true } } 19 | -------------------------------------------------------------------------------- /config/initializers/paper_trail.rb: -------------------------------------------------------------------------------- 1 | PaperTrail.config.track_associations = false 2 | -------------------------------------------------------------------------------- /config/initializers/redis.rb: -------------------------------------------------------------------------------- 1 | require 'redis' 2 | 3 | #redis 4 | redis_config_in_file = YAML.load(File.open(Rails.root.join("config/redis.yml"))).symbolize_keys 5 | redis_default_config = redis_config_in_file[:default].symbolize_keys 6 | $redis_config = redis_default_config.merge(redis_config_in_file[Rails.env.to_sym].symbolize_keys) if redis_config_in_file[Rails.env.to_sym] 7 | 8 | $redis = Redis.new($redis_config) 9 | 10 | # To clear out the db before each test 11 | $redis.flushdb if Rails.env.test? 12 | -------------------------------------------------------------------------------- /config/initializers/sidekiq.rb: -------------------------------------------------------------------------------- 1 | require 'sidekiq' 2 | require Rails.root.join("config", 'initializers', 'redis.rb') 3 | 4 | Sidekiq.configure_server do |config| 5 | #config.redis = ConnectionPool.new(size: 5, &redis_conn) 6 | config.redis = $redis_config 7 | 8 | config.error_handlers << Proc.new { |ex, ctx_hash| SidekiqErrorMailer.send_email(ex, ctx_hash).deliver_now } 9 | end 10 | 11 | Sidekiq.configure_client do |config| 12 | #config.redis = ConnectionPool.new(size: 1, &redis_conn) 13 | config.redis = $redis_config 14 | end 15 | 16 | -------------------------------------------------------------------------------- /config/initializers/swagger_docs.rb: -------------------------------------------------------------------------------- 1 | module Swagger 2 | module Docs 3 | class Config 4 | def self.base_api_controller 5 | ActionController::API 6 | end 7 | end 8 | Config.register_apis('1.0' => 9 | { 10 | # the extension used for the API 11 | :api_extension_type => :json, 12 | # the output location where your .json files are written to 13 | :api_file_path => 'public/swagger_doc', 14 | # Ability to setup base controller for each api version. Api::V1::SomeController for example. 15 | :parent_controller => ActionController::API, 16 | # add custom attributes to api-docs 17 | :attributes => { 18 | :info => { 19 | 'title' => 'Bayetech Rails API App', 20 | 'description' => 'A grape replacement backend for iOS, Android and Wechat applet.', 21 | 'termsOfServiceUrl' => 'http://www.bayekeji.com/contact', 22 | 'contact' => 'admin@bayekeji.com' 23 | } 24 | } 25 | }) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/initializers/wx_pay.rb: -------------------------------------------------------------------------------- 1 | WxPay.appid = ENV['weapplet_app_id'] 2 | WxPay.key = ENV['WECHAT_STORE_KEY'] 3 | WxPay.mch_id = ENV['WECHAT_STORE_ID'] 4 | WxPay.debug_mode = false # default is `true` -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /config/locales/zh-CN.yml: -------------------------------------------------------------------------------- 1 | "zh-CN": 2 | activerecord: 3 | models: 4 | product: "商品" 5 | attributes: 6 | product: 7 | name: "名字" 8 | stock: "库存" 9 | order: 10 | city: "城市" 11 | county: "区县" 12 | errors: 13 | messages: 14 | inclusion: "不包含于列表中" 15 | exclusion: "是保留关键字" 16 | invalid: "是无效的" 17 | confirmation: "与确认值不匹配" 18 | accepted: "必须是可被接受的" 19 | empty: "不能留空" 20 | blank: "不能为空字符" 21 | too_long: "过长(最长为 %{count} 个字符)" 22 | too_short: "过短(最短为 %{count} 个字符)" 23 | wrong_length: "长度非法(必须为 %{count} 个字符)" 24 | taken: "已经被使用" 25 | not_a_number: "不是数字" 26 | not_an_integer: "必须是整数" 27 | greater_than: "必须大于 %{count}" 28 | greater_than_or_equal_to: "必须大于或等于 %{count}" 29 | equal_to: "必须等于 %{count}" 30 | less_than: "必须小于 %{count}" 31 | less_than_or_equal_to: "必须小于或等于 %{count}" 32 | odd: "必须为单数" 33 | even: "必须为双数" 34 | record_invalid: "校验失败: %{errors}" 35 | taken: 36 | 已占用 37 | document_not_found: 38 | 没有发现类是 %{klass} ID 是 %{identifiers} 的文档 39 | invalid_type: 40 | 在类%{klass}中定义了字段,实际值是%{value}的%{other}. 41 | unsupported_version: 42 | MongoDB %{version} 版本已过期,请升级到 %{mongo_version}. 43 | validations: 44 | 校验失败 - %{errors}. 45 | -------------------------------------------------------------------------------- /config/puma-web.rb: -------------------------------------------------------------------------------- 1 | app_root = '/data/www/rapi/current' 2 | pidfile "#{app_root}/tmp/pids/puma.pid" 3 | state_path "#{app_root}/tmp/pids/puma.state" 4 | stdout_redirect "#{app_root}/log/puma.stdout.log", "#{app_root}/log/puma.stderr.log", true 5 | daemonize true 6 | port 8000 7 | workers 4 8 | threads 4, 10 9 | preload_app! 10 | 11 | on_worker_boot do 12 | ActiveSupport.on_load(:active_record) do 13 | ActiveRecord::Base.establish_connection 14 | end 15 | end 16 | 17 | before_fork do 18 | ActiveRecord::Base.connection_pool.disconnect! 19 | end 20 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum, this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests, default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. If you use this option 30 | # you need to make sure to reconnect any threads in the `on_worker_boot` 31 | # block. 32 | # 33 | # preload_app! 34 | 35 | # If you are preloading your application and using Active Record, it's 36 | # recommended that you close any connections to the database before workers 37 | # are forked to prevent connection leakage. 38 | # 39 | # before_fork do 40 | # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) 41 | # end 42 | 43 | # The code in the `on_worker_boot` will be called if you are using 44 | # clustered mode by specifying a number of `workers`. After each worker 45 | # process is booted this block will be run, if you are using `preload_app!` 46 | # option you will want to use this block to reconnect to any threads 47 | # or connections that may have been created at application boot, Ruby 48 | # cannot share connections between processes. 49 | # 50 | # on_worker_boot do 51 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) 52 | # end 53 | # 54 | 55 | # Allow puma to be restarted by `rails restart` command. 56 | plugin :tmp_restart 57 | -------------------------------------------------------------------------------- /config/redis.yml.sample: -------------------------------------------------------------------------------- 1 | default: 2 | host: 127.0.0.1 3 | port: 6379 4 | development: 5 | db: 3 6 | test: 7 | db: 3 8 | staging: 9 | db: 3 10 | #password: 'xxx' 11 | production: 12 | db: 3 13 | host: 127.0.0.1 14 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :manage_features, only: [:index] 3 | resources :products, only: [:index, :show] 4 | resources :home_slides, only: [:index] 5 | resources :coupons, only: [] do 6 | collection do 7 | get :list 8 | end 9 | end 10 | 11 | resources :orders, only: [] do 12 | collection do 13 | post :create_applet_order 14 | end 15 | end 16 | 17 | resources :sessions, only: [] do 18 | collection do 19 | post :wechat_user_type 20 | post :login 21 | post :logout 22 | get :get_mobile_passcode 23 | end 24 | end 25 | 26 | resources :my_assets, only: [:index, :show], param: :sku 27 | 28 | resource :send_validation_code, only: [] do 29 | get :send_message 30 | end 31 | 32 | namespace :wechat do 33 | resources :payments, only: [] do 34 | member do 35 | post :notify 36 | end 37 | end 38 | end 39 | 40 | resources :districts, only: [] do 41 | collection do 42 | get :provinces 43 | get :cities 44 | get :counties 45 | get :districts 46 | end 47 | end 48 | resources :product_qr_codes, only: [] do 49 | collection do 50 | get :image 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rails secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | # Shared secrets are available across all environments. 14 | 15 | shared: 16 | api_key: 123 17 | 18 | # Environmental secrets are only available for that specific environment. 19 | 20 | development: 21 | secret_key_base: 7f210aadd9d4dde8f0eaa2dc76693fa38cf231a02444fb92880de4b28ffc336c7538007982bb39beab77c8584f035ee499c9582716e6d93656338b8f8a5c19ee 22 | 23 | test: 24 | secret_key_base: 4a16d3ebe681835d7a5dba6bbd89b4ad3fc5a85a7ed2195e4685cb13a14cf8fbe18324a9d39dee23ae1d84de1e3751f08ddd9e98936f0f39136ed551b10734dd 25 | 26 | # Do not keep production secrets in the repository, 27 | # instead read values from the environment. 28 | 29 | production: 30 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 31 | -------------------------------------------------------------------------------- /config/sidekiq.yml.sample: -------------------------------------------------------------------------------- 1 | :concurrency: 5 2 | :pidfile: tmp/pids/sidekiq.pid 3 | :logfile: log/sidekiq.log 4 | staging: 5 | :concurrency: 10 6 | production: 7 | :concurrency: 10 8 | test: 9 | :concurrency: 10 10 | :queues: 11 | - default -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /config/wechat.yml.sample: -------------------------------------------------------------------------------- 1 | default: &default 2 | corpid: "corpid" 3 | corpsecret: "corpsecret" 4 | agentid: 1 5 | # Or if using public account, only need above two line 6 | # appid: "my_appid" 7 | # secret: "my_secret" 8 | token: "my_token" 9 | access_token: "C:/Users/[username]/wechat_access_token" 10 | encrypt_mode: false # if true must fill encoding_aes_key 11 | encoding_aes_key: "my_encoding_aes_key" 12 | jsapi_ticket: "C:/Users/[user_name]/wechat_jsapi_ticket" 13 | 14 | production: 15 | corpid: <%= ENV['WECHAT_CORPID'] %> 16 | corpsecret: <%= ENV['WECHAT_CORPSECRET'] %> 17 | agentid: <%= ENV['WECHAT_AGENTID'] %> 18 | 19 | # Or if using public account, only need above two line 20 | appid: <%= ENV['weapplet_app_id'] %> 21 | secret: <%= ENV['weapplet_secret'] %> 22 | token: <%= ENV['WECHAT_TOKEN'] %> 23 | timeout: 30, 24 | skip_verify_ssl: true 25 | access_token: <%= ENV['WECHAT_ACCESS_TOKEN'] %> 26 | encrypt_mode: false # if true must fill encoding_aes_key 27 | encoding_aes_key: <%= ENV['WECHAT_ENCODING_AES_KEY'] %> 28 | jsapi_ticket: <%= ENV['WECHAT_JSAPI_TICKET'] %> 29 | oauth2_cookie_duration: <%= ENV['WECHAT_OAUTH2_COOKIE_DURATION'] %> # seconds 30 | 31 | development: 32 | <<: *default 33 | trusted_domain_fullname: "http://your_dev.proxy.qqbrowser.cc" 34 | 35 | test: 36 | <<: *default 37 | 38 | # Multiple Accounts 39 | # 40 | # wx2_development: 41 | # <<: *default 42 | # appid: "my_appid" 43 | # secret: "my_secret" 44 | # access_token: "tmp/wechat_access_token2" 45 | # jsapi_ticket: "tmp/wechat_jsapi_ticket2" 46 | # 47 | # wx2_test: 48 | # <<: *default 49 | # appid: "my_appid" 50 | # secret: "my_secret" 51 | # 52 | # wx2_production: 53 | # <<: *default 54 | # appid: "my_appid" 55 | # secret: "my_secret" 56 | # 57 | # wx3_development: 58 | # <<: *default 59 | # appid: "my_appid" 60 | # secret: "my_secret" 61 | # access_token: "tmp/wechat_access_token3" 62 | # jsapi_ticket: "tmp/wechat_jsapi_ticket3" 63 | # 64 | # wx3_test: 65 | # <<: *default 66 | # appid: "my_appid" 67 | # secret: "my_secret" 68 | # 69 | # wx3_production: 70 | # <<: *default 71 | # appid: "my_appid" 72 | # secret: "my_secret" 73 | # -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/lib/tasks/.keep -------------------------------------------------------------------------------- /lib/wxbiz_data_crypt.rb: -------------------------------------------------------------------------------- 1 | class WXBizDataCrypt 2 | def initialize(app_id, session_key) 3 | @app_id = app_id 4 | @session_key = Base64.decode64(session_key) 5 | end 6 | 7 | def decrypt(encrypted_data, iv) 8 | encrypted_data = Base64.decode64(encrypted_data) 9 | iv = Base64.decode64(iv) 10 | 11 | cipher = OpenSSL::Cipher::AES.new(128, :CBC) 12 | cipher.decrypt 13 | 14 | cipher.key = @session_key 15 | cipher.iv = iv 16 | data = cipher.update(encrypted_data) << cipher.final 17 | result = JSON.parse(data[0...-data.last.ord]) 18 | 19 | raise '解密错误' if result['watermark']['appid'] != @app_id 20 | result 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/log/.keep -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/public/favicon.ico -------------------------------------------------------------------------------- /public/images/3_asset_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/public/images/3_asset_info.png -------------------------------------------------------------------------------- /public/images/3_assets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/public/images/3_assets.png -------------------------------------------------------------------------------- /public/images/53_asset_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/public/images/53_asset_info.png -------------------------------------------------------------------------------- /public/images/53_assets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/public/images/53_assets.png -------------------------------------------------------------------------------- /public/images/5_asset_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/public/images/5_asset_info.png -------------------------------------------------------------------------------- /public/images/5_assets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/public/images/5_assets.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /rapi.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": ".", 6 | "folder_exclude_patterns": [".bundle",".idea","tmp",".capistrano","log","tmp","public/assets","coverage"], 7 | "file_exclude_patterns": ["*.sublime-workspace",".byebug_history"] 8 | }, 9 | { 10 | "path": "../baye-core", 11 | "folder_exclude_patterns": [".bundle",".idea",".yardoc","log","tmp","pkg",".capistrano"], 12 | "file_exclude_patterns": [".gitkeep","*.sublime-workspace","*.sqlite3*",".tags*",".gemtags",".DS_Store"] 13 | } 14 | ], 15 | "settings": 16 | { 17 | "translate_tabs_to_spaces": true, 18 | "trim_trailing_white_space_on_save": true, 19 | "tab_size": 2 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/test/controllers/.keep -------------------------------------------------------------------------------- /test/controllers/sessions_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SessionsControllerTest < ActionDispatch::IntegrationTest 4 | setup do 5 | @wechat_user = wechat_users(:gehao) 6 | @customer = customers(:gehao) 7 | @client_token = @wechat_user.client_token 8 | 9 | @header = { 'Content-Type' => 'application/json' } 10 | @head_with_token = @header.merge('Authorization' => @client_token) 11 | 12 | SessionsController.class_eval do 13 | def update_wechat_user_token 14 | @token = 'wx_' + SecureRandom.hex(20) 15 | true 16 | end 17 | end 18 | end 19 | 20 | test 'token login' do 21 | post login_sessions_url, params: {}, headers: @head_with_token 22 | resp = JSON.parse @response.body 23 | 24 | assert_equal(@client_token, resp['token']) 25 | assert_equal(@customer.name, resp['customer']['name']) 26 | assert_equal(@customer.baye_rank, resp['customer']['baye_rank']) 27 | end 28 | 29 | test 'password login success' do 30 | post login_sessions_url(mobile: @customer.mobile, password: '1234567'), headers: @header 31 | 32 | assert_response :success 33 | resp = JSON.parse(@response.body) 34 | 35 | # Not equal! 36 | assert_not_equal(@client_token, resp['token']) 37 | 38 | assert_equal(@customer.name, resp['customer']['name']) 39 | assert_equal(@customer.baye_rank, resp['customer']['baye_rank']) 40 | end 41 | 42 | test 'mobile code login success' do 43 | Verification.send_message!(@customer.mobile) 44 | mobile_code = Verification.where(mobile: @customer.mobile).last.code 45 | 46 | post login_sessions_url(mobile: @customer.mobile, mobile_code: mobile_code), headers: @header 47 | 48 | assert_response :success 49 | resp = JSON.parse(@response.body) 50 | 51 | # Not equal! 52 | assert_not_equal(@client_token, resp['token']) 53 | 54 | assert_equal(@customer.name, resp['customer']['name']) 55 | assert_equal(@customer.baye_rank, resp['customer']['baye_rank']) 56 | end 57 | 58 | test 'mobile code login (New mobile) success' do 59 | new_mobile = '13055556666' 60 | 61 | Verification.send_message!(new_mobile) 62 | mobile_code = Verification.where(mobile: new_mobile).last.code 63 | 64 | post login_sessions_url(mobile: new_mobile, mobile_code: mobile_code), headers: @header 65 | 66 | assert_response :success 67 | resp = JSON.parse(@response.body) 68 | 69 | # Not equal! 70 | assert_not_equal(@client_token, resp['token']) 71 | assert_not_nil(Customer.find_by(mobile: resp['customer']['mobile'])) 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/test/fixtures/.keep -------------------------------------------------------------------------------- /test/fixtures/customers.yml: -------------------------------------------------------------------------------- 1 | gehao: 2 | id: 4491 3 | name: baye 4 | mobile: "13901234567" 5 | reference_code: "CU8X" 6 | referenced_code: "ZV0J" 7 | referenced_customer_id: 579 8 | coin_balance: 100 9 | created_at: "2016-07-05 03:17:23" 10 | updated_at: "2016-12-07 14:52:09" 11 | pinyin_of_name: "ba-ye" 12 | uid: "282dba002bb3365de29e3d15af0074ee" 13 | nickname: "baye" 14 | level: 10 15 | encrypted_password: "$2a$10$d3JizEoeFFisKpQFeWSmN.RTh58YK4FK/Mf7ehWJE6SzsHL8KUaz." 16 | gender: "男" 17 | recommend_amount: 10 18 | salesforce_id: "0012800000jECRpAAO" 19 | synchronized_at: "2016-10-11 07:27:27" 20 | account_type: "巴爷" 21 | is_seller_at: "2016-09-09 10:56:58" 22 | liker_notifications_count: 8 23 | reply_notifications_count: 5 24 | follower_notifications_count: 1 25 | notifications_count: 0 26 | baye_rank: "巴爷" 27 | easemob_account_created_at: "2016-10-19 09:05:17" 28 | mobile_visible_scope: 1 29 | namecard_visible_scope: 1 30 | free_coin_balance: 0 -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/test/fixtures/files/.keep -------------------------------------------------------------------------------- /test/fixtures/wechat_users.yml: -------------------------------------------------------------------------------- 1 | gehao: 2 | id: 19245 3 | open_id: "o4X350GBEGHSgAIfQvjIeo-jiJGI" 4 | nickname: "bayekeji" 5 | expired_at: <%= 7.days.from_now.to_s(:db) %> 6 | union_open_id: "ons-ruL8EzG73uFQh7TCpY5-M7Ag" 7 | avatar: "http://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLTkDEUjd..." 8 | gender: 1 9 | country: "CN" 10 | city: "Shanghai" 11 | province: "Shanghai" 12 | created_at: "2016-11-27 13:50:47" 13 | updated_at: "2016-12-23 07:07:50" 14 | customer_id: 4491 15 | app_id: "wx8cdc4da512e11844" 16 | session_key: "18kSv1ZsjR411jUAhEVV2w==" 17 | wx_code: "021G9fDO0bi1A82duaEO0JHzDO1G9fDb" 18 | client_token: "wx_bab6d75fbab1bd9fdd13bd0ead4683ead09fe85b" -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/test/integration/.keep -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/test/mailers/.keep -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/test/models/.keep -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../config/environment', __FILE__) 2 | require 'rails/test_help' 3 | 4 | class ActiveSupport::TestCase 5 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 6 | fixtures :all 7 | 8 | # Add more helper methods to be used by all tests here... 9 | end 10 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayetech/wechat_mall_applet_backend/3dc17f06d2b24e2eb44b9dc11f4bd739f96a74e1/tmp/.keep --------------------------------------------------------------------------------