{"id":15792848,"url":"https://github.com/wolfg1969/rack-weixin","last_synced_at":"2025-10-25T20:34:48.133Z","repository":{"id":7037714,"uuid":"8314021","full_name":"wolfg1969/rack-weixin","owner":"wolfg1969","description":"微信公众平台 开放消息接口 Rack Middleware","archived":false,"fork":false,"pushed_at":"2014-03-06T02:25:59.000Z","size":504,"stargazers_count":105,"open_issues_count":0,"forks_count":27,"subscribers_count":18,"default_branch":"master","last_synced_at":"2024-03-14T23:47:12.042Z","etag":null,"topics":["rack","rack-middleware","rails","ruby","weixin"],"latest_commit_sha":null,"homepage":"http://rubygems.org/gems/rack-weixin","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wolfg1969.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-02-20T13:55:36.000Z","updated_at":"2022-05-03T03:21:39.000Z","dependencies_parsed_at":"2022-08-20T13:10:46.973Z","dependency_job_id":null,"html_url":"https://github.com/wolfg1969/rack-weixin","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wolfg1969%2Frack-weixin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wolfg1969%2Frack-weixin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wolfg1969%2Frack-weixin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wolfg1969%2Frack-weixin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wolfg1969","download_url":"https://codeload.github.com/wolfg1969/rack-weixin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248119543,"owners_count":21050786,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rack","rack-middleware","rails","ruby","weixin"],"created_at":"2024-10-04T23:05:36.949Z","updated_at":"2025-10-25T20:34:43.082Z","avatar_url":"https://github.com/wolfg1969.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"微信公众平台 开放消息接口 Rack Middleware\n========================================\n\n[![Build Status](https://travis-ci.org/wolfg1969/rack-weixin.png?branch=master)](https://travis-ci.org/wolfg1969/rack-weixin) [![Gem Version](https://badge.fury.io/rb/rack-weixin.png)](http://badge.fury.io/rb/rack-weixin)\n\n* 验证微信请求 with 'weixin/middleware'\n* 解析推送消息 with 'weixin/model'\n* 生成回复消息 with 'weixin/model'\n\n\nInstallation\n------------\n```\n$ gem install rack-weixin\n```\n\n\nUsage\n-----\n\nA sinatra demo\n\n```ruby\n# -*- encoding : utf-8 -*-\nrequire 'sinatra'\nrequire 'rack-weixin'\n\nuse Weixin::Middleware, 'your api token', '/your_app_root' \n\nconfigure do\n    set :wx_id, 'your_weixin_account'\nend\n\nhelpers do\n  def msg_router(msg)\n    case msg.MsgType\n    when 'text'\n      # text message handler\n    when 'image'\n      # image message handler\n    when 'location'\n      # location message handler\n    when 'link'\n      # link message handler\n    when 'event'\n      # event messge handler\n    when 'voice'\n      # voice message handler\n    when 'video'\n      # video message handler\n    else\n      Weixin.text_msg(msg.ToUserName, msg.FromUserName, '未知消息类型')\n    end\n  end\nend\n\nget '/your_app_root' do\n    params[:echostr]\nend\n\npost '/your_app_root' do\n    content_type :xml, 'charset' =\u003e 'utf-8'\n\n    message = request.env[Weixin::Middleware::WEIXIN_MSG]\n    logger.info \"原始数据: #{request.env[Weixin::Middleware::WEIXIN_MSG_RAW]}\"\n    \n    # handle the message according to your business logic\n    msg_router(message) unless message.nil?\nend\n```\n\n### Padrino下使用\n在`Gemfile`里加入:\n\n\tgem 'rack-weixin'\n\n在`config/apps.rb`关闭以下两个：\n\n\tset :protection, false\n\tset :protect_from_csrf, false\n\t\n在`app.rb`里加入：\n\n\tuse Weixin::Middleware, 'your api token', '/your_app_root' \n\n\tconfigure do\n    \t  set :wx_id, 'your_weixin_account'\n\tend\n\n\n### Rack和Rails ActionController配合下使用\n\n``` ruby\nclass WeixinController \u003c ActionController::Base\n\n  def index\n    params[:echostr]\n  end\n\n  def create\n\n    xml_message = request.env[Weixin::Middleware::WEIXIN_MSG]\n\n    raw_message = request.env[Weixin::Middleware::WEIXIN_MSG_RAW]\n\n    Rails.logger.debug \"raw_message = \" do\n      raw_message\n    end\n\n    if xml_message.present?\n      response_xml = msg_router(xml_message)\n\n      Rails.logger.info \"Weixin response_xml = \" do\n        response_xml\n      end\n\n    end\n\n    if response_xml.present?\n      render xml: response_xml\n    else\n      render :nothing =\u003e true, :status =\u003e 200, :content_type =\u003e 'text/html'\n    end\n\n  end\n\n  def msg_router(msg)\n    case msg.MsgType\n      when 'text'\n        text_parse(msg)\n      when 'image'\n        image_parse(msg)\n      when 'location'\n        location_parse(msg)\n      when 'link'\n        link_parse(msg)\n      when 'event'\n        event_parse(msg)\n      when 'voice'\n        voice_parse(msg)\n      when 'video'\n        video_parse(msg)\n      else\n    end\n\n  end\n\nend\n```\n\n### Rails before_filter验证消息，用于接入多个微信公众号\n\n``` ruby\nclass WeixinController \u003c ActionController::Base\n\n  before_filter :check_signature\n\n  def index\n    render :text =\u003e params[:echostr]\n  end\n\n  def create\n\n    raw_msg = env[Weixin::Middleware::POST_BODY].read\n\n    begin\n\n       xml_message = Weixin::Message.factory(raw_msg)\n\n    rescue Exception =\u003e e\n      message = \"weixin post message error!\"\n      Rails.logger.error \"#{message} params = \" do\n        params\n      end\n    end\n    \n    Rails.logger.debug \"raw_message = \" do\n      raw_message\n    end\n\n    if xml_message.present?\n      response_xml = msg_router(xml_message)\n\n      Rails.logger.info \"Weixin response_xml = \" do\n        response_xml\n      end\n\n    end\n\n    if response_xml.present?\n      render xml: response_xml\n    else\n      render :nothing =\u003e true, :status =\u003e 200, :content_type =\u003e 'text/html'\n    end\n\n\n  end\n\n  private\n\n  def msg_router(msg)\n    case msg.MsgType\n      when 'text'\n        text_parse(msg)\n      when 'image'\n        image_parse(msg)\n      when 'location'\n        location_parse(msg)\n      when 'link'\n        link_parse(msg)\n      when 'event'\n        event_parse(msg)\n      when 'voice'\n        voice_parse(msg)\n      when 'video'\n        video_parse(msg)\n      else\n    end\n\n  end\n\n  def check_signature\n\n\n    fullpath = \"#{request.protocol + request.host_with_port + request.path}\"\n\n    Rails.logger.debug \"fullpath = \" do\n      fullpath\n    end\n\n    # 根据fullpath查询不同的token\n    # token = query_token_by_fullpath(fullpath)\n\n    Rails.logger.debug \"token = \" do\n      token\n    end\n\n    if token.present? \u0026\u0026 Weixin::Middleware.request_is_valid?(token, params)\n\n\n    else\n\n      Rails.logger.error \"token is null or request_is_valid! params = \" do\n        params\n      end\n\n      render :status =\u003e 401\n\n    end\n\n  end\n\n\nend\n```\n\n### 微信接口\n\n初始化Weixin::Client\n\n``` ruby\nclient = Weixin::Client.new('your_weixin_app_key', 'your_weixin_app_secret')\n```\n\n[获取access token](http://mp.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96access_token)\n\n``` ruby\nclient.access_token\n```\n\n[获取用户基本信息](http://mp.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96%E7%94%A8%E6%88%B7%E5%9F%BA%E6%9C%AC%E4%BF%A1%E6%81%AF)\n\n``` ruby\nclient.user.info('openid')\n```\n\n[发送客服消息](http://mp.weixin.qq.com/wiki/index.php?title=%E5%8F%91%E9%80%81%E5%AE%A2%E6%9C%8D%E6%B6%88%E6%81%AF)\n\n``` ruby\n# 发送文本消息\nmessage = \u003c\u003cSTRING\n{\n    \"touser\":\"OPENID\",\n    \"msgtype\":\"text\",\n    \"text\":\n    {\n         \"content\":\"Hello World\"\n    }\n}\nSTRING\n\nmessage = JSON.parse(message)\n\nclient.message_custom.send(message)\n\n\nmessage = {\"touser\"=\u003e\"OPENID\", \"msgtype\"=\u003e\"text\", \"text\"=\u003e{\"content\"=\u003e\"Hello World\"}} \n\nclient.message_custom.send(message)\n\n\n\n# 发送图片消息\nmessage = \u003c\u003cSTRING\n{\n    \"touser\":\"OPENID\",\n    \"msgtype\":\"image\",\n    \"image\":\n    {\n      \"media_id\":\"MEDIA_ID\"\n    }\n}\nSTRING\n\nmessage = JSON.parse(message)\n\nclient.message_custom.send(message)\n\n\nmessage = {\"touser\"=\u003e\"OPENID\", \"msgtype\"=\u003e\"image\", \"image\"=\u003e{\"media_id\"=\u003e\"MEDIA_ID\"}}\n\nclient.message_custom.send(message)\n\n\n\n\n\n# 发送语音消息\nmessage = \u003c\u003cSTRING\n{\n    \"touser\":\"OPENID\",\n    \"msgtype\":\"voice\",\n    \"voice\":\n    {\n      \"media_id\":\"MEDIA_ID\"\n    }\n}\nSTRING\n\nmessage = JSON.parse(message)\n\nclient.message_custom.send(message)\n\n\nmessage = {\"touser\"=\u003e\"OPENID\", \"msgtype\"=\u003e\"voice\", \"voice\"=\u003e{\"media_id\"=\u003e\"MEDIA_ID\"}}\n\nclient.message_custom.send(message)\n\n\n\n\n\n# 发送视频消息\nmessage = \u003c\u003cSTRING\n{\n    \"touser\":\"OPENID\",\n    \"msgtype\":\"video\",\n    \"video\":\n    {\n      \"media_id\":\"MEDIA_ID\",\n      \"title\":\"TITLE\",\n      \"description\":\"DESCRIPTION\"\n    }\n}\nSTRING\n\nmessage = JSON.parse(message)\n\nclient.message_custom.send(message)\n\n\nmessage = {\"touser\"=\u003e\"OPENID\", \"msgtype\"=\u003e\"video\", \"video\"=\u003e{\"media_id\"=\u003e\"MEDIA_ID\", \"title\"=\u003e\"TITLE\", \"description\"=\u003e\"DESCRIPTION\"}}\n\nclient.message_custom.send(message)\n\n\n\n\n# 发送音乐消息\nmessage = \u003c\u003cSTRING\n{\n    \"touser\":\"OPENID\",\n    \"msgtype\":\"music\",\n    \"music\":\n    {\n      \"title\":\"MUSIC_TITLE\",\n      \"description\":\"MUSIC_DESCRIPTION\",\n      \"musicurl\":\"MUSIC_URL\",\n      \"hqmusicurl\":\"HQ_MUSIC_URL\",\n      \"thumb_media_id\":\"THUMB_MEDIA_ID\" \n    }\n}\nSTRING\n\nmessage = JSON.parse(message)\n\nclient.message_custom.send(message)\n\n\nmessage = {\"touser\"=\u003e\"OPENID\", \"msgtype\"=\u003e\"music\", \"music\"=\u003e{\"title\"=\u003e\"MUSIC_TITLE\", \"description\"=\u003e\"MUSIC_DESCRIPTION\", \"musicurl\"=\u003e\"MUSIC_URL\", \"hqmusicurl\"=\u003e\"HQ_MUSIC_URL\", \"thumb_media_id\"=\u003e\"THUMB_MEDIA_ID\"}}\n\nclient.message_custom.send(message)\n\n\n\n\n# 发送图文消息\nmessage = \u003c\u003cSTRING\n{\n    \"touser\":\"OPENID\",\n    \"msgtype\":\"news\",\n    \"news\":{\n        \"articles\": [\n         {\n             \"title\":\"Happy Day\",\n             \"description\":\"Is Really A Happy Day\",\n             \"url\":\"URL\",\n             \"picurl\":\"PIC_URL\"\n         },\n         {\n             \"title\":\"Happy Day\",\n             \"description\":\"Is Really A Happy Day\",\n             \"url\":\"URL\",\n             \"picurl\":\"PIC_URL\"\n         }\n         ]\n    }\n}\nSTRING\n\nmessage = JSON.parse(message)\n\nclient.message_custom.send(message)\n\n\nmessage = {\"touser\"=\u003e\"OPENID\", \"msgtype\"=\u003e\"news\", \"news\"=\u003e{\"articles\"=\u003e[{\"title\"=\u003e\"Happy Day\", \"description\"=\u003e\"Is Really A Happy Day\", \"url\"=\u003e\"URL\", \"picurl\"=\u003e\"PIC_URL\"}, {\"title\"=\u003e\"Happy Day\", \"description\"=\u003e\"Is Really A Happy Day\", \"url\"=\u003e\"URL\", \"picurl\"=\u003e\"PIC_URL\"}]}} \n\n\nclient.message_custom.send(message)\n\n```\n\n\n\n\n\n[自定义菜单创建接口](http://mp.weixin.qq.com/wiki/index.php?title=%E8%87%AA%E5%AE%9A%E4%B9%89%E8%8F%9C%E5%8D%95%E5%88%9B%E5%BB%BA%E6%8E%A5%E5%8F%A3)\n``` ruby\nmenu_string = \u003c\u003cSTRING\n {\n     \"button\":[\n     {  \n          \"type\":\"click\",\n          \"name\":\"今日歌曲\",\n          \"key\":\"V1001_TODAY_MUSIC\"\n      },\n      {\n           \"type\":\"click\",\n           \"name\":\"歌手简介\",\n           \"key\":\"V1001_TODAY_SINGER\"\n      },\n      {\n           \"name\":\"菜单\",\n           \"sub_button\":[\n           {  \n               \"type\":\"view\",\n               \"name\":\"搜索\",\n               \"url\":\"http://www.soso.com/\"\n            },\n            {\n               \"type\":\"view\",\n               \"name\":\"视频\",\n               \"url\":\"http://v.qq.com/\"\n            },\n            {\n               \"type\":\"click\",\n               \"name\":\"赞一下我们\",\n               \"key\":\"V1001_GOOD\"\n            }]\n       }]\n }\nSTRING\n\nmenu_hash = JSON.parse(menu_string)\n\nmenu_hash = {\"button\"=\u003e[{\"type\"=\u003e\"click\", \"name\"=\u003e\"今日歌曲\", \"key\"=\u003e\"V1001_TODAY_MUSIC\"}, {\"type\"=\u003e\"click\", \"name\"=\u003e\"歌手简介\", \"key\"=\u003e\"V1001_TODAY_SINGER\"}, {\"name\"=\u003e\"菜单\", \"sub_button\"=\u003e[{\"type\"=\u003e\"view\", \"name\"=\u003e\"搜索\", \"url\"=\u003e\"http://www.soso.com/\"}, {\"type\"=\u003e\"view\", \"name\"=\u003e\"视频\", \"url\"=\u003e\"http://v.qq.com/\"}, {\"type\"=\u003e\"click\", \"name\"=\u003e\"赞一下我们\", \"key\"=\u003e\"V1001_GOOD\"}]}]} \n\nmenu.add(menu_hash)\n```\n\n\n\n[自定义菜单查询接口](http://mp.weixin.qq.com/wiki/index.php?title=%E8%87%AA%E5%AE%9A%E4%B9%89%E8%8F%9C%E5%8D%95%E6%9F%A5%E8%AF%A2%E6%8E%A5%E5%8F%A3)\n\n``` ruby\nmenu = Weixin::Menu.new('your_weixin_app_key', 'your_weixin_app_secret')\n\nmenu.get\n```\n\n\n\n[自定义菜单删除接口](http://mp.weixin.qq.com/wiki/index.php?title=%E8%87%AA%E5%AE%9A%E4%B9%89%E8%8F%9C%E5%8D%95%E5%88%A0%E9%99%A4%E6%8E%A5%E5%8F%A3)\n\n``` ruby\nmenu.delete\n```\n\n\n\nTODO\n----\n\nCopyright\n---------\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to\ndeal in the Software without restriction, including without limitation the\nrights to use, copy, modify, merge, publish, distribute, sublicense, and/or\nsell copies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\nTHE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/wolfg1969/rack-weixin/trend.png)](https://bitdeli.com/free \"Bitdeli Badge\")\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwolfg1969%2Frack-weixin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwolfg1969%2Frack-weixin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwolfg1969%2Frack-weixin/lists"}