
https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index




NATAPP1分钟快速新手图文教程: https://natapp.cn/article/natapp_newbie
下载: https://natapp.cn/#download
使用本地配置文件config.ini: https://natapp.cn/article/config_ini
注意每次打开程序域名都会变化;


https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#2
2 第二步:通过 code 换取网页授权access_token
4 第四步:拉取用户信息(需 scope 为 snsapi_userinfo)

HttpClient4Util 用来发http请求;
https://www.cnblogs.com/xiaoqigui/p/16839536.html
application.yml
#端口
server:
port: 8096
# 自定义微信授权信息
wechat:
auth:
app-id: wxd4e20add67****** # appID
app-secret: a21e97d21d0d6ce408b7a6c****** # appsecret
code-uri: https://open.weixin.qq.com/connect/oauth2/authorize # 请求微信官方获取用户授权code 的请求地址
redirect-uri: http://******.natappfree.cc/wechat/auth/codeBack # 微信官方返回 用户授权code 的回调地址
access-token-uri: https://api.weixin.qq.com/sns/oauth2/access_token # 根据微信回调的code值,请求微信官方获取用户access_token 的请求地址
user-info-uri: https://api.weixin.qq.com/sns/userinfo #根据用户的 accessToken 和 openId 拉取用户信息 的请求地址
//自定义微信授权参数信息配置类
@Data
@Component
@ConfigurationProperties(prefix = "wechat.auth")
public class WeChatAuthConfig {
/*
应用id
*/
private String appId;
/*
应用密钥
*/
private String appSecret;
/*
请求获取code的地址
*/
private String codeUri;
/*
微信官方回调code的地址
*/
private String redirectUri;
/**
* 微信官方获取access_token地址
*/
private String accessTokenUri;
/*
微信官方获取userInfo地址
*/
private String userInfoUri;
}
/**
* Created On : 28/10/2022.
* <p>
* Author : huayu
* <p>
* Description: 微信授权的业务接口
*/
public interface WeChatAuthService {
//生成请求微信官方获取用户授权code的请求地址
//根据微信回调的code值,请求微信官方获取用户access_token
//根据用户的 accessToken 和 openId 拉取用户信息
}
/**
* @author : huayu
* @date : 28/10/2022
* @param : []
* @return : java.lang.String
* @description : 生成请求微信官方获取用户授权code的请求地址
*/
String generateWeChatAuthCodeUrl();
/**
* @author : huayu
* @date : 28/10/2022
* @param : [wechatAuthCode]
* @return : java.lang.String
* @description : 根据微信回调的code值,请求微信官方获取用户access_token
*/
String getAccessTokenFromWechatUseCode(String wechatAuthCode);
/**
* @author : huayu
* @date : 28/10/2022
* @param : [accessToken, openId]
* @return : java.lang.String
* @description : 根据用户的 accessToken 和 openId 拉取用户信息
*/
String getUserInfoFromWechatUseAccessToken(String accessToken,String openId);
/**
* Created On : 28/10/2022.
* <p>
* Author : huayu
* <p>
* Description: 微信授权的业务接口 实现类
*/
@Service
@Slf4j
public class WeChatAuthServiceImpl implements WeChatAuthService{
//注入 http请求工具类
@Autowired
private WeChatAuthConfig weChatAuthConfig;
//生成请求微信官方获取用户授权code的请求地址
//根据微信回调的code值,请求微信官方获取用户access_token
//根据用户的 accessToken 和 openId 拉取用户信息
}
/**
* @author : huayu
* @date : 29/10/2022
* @param : []
* @return : java.lang.String
* @description : 生成请求微信官方获取用户授权code的请求地址
*/
@Override
public String generateWeChatAuthCodeUrl() {
//微信官方引导用户打开授权页面,获取code的完整路径
//https://open.weixin.qq.com/connect/oauth2/authorize
// ?appid=APPID
// &redirect_uri=REDIRECT_URI
// &response_type=code
// &scope=SCOPE
// &state=STATE
// #wechat_redirect
//尤其注意:由于授权操作安全等级较高,所以在发起授权请求时,微信会对授权链接做正则强匹配校验,如果链接的参数顺序不对,授权页面将无法正常访问
//生成请求卫星官方获取用户code的完整地址
StringBuilder weCharAuthCodeUrl = new StringBuilder(weChatAuthConfig.getCodeUri());
weCharAuthCodeUrl.append("?appid=").append(weChatAuthConfig.getAppId())
.append("&redirect_uri=").append(weChatAuthConfig.getRedirectUri())
.append("&response_type=code")
//&scope=snsapi_userinfo&state=STATE
.append("&scope=").append("snsapi_userinfo")
.append("&state=").append("kh96_wechat_auth")
.append("#wechat_redirect");
log.info("------ 请求微信官方授权网站地址:{} ------",weCharAuthCodeUrl.toString());
//返货完整的请求地址
return weCharAuthCodeUrl.toString();
}
/**
* @author : huayu
* @date : 29/10/2022
* @param : [wechatAuthCode]
* @return : java.lang.String
* @description : 根据微信回调的code值,请求微信官方获取用户access_token
*/
@Override
public String getAccessTokenFromWechatUseCode(String wechatAuthCode) {
// 尤其注意:由于公众号的 secret 和获取到的access_token安全级别都非常高,必须只保存在服务器,不允许传给客户端。
// 请求方法:获取 code 后,请求以下链接获取access_token:
// https://api.weixin.qq.com/sns/oauth2/access_token
// ?appid=APPID
// &secret=SECRET
// &code=CODE
// &grant_type=authorization_code
// 封装根据code,请求微信官方获取access_token的完整地址
StringBuilder accessTokenUrl = new StringBuilder(weChatAuthConfig.getAccessTokenUri());
accessTokenUrl.append("?appid=").append(weChatAuthConfig.getAppId())
.append("&secret=").append(weChatAuthConfig.getAppSecret())
.append("&code=").append(wechatAuthCode)
.append("&grant_type=authorization_code");
log.info("------ 根据code,请求微信官方获取access_token的完整地址:{} ------", accessTokenUrl.toString());
// 根据code,请求微信官方获取access_token,返回结果是同步返回的,不再是异步回调
// 请求是服务器内部发起的,也就是说:在程序中,要根据上面完整的请求地址,主动发送请求到微信官方,接口同步会返回一个json格式的字符串结果,程序内要解析获取的结果
// 程序内主动发起http请求,获取access_token
return HttpClient4Util.getResponse4GetAsString(accessTokenUrl.toString(), "utf-8");
}
/**
* @author : huayu
* @date : 29/10/2022
* @param : [accessToken, openId]
* @return : java.lang.String
* @description : 根据用户的 accessToken 和 openId 拉取用户信息
*/
@Override
public String getUserInfoFromWechatUseAccessToken(String accessToken, String openId) {
// 如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和 openid 拉取用户信息了。
// http:GET(请使用 https 协议):
// https://api.weixin.qq.com/sns/userinfo
// ?access_token=ACCESS_TOKEN
// &openid=OPENID
// &lang=zh_CN
// 封装根据accessToken和openId,请求微信官方获取用户信息详情地址
StringBuilder userInfoUrl = new StringBuilder(weChatAuthConfig.getUserInfoUri());
userInfoUrl.append("?access_token=").append(accessToken)
.append("&openid=").append(openId)
.append("&lang=zh_CN");
log.info("------ 根据access_token,请求微信官方获取userinfo的完整地址:{} ------", userInfoUrl.toString());
// 程序内主动发起http请求,获取用户详情
return HttpClient4Util.getResponse4GetAsString(userInfoUrl.toString(), "utf-8");
}
/**
* Created On : 28/10/2022.
* <p>
* Author : huayu
* <p>
* Description: 测试微信授权登录操作入口
*/
//@SuppressWarnings("all")
@Slf4j
@RestController
@RequestMapping("/wechat/auth")
public class WeChatAuthController {
//注入service层实现类
@Autowired
private WeChatAuthService weChatAuthService;
}
/**
* @author : huayu
* @date : 28/10/2022
* @param : []
* @return : com.kgc.scd.uitl.RequestResult<java.lang.String>
* @description : 获取请求微信官方货物code的完整地址,用户访问该地址,可以进行授权操作(把地址交给前端生成二维码给用户扫码,或者后端生成)
*/
@GetMapping("/codeUrl")
public RequestResult<String> codeUrl(){
//调用业务接口,获取完整用户授权访问的地址
return ResultBuildUtil.success(weChatAuthService.generateWeChatAuthCodeUrl());
}
用户授权后,接收微信官方异步回调请求,获取用户授权的code:(code,state)
1.通过 code 换取网页授权access_token
2.拉取用户信息(需 scope 为 snsapi_userinfo)
3.接口返回用户详情信息
/**
* @author : huayu
* @date : 28/10/2022
* @param : []
* @return : com.kgc.scd.uitl.RequestResult<java.util.Map<java.lang.String,java.lang.Object>>
* @description : 接收微信官方异步回调请求,获取用户授权的code
* 流程:用户先根据上一步返回请求地址,进行授权操作,如果用户统一授权,微信官方自动根据上一步请求带过去的回调地址redirectUri,进行结果回调
*/
@RequestMapping("/codeBack")
public RequestResult<Map<String, Object>> codeBack(HttpServletRequest request){
// 用户同意授权后,如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。
// code说明:code作为换取access_token的票据,每次用户授权带上的 code 将不一样,code只能使用一次,5分钟未被使用自动过期。
// 从官方回调的请求中,获取用户授权后的code参数值
String wechatAuthCode = request.getParameter("code");
// 从官方回调的请求中,获取用户授权时的自定义参数state
String wechatAuthState = request.getParameter("state");
log.info("------ 微信授权后,官方异步回调结果:code={},state={} ------", wechatAuthCode, wechatAuthState);
// 定义接口返回集合对象
Map<String, Object> resultMap = new HashMap<>();
// 参数非空校验
if(StringUtils.isBlank(wechatAuthCode)){
resultMap.put("msg", "授权code为空!");
return ResultBuildUtil.fail(resultMap);
}
// 1. 调用业务接口,通过 code 换取网页授权access_token
String accessTokenJson = weChatAuthService.getAccessTokenFromWechatUseCode(wechatAuthCode);
log.info("------ 通过 code 换取网页授权access_token返回结果:{} ------", accessTokenJson);
// 正确时返回的 JSON 数据包如下:
// {"access_token":"ACCESS_TOKEN","expires_in":7200,"refresh_token":"REFRESH_TOKEN","openid":"OPENID","scope":"SCOPE"}
// 错误时返回的 JSON 数据包如下:
// {"errcode":40029,"errmsg":"invalid code"}
// 解析返回的json数据
JSONObject accessTokenJsonObj = JSON.parseObject(accessTokenJson);
// 判断获取access_token结果是否正确,如果错误,直接结束,如果正确,获取对应的access_token
if(StringUtils.isNotBlank(accessTokenJsonObj.getString("errcode"))){
resultMap.put("wxCode", accessTokenJsonObj.getString("errcode"));
resultMap.put("wxMsg", accessTokenJsonObj.getString("errmsg"));
return ResultBuildUtil.fail(resultMap);
}
// 2. 拉取用户信息(需 scope 为 snsapi_userinfo)
// 根据上一步返回json,获取拉取用户信息凭证-access_token和用户唯一标识-openid
String accessToken = accessTokenJsonObj.getString("access_token");
String openId = accessTokenJsonObj.getString("openid");
// 调用业务接口,通过access_token和openId,拉取用户详情
String userInfoJson = weChatAuthService.getUserInfoFromWechatUseAccessToken(accessToken, openId);
log.info("------ 通过access_token和openId,拉取用户详情:{} ------", userInfoJson);
// 3.接口返回用户详情信息
resultMap.put("userInfo", userInfoJson);
// TODO 获取成功用户信息后,系统要完成静默注册-把用户信息注册到系统数据中,存储用户的头像,昵称,openId信息,并给系统用户表增加其它的基本信息
//返回用户详情
return ResultBuildUtil.success(resultMap);
}


重新查看appID和appsecret,可能是因为这里发生了改变;

8.1 配置文件
# 添加 检验授权凭证(access_token)是否有效 的请求地址
wechat:
auth:
check-access-token-uri: https://api.weixin.qq.com/sns/auth
/*
检验授权凭证(access_token)是否有效 的请求地址
*/
private String checkAccessTokenUri;
/**
* @author : huayu
* @date : 29/10/2022
* @param : [accessToken,openid]
* @return : String
* @description : 根据 accessToken 和 openid 校验授权凭证(access-token)是否有效
*/
String checkAccessToken(String accessToken,String openid);
/**
* @author : huayu
* @date : 29/10/2022
* @param : [accessToken,String openid]
* @return : String
* @description : 根据 accessToken,String openid 校验授权凭证(access-token)是否有效
*/
@Override
public String checkAccessToken(String accessToken,String openid) {
//https://api.weixin.qq.com/sns/auth
// ?access_token=ACCESS_TOKEN
// &openid=OPENID
//根据 accessToken openid 校验授权凭证(access-token)是否有效
StringBuilder checkAccessTokenUri = new StringBuilder(weChatAuthConfig.getCheckAccessTokenUri());
checkAccessTokenUri.append("?access_token=").append(accessToken)
.append("&openid=").append(openid);
log.info("------ 校验授权凭证(access-token)是否有效的完整地址:{} ------", checkAccessTokenUri.toString());
// 程序内主动发起http请求,获取用户详情
return HttpClient4Util.getResponse4GetAsString(checkAccessTokenUri.toString(),"utf-8");
}
/**
* @author : huayu
* @date : 29/10/2022
* @param : [accessToken, openid]
* @return : com.kgc.scd.uitl.RequestResult<java.lang.String>
* @description : 校验授权凭证(access-token)是否有效
*/
@GetMapping("/checkAccessToken")
public RequestResult<String> checkAccessToken(String accessToken,String openid){
//调取业务接口 校验授权凭证(access-token)是否有效
String checkAccessTokenResult = weChatAuthService.checkAccessToken(accessToken, openid);
log.info("------ 根据accessToken 和 openid,校验授权凭证(access-token)是否有效:{} ------",accessToken,openid,checkAccessTokenResult);
//返回说明
//正确的 JSON 返回结果:
//{ "errcode":0,"errmsg":"ok"}
//错误时的 JSON 返回示例:
//{ "errcode":40003,"errmsg":"invalid openid"}
log.info("------ errcode:{} ------",JSONObject.parseObject(checkAccessTokenResult).getString("errcode"));
return JSONObject.parseObject(checkAccessTokenResult).getString("errcode").equals("0") ?
ResultBuildUtil.success("access_token 有效")
: ResultBuildUtil.success("access_token 无效");
}

wechat:
auth:
refresh-access-token-uri: https://api.weixin.qq.com/sns/oauth2/refresh_token #刷新access_token 的请求地址
grant-type: refresh_token #刷新access_token 的请求地址 的 grant_type 参数值
/*
刷新access_token 的请求地址
*/
private String refreshAccessTokenUri;
/*
刷新access_token 的请求地址 的 grant_type 参数值
*/
private String grantType;
/**
* @author : huayu
* @date : 29/10/2022
* @param : [refreshToken]
* @return : java.lang.String
* @description : 根据 refreshToken 刷新access_token
*/
String refreshToken(String refreshToken);
/**
* @author : huayu
* @date : 29/10/2022
* @param : [refreshToken]
* @return : java.lang.String
* @description : 根据 refreshToken 刷新access_token
*/
@Override
public String refreshToken(String refreshToken) {
//https://api.weixin.qq.com/sns/oauth2/refresh_token
// ?appid=APPID
// &grant_type=refresh_token
// &refresh_token=REFRESH_TOKEN
StringBuilder refreshAccessTokenUri = new StringBuilder(weChatAuthConfig.getRefreshAccessTokenUri());
refreshAccessTokenUri.append("?appid=").append(weChatAuthConfig.getAppId())
.append("&grant_type=").append(weChatAuthConfig.getGrantType())
.append("&refresh_token=").append(refreshToken);
log.info("------ refreshToken 刷新access_token的完整地址:{} ------", refreshAccessTokenUri.toString());
// 程序内主动发起http请求,获取用户详情
return HttpClient4Util.getResponse4GetAsString(refreshAccessTokenUri.toString(),"utf-8");
}
/**
* @author : huayu
* @date : 29/10/2022
* @param : [accessToken]
* @return : com.kgc.scd.uitl.RequestResult<java.lang.String>
* @description : 刷新access_token根据官方接口文档对接
*/
@GetMapping("/refreshAccessToken")
public RequestResult<String> refreshAccessToken(@RequestParam("refreshToken") String refreshToken){
//调取业务接口 刷新access_token
String refreshTokenResult = weChatAuthService.refreshToken(refreshToken);
log.info("------ refreshToken:{},刷新access_token根据官方接口文档对接:{} ------",refreshToken,refreshTokenResult);
//正确时返回的 JSON 数据包如下:
//{
// "access_token":"ACCESS_TOKEN",
// "expires_in":7200,
// "refresh_token":"REFRESH_TOKEN",
// "openid":"OPENID",
// "scope":"SCOPE"
//}
//错误时微信会返回 JSON 数据包如下(示例为 code 无效错误):
//{"errcode":40029,"errmsg":"invalid code"}
return JSON.parseObject(refreshTokenResult).getString("expires_in").equals("7200")
? ResultBuildUtil.success("access_token 刷新成功")
: ResultBuildUtil.success("access_token 刷新失败");
}

大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数
有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url
我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge
我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c
我安装了ruby版本管理器,并将RVM安装的ruby实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby。有没有办法让emacs像shell一样尊重ruby的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit
我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur