Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

laochen/lua-resty-wechat

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

32 Commits

Repository files navigation

lua-resty-sns

使用Lua编写的nginx服务器微信、微博公众平台代理.

目标:

  • 在前置的nginx内做微信代理, 降低内部应用层和微信服务的耦合.
  • 配置微信公众号的自动回复, 在nginx内处理部分用户消息, 减小应用层压力.
  • 统一管理微信公众号API中使用的ACCESS_TOKEN, 作为中控服务器隔离业务层和API实现, 降低ACCESS_TOKEN冲突率, 增加服务稳定性.
  • 部署微信JS-SDK授权回调页面, 减小应用层压力.

子模块说明

全局配置

config

公众号全局配置数据, 包括接口Token, 自动回复设置.

作为服务端由微信请求并响应

server

接收微信发出的普通消息和事件推送等请求, 并按配置做出响应, 未做对应配置则按微信要求返回success.

此部分核心代码由aCayF/lua-resty-wechat做重构修改而来.

使用config.autoreplyurl, 配置后台处理服务地址, 转发处理并响应复杂的微信消息. (依赖pintsized/lua-resty-http)

作为客户端代理调用微信公众号API

proxy_access_token

使用Redis缓存AccessToken和jsapi_ticket, 定时自动调用微信服务更新, 支持分布式更新.

proxy

代理调用微信公众平台API接口, 自动添加access_token参数.

proxy_access_filter

过滤客户端IP, 限制请求来源.

代理网页授权获取用户基本信息

oauth

JS-SDK权限签名

jssdk_config

示例

nginx配置:

http {
 lua_package_path 'path to lua files';
 resolver 114.114.114.114;
 lua_shared_dict sns 1M; # 利用共享内存保持单例定时器
 init_by_lua '
 ngx.shared.sns:delete("updater") -- 清除定时器标识
 require("resty.sns.config")
 ';
 init_worker_by_lua '
 local ok, err = ngx.shared.sns:add("updater", "1") -- 单进程启动定时器
 if not ok or err then return end
 require("resty.sns.wechat.proxy_access_token")()
 ';
 server {
 location /wechat-server {
 content_by_lua '
 require("resty.sns.wechat.server")()
 ';
 }
 location /wechat-proxy/ {
 rewrite_by_lua '
 require("resty.sns.wechat.proxy")("wechat-proxy") -- 参数为location路径
 ';
 access_by_lua '
 require("resty.sns.wechat.proxy_access_filter")()
 ';
 proxy_pass https://api.weixin.qq.com/;
 }
 location /wechat-baseoauth { # param: goto
 rewrite_by_lua '
 require("resty.sns.wechat.oauth").base_oauth("path to /wechat-redirect")
 ';
 }
 location /wechat-useroauth { # param: goto
 rewrite_by_lua '
 require("resty.sns.wechat.oauth").userinfo_oauth("path to /wechat-redirect")
 ';
 }
 location /wechat-redirect {
 rewrite_by_lua '
 require("resty.sns.wechat.oauth").redirect()
 ';
 }
 location /wechat-useroauth-code { 
 content_by_lua '
 if not ngx.var.arg_code then -- unauthorized
 ngx_log(ngx.ERR, "code is empty")
 return ngx_exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
 end
 local code = tostring(ngx.var.arg_code)
 local baseinfo, userinfo, err = require("resty.sns.wechat.oauth").oauth_by_code(code)
 ';
 }
 location /wechat-jssdk-config { # GET/POST, param: url, [api]
 add_header Access-Control-Allow-Origin "if need cross-domain call";
 content_by_lua '
 require("resty.sns.wechat.jssdk_config")()
 ';
 }
 location /weibo-useroauth-code { 
 content_by_lua '
 if not ngx.var.arg_code then -- unauthorized
 ngx_log(ngx.ERR, "code is empty")
 return ngx_exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
 end
 local code = tostring(ngx.var.arg_code)
 local baseinfo, userinfo, err = require("resty.sns.weibo.oauth").oauth_by_code(code)
 ';
 }
 }
}

网页注入JS-SDK权限:

$.ajax({
 url: "url path to /wechat-jssdk-config",
 data: {
 url: window.location.href,
 api: "onMenuShareTimeline|onMenuShareAppMessage|onMenuShareQQ|onMenuShareWeibo|onMenuShareQZone"
 },
 success: function(response) {
 wx.config(response);
 }
});
$.ajax({
 url: "url path to /wechat-jssdk-config",
 data: {
 url: window.location.href
 },
 success: function(response) {
 wx.config({
 appId: response.appId,
 timestamp: response.timestamp,
 nonceStr: response.nonceStr,
 signature: response.signature,
 jsApiList: [
 'onMenuShareTimeline',
 'onMenuShareAppMessage',
 'onMenuShareQQ',
 'onMenuShareWeibo',
 'onMenuShareQZone'
 ]
 });
 }
});

使用Java解析代理网页授权获得的cookie

Map authInfo = JSON.parseObject(decryptAES(unBase64("cookie value"), getKey("AES key")));
// 默认AES key: "vFrItmxI9ct8JbAg"
// 配置于config.lua -> cookie_aes_key
// 依赖方法
import com.alibaba.fastjson.JSON;
import com.google.common.base.Charsets;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
public StringBuilder padding(String s, char letter, int repeats) {
 StringBuilder sb = new StringBuilder(s);
 while (repeats-- > 0) {
 sb.append(letter);
 }
 return sb;
}
public String padding(String s) {
 return padding(s, '=', s.length() % 4).toString();
}
public byte[] unBase64(String value) {
 return org.apache.commons.codec.binary.Base64.decodeBase64(padding(value));
}
public String string(byte[] bytes) {
 return new String(bytes, Charsets.UTF_8);
}
public String decryptAES(byte[] value, Key key) {
 try {
 Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
 cipher.init(Cipher.DECRYPT_MODE, key);
 byte[] decrypted = cipher.doFinal(value);
 return string(decrypted);
 } catch (Exception e) {
 throw new RuntimeException(e);
 }
}
public byte[] bytes(String str) {
 return str == null ? null : str.getBytes(Charsets.UTF_8);
}
public Key keyFromString(String keyString) {
 return new SecretKeySpec(bytes(keyString), "AES");
}
public Key getKey(String key) {
 if (key.length() >= 16) {
 return keyFromString(key.substring(0, 16));
 }
 StringBuilder sb = new StringBuilder(key);
 while (sb.length() < 16) {
 sb.append(key);
 }
 return keyFromString(sb.toString().substring(0, 16));
}

About

使用Lua编写的nginx服务器微信公众平台代理.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Lua 100.0%

AltStyle によって変換されたページ (->オリジナル) /