微信考勤百度地图定位,错误修正
最后更新于:2022-04-01 07:06:25
在使用百度地图进行微信考勤时,遇到很多问题,尤其是定位问题,我知道定位有偏差,但使用百度地图,几十次后,偶尔一次会错的离谱,例如直接就定位到了外省,例如我一直在西安,一次定位到了天津,一次定位到了石家庄,我不知道为什么会这样。偏差几千米还能理解,可偏差几百公里就是在让人不能理解了。是因为我用的百度API是免费的吗?当然出现的次数不多,一般情况下,四五十次,会偶尔出现一两次这么离谱的偏差。
实在找不到原因,也不能是缓存,因为我没有去过石家庄,天津。没办法,但这个问题,又不能不解决,于是只能把四种计算经纬度的方法都列出来,高德地图的经纬度,HTML5的经纬度,经过百度转化的经纬度,百度地图的经纬度,都列出来,看看到底那一块出错了。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757deaa71d.jpg)
发现高德的经纬度和HTML5的经纬度基本相同,HTML5的经纬度经过转化的经纬度和百度地图的经纬度基本相同。
这四个经纬度的具体差异参考[百度地图,高德地图,HTML5经纬度比较](http://blog.csdn.net/xuexiaodong009/article/details/49246459)
经过实际测试,百度地图获取的周边信息比高德地图获取的周边信息要准不少,高德地图获取的周边信息经常偏差四五公里,但百度地图就会好很多,基本上在一公里以内。
因此我还是决定使用百度地图,但百度地图偶尔错的离谱如何修正呢?
百度地图一次错的离谱的定位:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757debdffa.jpg)
既然决定使用百度地图定位,那么这个错误就不能不修正,但如何修正呢?使用高德地图,高德地图虽然没有这么离谱的事,但我测试,误差经常是四五公里,实在是太大了。
那有没有更好的方法呢?
最终我发现,在百度地图没有问题时,获取的省市区和高德地图的省市区是一致的,在百度地图出现异常时,省市区是不一致的,因此我采用了,以百度地图为主,高德地图为辅的方式,修正了百度地图的偶尔出现的错误,在发现错误时,让员工刷新重新获取地理信息,来修正百度地图的这个偶尔出现的错误。其实也可以采用比较HTML5的经纬度和百度地图经纬度比较的方式进行修正,但我目前没有采用。
参考文章
[关于地图坐标和定位偏差](http://www.cnblogs.com/mengdd/p/3463919.html)
[中国地图偏移问题](http://blog.csdn.net/caz28/article/details/8143609)
百度地图,高德地图,HTML5经纬度比较
最后更新于:2022-04-01 07:06:22
**对于一个地点的经纬度,是确定的?这个问题,我想很多人都会回答,肯定了,可实际上呢?我只能呵呵了。**
在使用百度地图的过程中,发现一个很奇怪的现象,有时候调用百度地图js API时,后得到一个错的离谱的地方。然后我自己也就研究了一下jsAPI获取地理位置的相关信息,jsAPI其实都是利用了HTML5中的方法获取地理信息的。但很奇怪,使用HTML5获取到的经纬度和百度获取的竟然相差很大。
HTML5获取到的经纬度
~~~
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
}
else {alert( "Geolocation is not supported by this browser.") }
}
function showPosition(position)
{
$("#lngl").val(position.coords.longitude );
$("#latl").val(position.coords.latitude);
}
~~~
百度地图获取经纬度的方法
~~~
// 百度地图API功能
var map = new BMap.Map("allmap");
var circle = new BMap.Geolocation();
var options={};
options.enableHighAccuracy=true;
options.timeout=10;
options.maximumAge=0;
circle.getCurrentPosition(locationResult, options); //enableHighAccuracy Boolean 要求浏览器获取最佳结果。,timeout Number 超时时间。,maximumAge Number 允许返回指定时间内的缓存结果。如果此值为0,则浏览器将立即获取新定位结果。
map.addOverlay(circle);
var tempGeocoder = new BMap.Geocoder();
function locationResult(geolocationResult) {
var Status = this.getStatus()
if (Status == 0)//检索成功。对应数值“0”。
{
$("#lngBaidu").val(geolocationResult.point.lng);
$("#latBaidu").val(geolocationResult.point.lat);
var address = geolocationResult.address;
$("#cityBaidu").val(address.city);
$("#districtBaidu").val(address.district);
$("#streetBaidu").val(address.street);
else {
alert("定位失败错误码" + Status)
}
}
~~~
高德地图获取经纬的方法
~~~
// 高德地图API功能
var mapObj, geolocation;
var MGeocoder;
mapObj = new AMap.Map('allmap1');
mapObj.plugin('AMap.Geolocation', function () {
geolocation = new AMap.Geolocation({
enableHighAccuracy: true, //是否使用高精度定位,默认:true
timeout: 10000, //超过10秒后停止定位,默认:无穷大
maximumAge: 0, //定位结果缓存0毫秒,默认:0
convert: false, //自动偏移坐标,偏移后的坐标为高德坐标,默认:true
showButton: false, //显示定位按钮,默认:true
buttonPosition: 'LB', //定位按钮停靠位置,默认:'LB',左下角
buttonOffset: new AMap.Pixel(10, 20), //定位按钮与设置的停靠位置的偏移量,默认:Pixel(10, 20)
showMarker: false, //定位成功后在定位到的位置显示点标记,默认:true
showCircle: false, //定位成功后用圆圈表示定位精度范围,默认:true
panToLocation: true, //定位成功后将定位到的位置作为地图中心点,默认:true
zoomToAccuracy: true //定位成功后调整地图视野范围使定位位置及精度范围视野内可见,默认:false
});
mapObj.addControl(geolocation);
AMap.event.addListener(geolocation, 'complete', onComplete); //返回定位信息
AMap.event.addListener(geolocation, 'error', onError); //返回定位出错信息
geolocation.getCurrentPosition();
});
function onComplete(data) {
var lnglatXY = [data.position.getLng(), data.position.getLat()];
$("#accuracy").val(data.accuracy);
$("#lng").val(data.position.getLng());
$("#lat").val(data.position.getLat());
}
function onError(data) {
var str = '定位失败;';
str += '错误信息:'
switch (data.info) {
case 'PERMISSION_DENIED':
str += '浏览器阻止了定位操作';
break;
case 'POSITION_UNAVAILBLE':
str += '无法获得当前位置';
break;
case 'TIMEOUT':
str += '定位超时';
break;
default:
str += '未知错误';
break;
}
alert(str);
}
~~~
使用这三种方法获取的经纬度竟然有很大不同!!
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757de98304.jpg)
小数点后第一位就有可能不一样了,小数点后第二位是肯定不一样的,0.01的偏差就会导致几十公里的偏差,测试多次依然是这样。
最后在百度地图的官方文档找到了这样的的话
百度地图坐标转换API是一套以HTTP形式提供的坐标转换接口,用于将常用的非百度坐标(目前支持GPS设备获取的坐标、google地图坐标、soso地图坐标、amap地图坐标、mapbar地图坐标)转换成百度地图中使用的坐标,并可将转化后的坐标在百度地图JavaScript API、车联网API、静态图API、web服务API等产品中使用。注意Android SDK、iOS SDK、定位SDK和导航SDK坐标转换服务需[单独申请](http://developer.baidu.com/map/index.php?title=open/help_index)
[百度地图官方文档](http://developer.baidu.com/map/index.php?title=webapi/guide/changeposition)
才知道HTML5得到的数据是需要转化之后才是百度地图的经纬度。难怪测试了很多次,几乎都有这么大的偏差。
HTML5经纬度转化为百度地图经纬度的方法:
~~~
var ggPoint = new BMap.Point(position.coords.longitude, position.coords.latitude);//HTML5的经纬度
var convertor = new BMap.Convertor();
var pointArr = [];
pointArr.push(ggPoint);
convertor.translate(pointArr, 1, 5, translateCallback)
}
//坐标转换完之后的回调函数
translateCallback = function (data) {
if (data.status === 0) {
// data.points[0];
//转换后的百度坐标(正确)
$("#lngz").val(data.points[0].lng);
$("#latz").val(data.points[0].lat);
}
}
~~~
经过这么转化后,偏差基本上在小数点后前三位基本上都一样了。
经过测试发现,高德地图似乎直接使用了HTML5返回的经纬度,但没找到相关的文档,百度地图使用的是转化过的经纬度。一个简单的经纬度问题却由于各种原因导致了这么一个背离常识的问题,实在是令人难以理解!!
[如何将非高德坐标转换为高德坐标系](http://lbsbbs.amap.com/forum.php?mod=viewthread&tid=724&extra=page%3D1)
[百度地图坐标转换](http://developer.baidu.com/map/index.php?title=webapi/guide/changeposition)
微信考勤百度地图定位二
最后更新于:2022-04-01 07:06:20
使用[微信考勤百度地图定位](http://blog.csdn.net/xuexiaodong009/article/details/48345445)中的方法定位,可以定位到一个具体的位置某省某市某区某路某号,总是让人感觉显示不是很友好,如果直接显示,软件园,科技园之类的是不是更好呢?于是查了一下百度地图的相关文档,其实也很简单。百度有附近的功能,还有地址解析的功能,都可以实现。
例如我就是用了Geocoder服务,实现了需要的效果。
**核心代码:**
~~~
var map = new BMap.Map("allmap");
var circle = new BMap.Geolocation();
circle.getCurrentPosition(locationResult); //
map.addOverlay(circle);
var tempGeocoder = new BMap.Geocoder();
function locationResult(geolocationResult) {
var Status = this.getStatus()
if (Status == 0)//检索成功。对应数值“0”。
{
$("#lng").val(geolocationResult.point.lng);
$("#lat").val(geolocationResult.point.lat);
var address = geolocationResult.address;
$("#city").val(address.city);
$("#district").val(address.district);
$("#street").val(address.street);
var text = "";
if (address.province != address.city)
{
text += address.province;
}
text += address.city + address.district + address.street + address.street_number;
tempGeocoder.getLocation(geolocationResult.point, locationResultcallback, { poiRadius: 500, numPois: 5 }); //
$("#province").val(address.province);
$("#address").val(text);
}
else {
alert("定位失败错误码" + Status)
}
}
function locationResultcallback(GeocoderResult) {
var yyy = GeocoderResult.surroundingPois;
if (GeocoderResult.surroundingPois.length > 0) {
var address2 = GeocoderResult.surroundingPois[0].title;
if (address2) {
var text = "";
var province = $("#province").val();
var city = $("#city").val();
if (province != city) {
text += province;
}
text += city;
$("#address").val(text + address2);
}
}
}
~~~
实现效果:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757de785ff.jpg)
**这样总比显示陕西省西安市雁塔区西三环好好的多吧。**
微信考勤Cookies的使用
最后更新于:2022-04-01 07:06:18
使用微信考勤,每次使用[微信企业号开发:微信用户信息和web网页的session的关系](http://blog.csdn.net/xuexiaodong009/article/details/47747569)这个里边的方法,调用微信的接口,有点慢,微信官方也推荐使用Cookies,但如何使用Cookies,自己却一直没有搞清楚。
原来一直以为在服务端获取客户端的数据有两个方法,一种就是查询字符串放在URL上,一种就是放在form中,post提交,自己以前也使用过但主要是在客户端使用,从来没法把
Cookies中的数据直接提交到服务端,即使有也是通过把Cookies中的数据读取出来后放入form中的隐藏字段,然后post到服务端。
显然微信考勤这类其实就是一个URL,在进入URL的过程中,没有什么post数据的过程。只有进入URL之后再通过用户提交,或者ajax提交。总之似乎没法直接把Cookies中的数据直接提交给服务端。似乎陷入了僵局。于是自己再一次研究了Cookies,发现Cookies似乎是主动提交到服务端的,但和post是提交的位置不一样,当然我没有找到相关文档,是测试发现的。只要自己设定了Cookies,每次进入URL都会提交Cookies,自然就可以在服务端读取到Cookies的值了。这时才真正明白记住密码的真正实现原理。并不是把Cookies的值读出来,放入隐藏字段,然后通过ajax提交到服务端,就可以免登陆了。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757de5bee5.jpg)
可以看到Cookies的数据到了服务端,sessionID也是通过Cookies这种方式传到服务端的。
前端js读取,设置Cookies的方法:
~~~
function setCookie(name, value) {//两个参数,一个是cookie的名子,一个是值
var Days = 30; //此 cookie 将被保存 30 天
var exp = new Date(); //new Date("December 31, 9998");
exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000);
document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString();
}
function getCookie(name) {//取cookies函数
var arr = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)"));
if (arr != null) return unescape(arr[2]); return null;
}
~~~
CSharp服务端操作Cookies:
设置Cookies
~~~
HttpCookie cookie = new HttpCookie("UserCode", username);
cookie.Expires = DateTime.Now.AddDays(10);// (365 * 24 * 3600);
this.Response.AppendCookie(cookie);
HttpCookie cookieDeviceId = new HttpCookie("DeviceId", rt.DeviceId);
cookieDeviceId.Expires = DateTime.Now.AddDays(10);// (365 * 24 * 3600);
this.Response.AppendCookie(cookieDeviceId);
~~~
读取Cookies:
~~~
HttpCookie ttHttpCookie = this.Request.Cookies.Get("UserCode");
HttpCookie ttHttpCookieDeviceId = this.Request.Cookies.Get("DeviceId");
string code = Request.QueryString["code"];
if (ttHttpCookie == null || ttHttpCookieDeviceId == null)
{
WeiApi(code);
}
else {
string username = ttHttpCookie.Value;
string DeviceId = ttHttpCookieDeviceId.Value;
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(DeviceId))
{
WeiApi(code);
}
else {
new AppException("读取Cookies UserCode=" + username + ",DeviceId=" + DeviceId);
initSession(username, DeviceId);
}
}
~~~
微信考勤百度地图定位
最后更新于:2022-04-01 07:06:16
之前在[微信企业号开发:微信考勤](http://blog.csdn.net/xuexiaodong009/article/details/47952239)中使用了百度地图的定位组件,但发现在部分手机上会出现定位失败的提示,于是有研究了一下百度地图。原来使用的Web组件百度不打算更新了,也是重新查了一下百度地图的其他API,还有一个JavaScript API大众版,于是试了试,没想到竟然解决了。
核心代码很简单:
~~~
<div id="allmap"></div>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=kkkk"></script>
<script type="text/javascript">
// 百度地图API功能
var map = new BMap.Map("allmap");
var circle = new BMap.Geolocation();
circle.getCurrentPosition(locationResult); //
map.addOverlay(circle);
function locationResult(geolocationResult) {
var Status = this.getStatus()
if (Status == 0)//检索成功。对应数值“0”。
{
$("#lng").val(geolocationResult.point.lng);
$("#lat").val(geolocationResult.point.lat);
var address = geolocationResult.address;
$("#city").val(address.city);
$("#district").val(address.district);
$("#street").val(address.street);
$("#address").val(address.province + address.city + address.district + address.street + address.street_number);
}
else {
alert("定位失败错误码" + Status)
}
}
</script>
~~~
实现效果
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757de3b869.jpg)
微信考勤摇一摇考勤
最后更新于:2022-04-01 07:06:13
看到网上又不少微信企业号的摇一摇考勤,自己也想做一个,但查遍了微信企业号文档,也没有看到摇一摇的相关API,本以为做不出来了,想不到再问了同事后,才知道其实很简单,摇一摇不需要微信企业号的文档,HTML5就有,摇一摇其实就是相当于点击了保存按钮而已。
其实获取地理位置HTML5也支持。
HTML5 - 使用地理定位
~~~
<script>
var x=document.getElementById("demo");
function getLocation()
{
if (navigator.geolocation)
{
navigator.geolocation.getCurrentPosition(showPosition);
}
else{x.innerHTML="Geolocation is not supported by this browser.";}
}
function showPosition(position)
{
x.innerHTML="Latitude: " + position.coords.latitude +
"<br />Longitude: " + position.coords.longitude;
}
</script>
~~~
摇一摇的核心代码:
~~~
/需要判断浏览器是否支持
if (window.DeviceMotionEvent) {
window.addEventListener('devicemotion', deviceMotionHandler, false);
} else {
$("#shake").html('您的手机现在还不支持摇一摇功能。');
}
function deviceMotionHandler(eventData) {
var acceleration = eventData.accelerationIncludingGravity;
var curTime = new Date().getTime(); //获取当前时间戳
var diffTime = curTime - last_update;
if (diffTime > 100) {
last_update = curTime; //记录上一次摇动的时间
x = acceleration.x; //获取加速度X方向
y = acceleration.y; //获取加速度Y方向
z = acceleration.z; //获取加速度垂直方向
var speed = Math.abs(x + y + z - last_x - last_y - last_z) / diffTime * 10000; //计算阈值
if (speed > SHAKE_THRESHOLD) {
btnSave();
}
}
//记录上一次加速度
last_x = x;
last_y = y;
last_z = z;
}
~~~
完整的摇一摇考勤代码:
~~~
<!doctype html>
<html>
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>考勤打卡</title>
<script src="http://api.map.baidu.com/components?ak=1IYR&v=1.0"></script>
<link rel="stylesheet" href="../assets/css/amazeui.min.css">
<link rel="stylesheet" href="../assets/css/app.css">
<script src="../assets/js/jquery.min.js" type="text/javascript"></script>
<script src="../assets/js/amazeui.min.js" type="text/javascript"></script>
<script src="../assets/js/amazeui.widgets.helper.min.js" type="text/javascript"></script>
<script type="text/javascript">
var SHAKE_THRESHOLD = 3000; //定义一个摇动的阈值
var last_update = new Date().getTime(); //定义一个变量记录上一次摇动的时间
var x = y = z = last_x = last_y = last_z = 0; //定义x、y、z记录三个轴的数据以及上一次触发的时间
$(document).ready(function () {
$("#btnSave").click(function (e) { // 绑定保存按钮
btnSave();
})
//需要判断浏览器是否支持
if (window.DeviceMotionEvent) {
window.addEventListener('devicemotion', deviceMotionHandler, false);
} else {
$("#shake").html('您的手机现在还不支持摇一摇功能。');
}
var Name = $("#Name").val();
if (!Name) {//没有session
$("#session").show();
}
});
function deviceMotionHandler(eventData) {
var acceleration = eventData.accelerationIncludingGravity;
var curTime = new Date().getTime(); //获取当前时间戳
var diffTime = curTime - last_update;
if (diffTime > 100) {
last_update = curTime; //记录上一次摇动的时间
x = acceleration.x; //获取加速度X方向
y = acceleration.y; //获取加速度Y方向
z = acceleration.z; //获取加速度垂直方向
var speed = Math.abs(x + y + z - last_x - last_y - last_z) / diffTime * 10000; //计算阈值
if (speed > SHAKE_THRESHOLD) {
btnSave();
}
}
//记录上一次加速度
last_x = x;
last_y = y;
last_z = z;
}
function btnSave() {
var formId = "form";
var isOk = Checkform(); //验证form
if (isOk == false) {
return;
}
$.ajax({ type: "post",
url: "KaoQinAjax.ashx?OperationType=kaoqin",
data: $(formId).serialize(),
success: function (obj) {
if (obj.IsSuccess == true) {
alertInfo(obj.Msg);
window.location = "KaoQinList.aspx";
}
else {
alertInfo(obj.Msg);
}
}
});
}
function Checkform() {
var address = $("#address").val();
if (!address) {
alertInfo("地理位置为空,请开打GPS,刷新所在位置");
return false;
}
return true;
}
function alertInfo(text) {
alert(text);
}
</script>
</head>
<body>
<form id="form1" runat="server" class="am-form" >
<fieldset>
<legend>考勤打卡</legend>
<input type="hidden" id="Name" name="Name" value="<%=Name%>" />
<div class="am-form-group">
<label for="doc-ta-1">所在位置 </label>
<p>
<lbs-geo id="geo" city="北京" enable-modified="false"></lbs-geo>
<input type="hidden" id="address" name="address"/>
<input type="hidden" id="lng" name="lng"/>
<input type="hidden" id="lat" name="lat"/>
</p>
</div>
<script>
// 先获取元素
var lbsGeo = document.getElementById('geo');
//监听定位失败事件 geofail
lbsGeo.addEventListener("geofail", function (evt) {
alert("地理位置为空,请开打GPS,刷新所在位置");
});
//监听定位成功事件 geosuccess
lbsGeo.addEventListener("geosuccess", function (evt) {
var address = evt.detail.address;
var coords = evt.detail.coords;
var x = coords.lng;
var y = coords.lat;
$("#address").val(address);
$("#lng").val(x);
$("#lat").val(y);
});
</script>
<div id="shake" style="font-size: 14px; margin: 10px; line-height: 35px;"></div>
<div id="session" style="font-size: 14px; margin: 10px; line-height: 35px;display:none">请关闭后,重新打开</div>
<button type="button" class="am-btn am-btn-primary am-btn-block" id="btnSave">不能摇一摇点击</button>
</fieldset>
</form>
</body>
</html>
~~~
实现效果
**![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757de19514.jpg)**
微信考勤2如何计算距离
最后更新于:2022-04-01 07:06:11
通过[微信企业号开发:微信考勤](http://blog.csdn.net/xuexiaodong009/article/details/47952239)中的方法计算出了用户的地理位置,我们知道GPS会有一定偏差,但如何把这个偏差考虑进去呢?例如在500米的偏差内都认为是在办公室内呢?
有两种方法,一种是使用根据两点经纬度计算距离的方法,一种是调用地图的API,
1根据两点经纬度计算距离的方法
核心代码:
~~~
private const double EARTH_RADIUS = 6378.137*1000;//地球半径,单位为米
private static double rad(double d)
{
return d * Math.PI / 180.0;
}
/// <summary>
/// 返回两点之间的距离,单位为米
/// </summary>
/// <param name="lat1"></param>
/// <param name="lng1"></param>
/// <param name="lat2"></param>
/// <param name="lng2"></param>
/// <returns></returns>
public static double GetDistance(double lat1, double lng1, double lat2, double lng2)
{
double radLat1 = rad(lat1);
double radLat2 = rad(lat2);
double a = radLat1 - radLat2;
double b = rad(lng1) - rad(lng2);
double s = 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin(a / 2), 2) +
Math.Cos(radLat1) * Math.Cos(radLat2) * Math.Pow(Math.Sin(b / 2), 2)));
s = s * EARTH_RADIUS;
s = Math.Round(s * 10000) / 10000;
return s;
}
~~~
[具体原理参考](http://www.cnblogs.com/ycsfwhh/archive/2010/12/20/1911232.html)
2调用地图的API
因为我调用的是百度地图,因此应该使用百度地图计算距离的方法
核心方法:
~~~
// 百度地图API功能
var map = new BMap.Map("allmap");
map.centerAndZoom("重庆",12); //初始化地图,设置城市和地图级别。
var pointA = new BMap.Point(106.486654,29.490295); // 创建点坐标A--大渡口区
var pointB = new BMap.Point(106.581515,29.615467); // 创建点坐标B--江北区
alert('从大渡口区到江北区的距离是:'+(map.getDistance(pointA,pointB)).toFixed(2)+' 米。'); //获取两点距离,保留小数点后两位
var polyline = new BMap.Polyline([pointA,pointB], {strokeColor:"blue", strokeWeight:6, strokeOpacity:0.5}); //定义折线
map.addOverlay(polyline); //添加折线到地图上
~~~
[具体参考](http://developer.baidu.com/map/jsdemo.htm#a6_1)
这两种方法会有一定的偏差,但可以接受。我个人认为百度计算出来的会更准确一些,毕竟地球不是标准的球形,百度应用这么广泛,应该已经修正了部分偏差。
例如对于点,Point(106.486654,29.490295),Point(106.581515,29.615467),百度计算出来的距离16670.90 米,通过经纬度计算出来的距离为16689.5796,偏差为20米,
还是可以接受的。
微信考勤
最后更新于:2022-04-01 07:06:09
使用微信考勤,有很多企业号应用都有,但如何实现呢?
核心有有两个,1其实就是获取用户位置,其实这个可以使用百度地图的API,当然其他的也可以,微信自己的地图实在是太差了,没有考虑。当然地理位置有偏差,如果需要进一步处理,我还没有找到合适的办法。当然了这些也只是表明曾经在某一个位置,并不表明一直都在,就好像打了卡,并不代表一直都在公司上班,也可能打卡后,就逛街去了。
2在获得了用户的位置信息后,在服务端如何知道到底是哪个员工的考勤呢?
1其实就是获取用户位置
使用百度地图的API很简单,[参考](http://developer.baidu.com/map/index.php?title=webcomponent/guide/geo)
需要注意的是一定要把百度地图的API的脚本放在 lbs-geo标签之下,否则就会有脚本错误。
例如:Cannot read property 'addEventListener' of null
核心代码:
~~~
<div class="am-form-group">
<label for="doc-ta-1">所在位置 </label>
<p>
<lbs-geo id="geo" city="北京" enable-modified="false"></lbs-geo>
<input type="hidden" id="address" class="am-form-field am-round" name="address"/>
<input type="hidden" id="lng" class="am-form-field am-round" name="lng"/>
<input type="hidden" id="lat" class="am-form-field am-round" name="lat"/>
</p>
</div>
<script>
// 先获取元素
var lbsGeo = document.getElementById('geo');
//监听定位失败事件 geofail
lbsGeo.addEventListener("geofail", function (evt) {
alert("fail");
});
//监听定位成功事件 geosuccess
lbsGeo.addEventListener("geosuccess", function (evt) {
var address = evt.detail.address;
var coords = evt.detail.coords;
var x = coords.lng;
var y = coords.lat;
// alert("地址:" + address);
// alert("地理坐标:" + x + ',' + y);
$("#address").val(address);
$("#lng").val(x);
$("#lat").val(y);
});
</script>
~~~
2获取用户信息,其实就是把微信的用户相关信息和系统中的相关信息关联起来。
这个可以参考[微信企业号开发:微信用户信息和web网页的session的关系](http://blog.csdn.net/xuexiaodong009/article/details/47747569)
实现效果
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757de03f3d.jpg)
微信用户信息和web网页的session的关系
最后更新于:2022-04-01 07:06:06
微信企业号的用户是需要验证的,因此能关注企业号的用户其实就是已经通过验证的用户,但企业应用中打开一个网页,在这个网页中如何根据微信用户的信息创建web应用中最长使用的session呢?微信用户如何和web的session关联起来呢?
例如:一个应用,根据不同的人员,显示不同的内容,各个网页之间需要session来传递一些信息,在微信企业号中如何处理呢?
这个问题需要涉及的接口是OAuth2验证接口,需要配置可信域名,初始化session。
一下以一个带有URL的菜单为例进行说明
**1根据OAuth2验证接口改写URL**
例如需要跳转到http://abc.def.com.cn:8082/index.aspx页面,则根据[OAuth验证接口](http://qydev.weixin.qq.com/wiki/index.php?title=OAuth%E9%AA%8C%E8%AF%81%E6%8E%A5%E5%8F%A3)说明,菜单的URL应该是
https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=http://abc.def.com.cn:8082/index.aspx&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
其中appid为corpid,请改为自己实际的参数值,response_type固定为code,scope固定为snsapi_base,#wechat_redirect不用改,直接加上就可以了。redirect_uri是需要跳转的URL,但需要urlencode处理,http://abc.def.com.cn:8082/index.aspx经过urlencode处理后为:http%3a%2f%2fabc.def.com.cn%3a8082%2findex.aspx,state不是必选的,可以填写a-zA-Z0-9的参数值组成的数据
因此菜单的URL应该为
https://open.weixin.qq.com/connect/oauth2/authorize?appid=myappid&redirect_uri=http%3a%2f%2fabc.def.com.cn%3a8082%2findex.aspx&response_type=code&scope=SCOPE&state=a#wechat_redirect
appid是myappid
response_type固定为code,scope固定为snsapi_base,state是a,
redirect_uri是http://abc.def.com.cn:8082/index.aspx,
经过urlencode后是http%3a%2f%2fabc.def.com.cn%3a8082%2findex.aspx
这样配置菜单的连接后,在微信中打开时,http://abc.def.com.cn:8082/index.aspx就会多一个查询字符串code,根据code就可以获取到打开这个微信用户的信息,然后就可以初始化web应用的session了。
**2需要配置可信域名**
再按照以第一步处理后,在微信端打开连接,会出现一个错误,这个是因为没有配置可信域名。
**redirect uri 参数错误**
需要在微信管理端配置可信域名,如果redirect_uri有端口号,那'可信域名'也必须加上端口号OAuth2验证接口
例如根据需要跳转的http://abc.def.com.cn:8082/index.aspx,配置可信域名如下,注意不要http
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dddf89a.jpg)
**3初始化session**
在进行了以上处理后,用户在点击菜单时,跳转的连接就会变为http://abc.def.com.cn:8082/index.aspx?code=3c452771ddfc0e75097d0509e0e555
也就是说多了一个查询字符串code,根据code就可以取到这个微信用户的UserId信息。具体参考[根据code获取成员信息](http://qydev.weixin.qq.com/wiki/index.php?title=OAuth%E9%AA%8C%E8%AF%81%E6%8E%A5%E5%8F%A3)
核心代码:
~~~
/// <summary>
/// 根据code获取成员信息
/// </summary>
/// <param name="userid"></param>
/// <returns></returns>
public static string GetUserInfo(string CODE)
{
// https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
string urlFormat = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token={0}&code={1}";
var url = string.Format(urlFormat, BLLAccessToken.GetAccessToken(), CODE);
string UserId = string.Empty;
WebUtils wut = new WebUtils();
//数据不用加密发送
LogInfo.Info("根据code获取成员信息: " + CODE);
string sendResult = wut.DoGet(url);
OAuthResult tempAccessTokenjson = Tools.JsonStringToObj<OAuthResult>(sendResult);
if (tempAccessTokenjson.HasError())
{
LogInfo.Error("根据code获取成员信息返回错误: " + Tools.ToJsonString<OAuthResult>(tempAccessTokenjson));
}
else
{
UserId = tempAccessTokenjson.UserId;
}
return UserId;
}
~~~
index.aspx网页后端代码:
~~~
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
string code = Request.QueryString["code"].ToLower().Trim();
if (!string.IsNullOrEmpty(code))
{
if (HttpContext.Current.Session["usersession"] != null) //session信息已经存在,直接返回
{
new AppException("usersession已经存在不需要在处理");
return;
}
string username= BLLUser.GetUserInfo(code);
if (!string.IsNullOrEmpty(username))
{
initSession(username);
new AppException("初始化initSession,code=" + code + ",username=" + username);
}
else
{
new AppException("收到信息异常username为空");
}
}
else {
new AppException("收到信息异常code为空");
}
}
}
~~~
接收消息和事件
最后更新于:2022-04-01 07:06:04
接收到的消息和事件,其实都是微信post到我们配置的URL的消息。接收普通消息就是用户给公众号发送的消息,事件是由于用户的特定操作,微信post给我们的消息。被动响应消息是我们收到微信post过来的普通消息或者是事件时,企业号通过Response.Write这种方式回复的消息。
**核心代码:**
把微信post过来的数据先解密,转为能处理的XML,再把XML转为对象
~~~
#region 将POST过来的数据转化成实体对象
/// <summary>
/// 将微信POST过来的数据转化成实体对象
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public static ReceiveMessageBase ConvertMsgToObject(string msgBody = "")
{
if (string.IsNullOrWhiteSpace(msgBody))
{
Stream s = System.Web.HttpContext.Current.Request.InputStream;
byte[] b = new byte[s.Length];
s.Read(b, 0, (int)s.Length);
msgBody = Encoding.UTF8.GetString(b);
}
string CorpToken = AppIdInfo.GetToken();
string corpId = AppIdInfo.GetCorpId();
string encodingAESKey = AppIdInfo.GetEncodingAESKey();
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(CorpToken, encodingAESKey, corpId);
string msg_signature = HttpContext.Current.Request.QueryString["msg_signature"];
string timestamp = HttpContext.Current.Request.QueryString["timestamp"];
string nonce = HttpContext.Current.Request.QueryString["nonce"];
string sMsg = ""; // 解析之后的明文
int flag = wxcpt.DecryptMsg(msg_signature, timestamp, nonce, msgBody, ref sMsg);
if (flag == 0)
{
msgBody = sMsg;
LogInfo.Info("解密后的消息为" + sMsg);
}
else
{
LogInfo.Error("解密消息失败!flag=" + flag);
}
if (string.IsNullOrWhiteSpace(msgBody))
{
return null;
}
XmlDocument doc = null;
MsgType msgType = MsgType.UnKnown;
EventType eventType = EventType.UnKnown;
ReceiveMessageBase msg = new ReceiveMessageBase();
msg.MsgType = msgType;
msg.MessageBody = msgBody;
XmlNode node = null;
XmlNode tmpNode = null;
try
{
doc = new XmlDocument();
doc.LoadXml(msgBody);//解密后才是需要处理的XML数据,读取XML字符串
XmlElement rootElement = doc.DocumentElement;
XmlNode msgTypeNode = rootElement.SelectSingleNode("MsgType");//获取字符串中的消息类型
node = rootElement.SelectSingleNode("FromUserName");
if (node != null)
{
msg.FromUserName = node.InnerText;
}
node = rootElement.SelectSingleNode("AgentID");
if (node != null)
{
msg.AgentID = Convert.ToInt32(node.InnerText);
}
node = rootElement.SelectSingleNode("ToUserName");
if (node != null)
{
msg.ToUserName = node.InnerText;
}
node = rootElement.SelectSingleNode("CreateTime");
if (node != null)
{
msg.CreateTime = Convert.ToInt64(node.InnerText);
}
#region 获取具体的消息对象
string strMsgType = msgTypeNode.InnerText;
string msgId = string.Empty;
string content = string.Empty;
tmpNode = rootElement.SelectSingleNode("MsgId");
if (tmpNode != null)
{
msgId = tmpNode.InnerText.Trim();
}
string strMsgType2 = strMsgType.Trim().ToLower();
switch (strMsgType2)
{
case "text"://接收普通消息 text消息
msgType = MsgType.Text;
tmpNode = rootElement.SelectSingleNode("Content");
if (tmpNode != null)
{
content = tmpNode.InnerText.Trim();
}
TextReceiveMessage txtMsg = new TextReceiveMessage(msg)
{
MsgType = msgType,
MsgId = Convert.ToInt64(msgId),
Content = content
};
txtMsg.AfterRead();
return txtMsg;
case "image"://接收普通消息 image消息
msgType = MsgType.Image;
ImageReceiveMessage imgMsg = new ImageReceiveMessage(msg)
{
MsgId = Convert.ToInt64(msgId),
MsgType = msgType,
MediaId = rootElement.SelectSingleNode("MediaId").InnerText,
PicUrl = rootElement.SelectSingleNode("PicUrl").InnerText
};
imgMsg.AfterRead();
return imgMsg;
case "voice"://接收普通消息 voice消息
msgType = MsgType.Voice;
XmlNode node1 = rootElement.SelectSingleNode("Recognition");
if (node1 != null)
{
msgType = MsgType.VoiceResult;
}
VoiceReceiveMessage voiceMsg = new VoiceReceiveMessage(msg)
{
MsgId = Convert.ToInt64(msgId),
MsgType = msgType,
Recognition = node1 == null ? string.Empty : node1.InnerText.Trim(),
Format = rootElement.SelectSingleNode("Format").InnerText,
MediaId = rootElement.SelectSingleNode("MediaId").InnerText
};
voiceMsg.AfterRead();
return voiceMsg;
case "video"://接收普通消息 video消息
msgType = MsgType.Video;
VideoReceiveMessage videoMsg = new VideoReceiveMessage(msg)
{
MediaId = rootElement.SelectSingleNode("MediaId").InnerText,
MsgId = Convert.ToInt64(msgId),
MsgType = msgType,
ThumbMediaId = rootElement.SelectSingleNode("ThumbMediaId").InnerText
};
videoMsg.AfterRead();
return videoMsg;
case "location"://接收普通消息 location消息
msgType = MsgType.Location;
LocationReceiveMessage locationMsg = new LocationReceiveMessage(msg)
{
MsgId = Convert.ToInt64(msgId),
MsgType = msgType,
Label = rootElement.SelectSingleNode("Label").InnerText,
Location_X = rootElement.SelectSingleNode("Location_X").InnerText,
Location_Y = rootElement.SelectSingleNode("Location_Y ").InnerText,
Scale = rootElement.SelectSingleNode("Scale").InnerText
};
locationMsg.AfterRead();
return locationMsg;
case "event":// 接收事件
msgType = MsgType.Event;
eventType = EventType.UnKnown;
msg.MsgType = msgType;
XmlNode eventNode = rootElement.SelectSingleNode("Event");
if (eventNode != null)
{
string eventtype = eventNode.InnerText.Trim().ToLower();
switch (eventtype)
{
case "subscribe": //接收事件 成员关注
eventType = EventType.Subscribe;
SubscribeEventMessage subEvt = new SubscribeEventMessage(msg)
{
EventType = EventType.Subscribe,
MsgType = msgType,
};
subEvt.AfterRead();
return subEvt;
case "unsubscribe": //接收事件 取消关注事件
eventType = EventType.UnSubscribe;
UnSubscribeEventMessage unSubEvt = new UnSubscribeEventMessage(msg)
{
EventType = eventType,
MsgType = msgType,
};
unSubEvt.AfterRead();
return unSubEvt;
case "location"://接收事件 上报地理位置事件
eventType = EventType.Location;
UploadLocationEventMessage locationEvt = new UploadLocationEventMessage(msg)
{
EventType = eventType,
Latitude = rootElement.SelectSingleNode("Latitude").InnerText,
Longitude = rootElement.SelectSingleNode("Longitude").InnerText,
MsgType = msgType,
Precision = rootElement.SelectSingleNode("Precision").InnerText,
};
locationEvt.AfterRead();
return locationEvt;
case "click": //接收事件 上报菜单事件 点击菜单拉取消息的事件推送
eventType = EventType.Click;
MenuEventMessage menuEvt = new MenuEventMessage(msg)
{
EventKey = rootElement.SelectSingleNode("EventKey").InnerText,
EventType = eventType,
MsgType = msgType,
};
menuEvt.AfterRead();
return menuEvt;
case "view": //接收事件 上报菜单事件 点击菜单跳转链接的事件推送
eventType = EventType.VIEW;
MenuEventVIEWEventMessage menuVIEWEvt = new MenuEventVIEWEventMessage(msg)
{
EventKey = rootElement.SelectSingleNode("EventKey").InnerText,
EventType = eventType,
MsgType = msgType,
};
menuVIEWEvt.AfterRead();
return menuVIEWEvt;
case "scancode_push"://接收事件 上报菜单事件 扫码推事件的事件推送
eventType = EventType.scancode_push;
ScanCodePushEventMessage scanCodePushEventMessage = new ScanCodePushEventMessage(msg)
{
EventKey = rootElement.SelectSingleNode("EventKey").InnerText,
EventType = eventType,
MsgType = msgType,
ScanCodeInfo = new ScanCodeInfo(rootElement.SelectSingleNode("ScanCodeInfo"))
};
scanCodePushEventMessage.AfterRead();
return scanCodePushEventMessage;
case "scancode_waitmsg"://接收事件 上报菜单事件 扫码推事件且弹出“消息接收中”提示框的事件推送
eventType = EventType.scancode_waitmsg;
ScanCodeWaitMsgEventMessage scanCodeWaitMsgEventMessage = new ScanCodeWaitMsgEventMessage(msg)
{
EventKey = rootElement.SelectSingleNode("EventKey").InnerText,
EventType = eventType,
MsgType = msgType,
ScanCodeInfo = new ScanCodeInfo(rootElement.SelectSingleNode("ScanCodeInfo"))
};
scanCodeWaitMsgEventMessage.AfterRead();
return scanCodeWaitMsgEventMessage;
case "pic_sysphoto"://接收事件 上报菜单事件 弹出系统拍照发图的事件推送
eventType = EventType.pic_sysphoto;
PicSysPhotoEventMessage picSysPhotoEventMessage = new PicSysPhotoEventMessage(msg)
{
EventKey = rootElement.SelectSingleNode("EventKey").InnerText,
MsgType = msgType,
SendPicsInfo = new SendPicsInfo(rootElement.SelectSingleNode("SendPicsInfo"))
};
picSysPhotoEventMessage.AfterRead();
return picSysPhotoEventMessage;
case "pic_photo_or_album"://接收事件 上报菜单事件 弹出拍照或者相册发图的事件推送
eventType = EventType.pic_photo_or_album;
PicPhotoOrAlbumEventMessage picPhotoOrAlbumEventMessage = new PicPhotoOrAlbumEventMessage(msg)
{
EventType = eventType,
EventKey = rootElement.SelectSingleNode("EventKey").InnerText,
MsgType = msgType,
SendPicsInfo = new SendPicsInfo(rootElement.SelectSingleNode("SendPicsInfo"))
};
picPhotoOrAlbumEventMessage.AfterRead();
return picPhotoOrAlbumEventMessage;
case "pic_weixin"://接收事件 上报菜单事件 弹出微信相册发图器的事件推送
eventType = EventType.pic_weixin;
PicWeiXinEventMessage picWeiXinEventMessage = new PicWeiXinEventMessage(msg)
{
EventType = eventType,
EventKey = rootElement.SelectSingleNode("EventKey").InnerText,
MsgType = msgType,
SendPicsInfo = new SendPicsInfo(rootElement.SelectSingleNode("SendPicsInfo"))
};
picWeiXinEventMessage.AfterRead();
return picWeiXinEventMessage;
case "location_select"://接收事件 上报菜单事件 弹出地理位置选择器的事件推送
eventType = EventType.location_select;
LocationSelectEventMessage locationSelectEventMessage = new LocationSelectEventMessage(msg)
{
EventType = eventType,
EventKey = rootElement.SelectSingleNode("EventKey").InnerText,
MsgType = msgType,
SendLocationInfo = new SendLocationInfo(rootElement.SelectSingleNode("SendLocationInfo"))
};
locationSelectEventMessage.AfterRead();
return locationSelectEventMessage;
case "enter_agent": //接收事件 成员进入应用的事件推送
eventType = EventType.enter_agent;
EnterAgentEventMessage EnterAgentEventMessage = new EnterAgentEventMessage(msg)
{
MsgType = msgType,
};
EnterAgentEventMessage.AfterRead();
return EnterAgentEventMessage;
default:
LogInfo.Error("事件类型" + eventtype + "未处理");
break;
}
}
break;
default:
LogInfo.Error("消息类型" + strMsgType2 + "未处理");
break;
}
msg.MsgType = msgType;
#endregion
}
catch (Exception ex)
{
LogInfo.Error("处理消息异常:" + msgBody, ex);
}
finally
{
if (doc != null)
{
doc = null;
}
}
msg.MsgType = msgType;
return msg;
}
~~~
发送被动响应文本消息:
~~~
/// <summary>
/// 发送被动响应文本消息,需要先加密在发送
/// </summary>
/// <param name="fromUserName">发送方</param>
/// <param name="toUserName">接收方</param>
/// <param name="content">文本内容</param>
public static void SendTextReplyMessage(string fromUserName, string toUserName, string content)
{
TextReplyMessage msg = new TextReplyMessage()
{
CreateTime = Tools.ConvertDateTimeInt(DateTime.Now),
FromUserName = fromUserName,
ToUserName = toUserName,
Content = content
};
/* LogInfo.Info("发送信息2sMsg=" + content);//也可以使用微信的接口发送消息
TextMsg data = new TextMsg(content);
data.agentid = "7";
data.safe = "0";
// data.toparty = "@all";
// data.totag = "@all";
data.touser = toUserName;
BLLMsg.SendMessage(data);*/
string CorpToken = AppIdInfo.GetToken();
string corpId = AppIdInfo.GetCorpId();
string encodingAESKey = AppIdInfo.GetEncodingAESKey();
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(CorpToken, encodingAESKey, corpId);
string msg_signature = HttpContext.Current.Request.QueryString["msg_signature"];
string timestamp = HttpContext.Current.Request.QueryString["timestamp"];
string nonce = HttpContext.Current.Request.QueryString["nonce"];
string encryptResponse = "";//加密后的文字
string sMsg = msg.ToXmlString();//加密前的文字
int isok = wxcpt.EncryptMsg(sMsg, timestamp, nonce, ref encryptResponse);//
LogInfo.Info("发送信息sMsg=" + sMsg);
// LogInfo.Info("发送信息encryptResponse=" + encryptResponse);
if (isok == 0 && !string.IsNullOrEmpty(encryptResponse))
{
HttpContext.Current.Response.ContentEncoding = Encoding.UTF8;
HttpContext.Current.Response.Write(encryptResponse);//被动相应消息不需要调用微信接口
}
else {
LogInfo.Info("发送信息失败isok=" + isok);
}
}
~~~
注释掉的代码就是主动发送消息,具体可参考[微信企业号开发:主动发送消息](http://blog.csdn.net/xuexiaodong009/article/details/46987227)
使用注释掉的代码也可以给用户发送消息,但这种方式不叫被动响应消息
被动响应消息实体
~~~
/// <summary>
/// 被动响应消息类
/// </summary>
public abstract class ReplyMessage
{
public string ToUserName { get; set; }
public string FromUserName { get; set; }
public long CreateTime { get; set; }
/// <summary>
/// 将对象转化为Xml消息
/// </summary>
/// <returns></returns>
public abstract string ToXmlString();
}
/// <summary>
/// 被动响应文本消息
/// </summary>
public class TextReplyMessage : ReplyMessage
{
/// <summary>
/// 回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示)
/// </summary>
public string Content { get; set; }
/// <summary>
/// 将对象转化为Xml消息
/// </summary>
/// <returns></returns>
public override string ToXmlString()
{
string s = "<xml><ToUserName><![CDATA[{0}]]></ToUserName><FromUserName><![CDATA[{1}]]></FromUserName><CreateTime>{2}</CreateTime><MsgType><![CDATA[{3}]]></MsgType><Content><![CDATA[{4}]]></Content></xml>";
s = string.Format(s,
ToUserName ?? string.Empty,
FromUserName ?? string.Empty,
CreateTime.ToString(),
"text",
Content ?? string.Empty
);
return s;
}
}
~~~
配置的URL网页的代码:
~~~
public class TestWeixin : IHttpHandler {
public void ProcessRequest (HttpContext context) {
if (context.Request.HttpMethod.ToLower() == "post")
{
try
{
System.IO.Stream s = context.Request.InputStream;
byte[] b = new byte[s.Length];
s.Read(b, 0, (int)s.Length);
string postStr = System.Text.Encoding.UTF8.GetString(b);
if (!string.IsNullOrEmpty(postStr))
{
Execute(postStr);
}
}catch(Exception e)
{
new AppException("收到信息异常" + e.Message);
}
}
else //开启应用的回调模式调用 ,代码省略
{
}
}
private void Execute(string postStr)
{
ReceiveMessageBase basemsg = ConvertMsgToObject(postStr);
if (basemsg.MsgType ==.MsgType.Text)
{
TextReceiveMessage txtMsg = basemsg as TextReceiveMessage;
if (txtMsg != null)
{
SendTextReplyMessage(txtMsg.ToUserName, txtMsg.FromUserName, "收到文本消息:" + txtMsg.Content);//发送被动消息
}
}
}
public bool IsReusable {
get {
return false;
}
}
~~~
开启应用的回调模式调用使用的代码参考[微信企业号开发:启用回调模式](http://blog.csdn.net/xuexiaodong009/article/details/46922321)
这样修改之后呢,用户给企业号发送文本消息时,企业号就可以把用户发送的消息主动回复给用户。
效果如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757ddc898e.jpg)
其他的类型的普通消息,也都相似。
但我个人发现,收到事件时,发送被动响应消息,似乎不保证用户能收到,似乎有很大的概率收不到,不知道是我人的原因,还是微信的原因。但奇怪的是,事件都能收到,发送被动响应消息,很大的概率收不到。
其实收到普通的消息时,也可以通过主动发送消息,也就是调用微信的相关接口,也可以达到回复用户的目的,这个我测试过,但比发送被动响应消息慢,也能实现和上边类似的效果。
主动发送消息
最后更新于:2022-04-01 07:06:02
**主企业号主动发送消息,也就是企业号主动推送的消息,适合于企业的通知,通告等。因此如果公司有通知,要求通知到所有员工,就应该使用主动发送消息。**
格式是json格式,而且微信很灵活,当touser,toparty,totag的json值是null时,微信服务器主动忽略了。原来还担心,如果是null,在生成json格式时如何忽略掉是null的字段。
核心基本类:
~~~
public class MsgBase
{
public MsgBase()
{
this.safe = "0"; //表示是否是保密消息,0表示否,1表示是,默认0
}
/// <summary>
/// UserID列表(消息接收者,多个接收者用‘|’分隔)。特殊情况:指定为@all,则向关注该企业应用的全部成员发送
/// </summary>
public string touser { get; set; }
/// <summary>
/// PartyID列表,多个接受者用‘|’分隔。当touser为@all时忽略本参数
/// </summary>
public string toparty { get; set; }
/// <summary>
/// TagID列表,多个接受者用‘|’分隔。当touser为@all时忽略本参数
/// </summary>
public string totag { get; set; }
/// <summary>
/// 消息类型
/// </summary>
public string msgtype { get; set; }
/// <summary>
/// 企业应用的id,整型。可在应用的设置页面查看
/// </summary>
public int agentid { get; set; }
/// <summary>
/// 表示是否是保密消息,0表示否,1表示是,默认0
/// </summary>
public string safe { get; set; }
}
~~~
~~~
public static class MsgType
{
public enum MsgBaseEnum
{
Text = 1,
image = 2,
voice = 3,
video = 4,
file = 5,
news = 6,
mpnews =7
};
public static string GetMsgTypeText(MsgBaseEnum type)
{
string text = "";
switch(type)
{
case MsgBaseEnum.Text:
text = "text";
break;
case MsgBaseEnum.image:
text = "image";
break;
case MsgBaseEnum.voice:
text = "voice";
break;
case MsgBaseEnum.video:
text = "video";
break;
case MsgBaseEnum.file:
text = "file";
break;
case MsgBaseEnum.news:
text = "news";
break;
case MsgBaseEnum.mpnews:
text = "mpnews";
break;
default:
throw new Exception("type=" + type + ",此类型的消息没有实现");
}
return text;
}
}
~~~
文字类型的消息
~~~
public class TextMsg : MsgBase
{
public TextMsg(string content)
{
this.text = new TextMsgContent(content);
this.msgtype = MsgType.GetMsgTypeText(MsgType.MsgBaseEnum.Text);
}
public TextMsgContent text { get; set; }
}
public class TextMsgContent
{
public TextMsgContent(string content)
{
this.content = content;
}
public string content { get; set; }
}
~~~
发送消息
~~~
public static class BLLMsg
{
public static bool SendMessage(MsgBase data)
{
string urlFormat = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={0}";
string accessToken = BLLAccessToken.GetAccessToken();
var url = string.Format(urlFormat, accessToken);
WebUtils ut = new WebUtils();
var postData = Tools.ToJsonString<MsgBase>(data);
//数据不用加密发送
LogInfo.Info("发送消息: " + postData);
string sendResult = ut.DoPost(url, postData);
SendMsgResult tempAccessTokenjson = Tools.JsonStringToObj<SendMsgResult>(sendResult);
if (tempAccessTokenjson.HasError())
{
LogInfo.Error("发送消息错误: " + Tools.ToJsonString<SendMsgResult>(tempAccessTokenjson));
return false;
}
return true;
}
}
~~~
测试代码:
~~~
private void button9_Click(object sender, EventArgs e)
{
TextMsg data = new TextMsg("测试发送文字消息给整个企业" + DateTime.Now);
data.agentid = 7;
data.safe = "0";
data.toparty = "1";
BLLMsg.SendMessage(data);
}
private void button11_Click(object sender, EventArgs e)
{
TextMsg data = new TextMsg("测试发送文字消息给医疗部" + DateTime.Now);
data.agentid = 7;
data.safe = "0";
data.toparty = "2";
BLLMsg.SendMessage(data);
}
private void button10_Click(object sender, EventArgs e)
{
TextMsg data = new TextMsg("测试发送文字消息给所有用户" + DateTime.Now);
data.agentid = 7;
data.safe = "0";
data.touser = "@all";
BLLMsg.SendMessage(data);
}
private void button1_Click(object sender, EventArgs e)
{
TextMsg data = new TextMsg("单用户文字消息" + DateTime.Now);
data.agentid = 7;
data.safe = "0";
data.touser = "iaodong";
BLLMsg.SendMessage(data);
}
~~~
[主动发送消息官方文档](http://qydev.weixin.qq.com/wiki/index.php?title=%E5%8F%91%E9%80%81%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E)
消息类型与区别
最后更新于:2022-04-01 07:05:59
**微信企业号的消息,分成两种,调用微信接口发送的消息在接口文档中叫做发送消息,这种消息是json格式的。在微信服务器给我们配置的URL post数据时发送的消息叫做接收消息和事件,需要加密解密,不需要调用微信接口,这种消息的格式是XML格式的。**
两种消息的区别和联系:
发送消息是json格式的,不需要加密解密,需要调用微信的接口,需要AccessToken,这种消息适用于公众号主动推送下发消息。
接收消息和事件的格式是XML格式的,需要加密,不需要调用微信接口,不需要AccessToken,但每次都会包含msg_signature、timestamp、nonce等几个参数,这类消息其实是微信把数据post到我们配置的URL时发生的。
**其实仔细分析可以发现:所有主动调用微信接口的都需要参数AccessToken,不需要加密解密,json格式的包括创建菜单,管理通信录等。**
**而在微信把数据post到我们配置的URL时,则需要解密加密,不需要调用微信接口,不需要AccessToken,但每次都会包含msg_signature、timestamp、nonce等几个参数,XML格式的数据。**
**在接收消息与事件中,消息又分为:普通消息,事件,被动响应消息三类消息。**
1普通消息其实就是用户发送给企业号的消息。
实际运行路径是,用户发送消息,微信服务器把消息处理后post给我们配置的URL,我们在配置的URL的后台就可以获得用户发送的消息。
2事件,其实也是用户的特殊动作导致的消息,例如点击菜单等。
实际运行路径是,用户发送消息,微信服务器把消息处理后post给我们配置的URL,我们在配置的URL的后台就可以获得用户发送的消息。
3被动响应消息,其实是微信服务器把前两类消息post给我们配置的URL后,我们通过HttpContext.Current.Response.Write(encryptResponse)这种方式,发送给微信服务器,微信服务器在把消息转给用户的消息。
仔细分析可以发现前两类是用户是主动者,而被动响应消息是企业号因为用户的某些动作而产生的回应,这或许就是微信把这类消息叫做被动响应消息的原因吧。
前两类消息的流程图
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dda29ee.jpg)
被动相应消息的顺序恰好相反。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757ddb2a71.jpg)
发送消息和被动响应消息有什么区别呢?
发送消息是公众号主动推送消息给用户,被动响应消息是由于用户的原因导致了微信服务器器post到URL上。从用户角度来说没有太大差别。
例如:用户回复1,公众号回复1,也就是简单的聊天,两种方式都可以,但被动响应消息速度能快一些,主动发送消息稍微慢一点。但被动响应消息不能主动推送,主动发送消息却可以主动发送。
自定义菜单
最后更新于:2022-04-01 07:05:57
开发微信企业号可以通过程序自定义菜单,只需要调用相关的接口就可以实现。
其实这个菜单也就是微信底部的菜单,目前自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“...”代替。其实创建菜单也很简单。
但有一点需要说明,如果有子菜单,那么这个菜单就不会向后端发送事件。例如:我定义了三个一级菜单一个click,两个view。
**如果没有子菜单,则点击click类型的菜单时,后主动向后端发送上报菜单事件,如果有则不会发送上报菜单事件。**
如果没有子菜单,则点击view类型的菜单时,回主动向后端发送点击菜单跳转链接的事件。并且会打开对应的网页,如果有则不会发送点击菜单跳转链接的事件,也不会打开对应的网页。
**也就是,如果有子菜单,则这个菜单,就是点击单纯的显示子菜单,不会有其他的动作了。**
例如:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dd818c1.jpg)
核心代码菜单相关:
~~~
public enum MenuTypeEnum
{
click = 1,
view = 2,
scancode_push = 3,
scancode_waitmsg = 4,
pic_sysphoto = 5,
pic_photo_or_album = 6,
pic_weixin = 7,
location_select = 8
};
public abstract class SubButton
{
/// <summary>
/// 菜单的响应动作类型,目前有click、view两种类型 scancode_push 扫码推事件scancode_waitmsg 扫码推事件且弹出“消息接收中”提示框
/// </summary>
public string type { get; protected set; }
/// <summary>
/// 菜单标题,不超过16个字节,子菜单不超过40个字节
/// </summary>
public string name { get; set; }
public List<SubButton> sub_button { get; set; }
public virtual bool HasError()
{
if (string.IsNullOrEmpty(this.name))
{
LogInfo.Error("菜单名称为空");
return true;
}
if (string.IsNullOrEmpty(this.type))
{
LogInfo.Error("菜单类型为空");
return true;
}
if (sub_button!=null&&sub_button.Count > 0)
{
foreach (SubButton bt in sub_button)
{
if (bt.HasError())
{
return true;
}
}
}
return false;
}
public static SubButton CreateSubButton(MenuTypeEnum type,string name,string key,string url)
{
SubButton subButton = null;
string menuTypeText = GetMenuTypeText(type);
switch (type)
{
case MenuTypeEnum.view:
subButton = new SubViewButton(menuTypeText,name, url);
break;
case MenuTypeEnum.click:
case MenuTypeEnum.scancode_push:
case MenuTypeEnum.scancode_waitmsg:
case MenuTypeEnum.pic_sysphoto:
case MenuTypeEnum.pic_photo_or_album:
case MenuTypeEnum.pic_weixin:
case MenuTypeEnum.location_select:
subButton = new SubClickButton(menuTypeText, name, key);
break;
default:
throw new Exception("type=" + type + ",此类型的SubButton没有实现");
}
return subButton;
}
public static MenuTypeEnum GetMenuType(string type)
{
MenuTypeEnum text = MenuTypeEnum.click;
switch (type)
{
case "click":
text = MenuTypeEnum.click;
break;
case "view":
text = MenuTypeEnum.view ;
break;
case "scancode_push":
text =MenuTypeEnum.scancode_push ;
break;
case "scancode_waitmsg":
text = MenuTypeEnum.scancode_waitmsg ;
break;
case "pic_sysphoto" :
text = MenuTypeEnum.pic_sysphoto;
break;
case "pic_photo_or_album":
text = MenuTypeEnum.pic_photo_or_album ;
break;
case "pic_weixin":
text = MenuTypeEnum.pic_weixin ;
break;
case "location_select":
text =MenuTypeEnum.location_select ;
break;
default:
throw new Exception("type=" + type + ",此类型的MenuTypeEnum没有找到");
}
return text;
}
public static string GetMenuTypeText(MenuTypeEnum type)
{
string text = "";
switch (type)
{
case MenuTypeEnum.click:
text = "click";
break;
case MenuTypeEnum.view:
text = "view";
break;
case MenuTypeEnum.scancode_push:
text = "scancode_push";
break;
case MenuTypeEnum.scancode_waitmsg:
text = "scancode_waitmsg";
break;
case MenuTypeEnum.pic_sysphoto:
text = "pic_sysphoto";
break;
case MenuTypeEnum.pic_photo_or_album:
text = "pic_photo_or_album";
break;
case MenuTypeEnum.pic_weixin:
text = "pic_weixin";
break;
case MenuTypeEnum.location_select:
text = "location_select";
break;
default:
throw new Exception("type=" + type + ",此类型的MenuTypeEnum没有实现");
}
return text;
}
}
public class SubClickButton : SubButton
{
public SubClickButton(string type,string name,string key)
{
this.type = type;
this.name = name;
this.key = key;
}
/// <summary>
/// 菜单KEY值,用于消息接口推送,不超过128字节
/// </summary>
public string key { get; set; }
public override bool HasError()
{
if (string.IsNullOrEmpty(this.key))
{
LogInfo.Error("菜单key为空");
return true;
}
return base.HasError();
}
}
public class SubViewButton : SubButton
{
public SubViewButton(string type, string name, string url)
{
this.type = type;
this.name = name;
this.url = url;
}
/// <summary>
/// 成员点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取成员基本信息接口结合,获得成员基本信息。
/// </summary>
public string url { get; set; }
public override bool HasError()
{
if (string.IsNullOrEmpty(this.url))
{
LogInfo.Error("菜单url为空");
return true;
}
return base.HasError();
}
}
~~~
~~~
/// <summary>
/// 自定义菜单
/// </summary>
public class Menu
{
public List<SubButton> button { get; set; }
public virtual bool HasError()
{
if (button.Count >= 4)
{
LogInfo.Error("最多包括3个一级菜单");
return true;
}
foreach(SubButton bt in button)
{
if (bt.HasError())
{
return true;
}
}
return false;
}
}
~~~
添加修改,删除菜单
~~~
public class BLLMenu
{
/// <summary>
///
/// </summary>
/// <param name="info"></param>
/// <param name="agentid">企业应用的id,整型。可在应用的设置页面查看</param>
/// <returns></returns>
public static bool Create(Menu info, int agentid)
{
if (info.HasError())
{
return false;
}
string urlFormat = "https://qyapi.weixin.qq.com/cgi-bin/menu/create?access_token={0}&agentid={1}";
var url = string.Format(urlFormat, BLLAccessToken.GetAccessToken(), agentid);
WebUtils wut = new WebUtils();
var postData = Tools.ToJsonString<Menu>(info);
//数据不用加密发送
LogInfo.Info("创建应用菜单消息: " + postData);
string sendResult = wut.DoPost(url, postData);
ReturnResult tempAccessTokenjson = Tools.JsonStringToObj<ReturnResult>(sendResult);
if (tempAccessTokenjson.HasError())
{
LogInfo.Error("发送创建应用菜单返回错误: " + Tools.ToJsonString<ReturnResult>(tempAccessTokenjson));
return false;
}
return true;
}
/// <summary>
///
/// </summary>
/// <param name="agentid">企业应用的id,整型。可在应用的设置页面查看</param>
/// <returns></returns>
public static bool DelAll(int agentid)
{
string urlFormat = "https://qyapi.weixin.qq.com/cgi-bin/menu/delete?access_token={0}&agentid={1}";
var url = string.Format(urlFormat, BLLAccessToken.GetAccessToken(), agentid);
WebUtils wut = new WebUtils();
//数据不用加密发送
LogInfo.Info("发送删除菜单消息: " + url);
string sendResult = wut.DoGet(url);
ReturnResult tempAccessTokenjson = Tools.JsonStringToObj<ReturnResult>(sendResult);
if (tempAccessTokenjson.HasError())
{
LogInfo.Error("发送删除菜单返回错误: " + Tools.ToJsonString<ReturnResult>(tempAccessTokenjson));
return false;
}
return true;
}
public static bool GetAll(int agentid)
{
string urlFormat = "https://qyapi.weixin.qq.com/cgi-bin/menu/get?access_token={0}&agentid={1}";
var url = string.Format(urlFormat, BLLAccessToken.GetAccessToken(), agentid);
WebUtils wut = new WebUtils();
//数据不用加密发送
LogInfo.Info("发送获取菜单列表消息: " + url);
string sendResult = wut.DoGet(url);
MenuListResult tempAccessTokenjson = Tools.JsonStringToObj<MenuListResult>(sendResult);
if (tempAccessTokenjson.HasError())
{
LogInfo.Error("发送获取菜单列表返回错误: " + Tools.ToJsonString<MenuListResult>(tempAccessTokenjson));
return false;
}
return true;
}
}
~~~
测试代码
~~~
private void button7_Click(object sender, EventArgs e)
{
///测试添加
ConmonWeixin.MenuInfo.Menu info = new ConmonWeixin.MenuInfo.Menu();
SubButton subbt1 = SubButton.CreateSubButton(MenuTypeEnum.click, "Click1", "Click1", "");
subbt1.sub_button = new List<SubButton>();
SubButton bt11 = SubButton.CreateSubButton(MenuTypeEnum.scancode_push, "codePush2", "CancodePushButton11", "");
SubButton bt12 = SubButton.CreateSubButton(MenuTypeEnum.scancode_waitmsg, "codeWaitmsg2", "CancodeWaitmsgButton12", "");
SubButton bt13 = SubButton.CreateSubButton(MenuTypeEnum.click, "Click12", "Click12", "");
SubButton bt14 = SubButton.CreateSubButton(MenuTypeEnum.view, "V2级", "V22", "https://www.baidu.com");
subbt1.sub_button.Add(bt11);
subbt1.sub_button.Add(bt12);
subbt1.sub_button.Add(bt13);
subbt1.sub_button.Add(bt14);
SubButton subbt2 = SubButton.CreateSubButton(MenuTypeEnum.view, "V1级", "", "www.baidu.com");
subbt2.sub_button = new List<SubButton>();
SubButton bt21 = SubButton.CreateSubButton(MenuTypeEnum.pic_sysphoto, "PicSysphoto", "PicSysphotoButton21", "");
SubButton bt22 = SubButton.CreateSubButton(MenuTypeEnum.pic_photo_or_album, "photoalbum2", "PicSysphotoButton22", "");
subbt2.sub_button.Add(bt21);
subbt2.sub_button.Add(bt22);
SubButton subbt3 = SubButton.CreateSubButton(MenuTypeEnum.view, "V1级", "", "http://hlogin.html");
subbt3.sub_button = new List<SubButton>();
SubButton bt31 = SubButton.CreateSubButton(MenuTypeEnum.pic_weixin, "pic_weixin2", "Subpic_weixinButton31", "");
SubButton bt32 = SubButton.CreateSubButton(MenuTypeEnum.location_select, "location_selec", "location_selec32", "");
subbt3.sub_button.Add(bt31);
subbt3.sub_button.Add(bt32);
info.button = new List<SubButton>();
info.button.Add(subbt1);
info.button.Add(subbt2);
info.button.Add(subbt3);
BLLMenu.Create(info,7);
// Menu info, int agentid
}
private void button8_Click(object sender, EventArgs e)
{
//测试删除
BLLMenu.DelAll(7);
}
~~~
[创建应用菜单官方文档](http://qydev.weixin.qq.com/wiki/index.php?title=%E5%88%9B%E5%BB%BA%E5%BA%94%E7%94%A8%E8%8F%9C%E5%8D%95)
[菜单接收事件官方文档](http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E4%BA%8B%E4%BB%B6)
获取AccessToken
最后更新于:2022-04-01 07:05:55
**微信企业号开发,需要调用微信企业号的相关接口,则必须使用AccessToken,但AccessToken需要corpid,corpsecret两个参数调用相关接口才能获取。**
**而且每一个接口都有一定的次数限制,当然获取AccessToken的接口也有这个限制。每一个AccessToken的有效期为7200秒,也就是两个小时,在有效期内调用接口,则自动续期。因此建议在获取到AccessToken后,保存在在某一个地方,等到快过期时在重新获取。其实AccessToken有点类似于web程序中的session,这个有效期7200秒相当于session的有效期,调用接口,则自动续期,就相当于web程序中用户登陆后,和服务端有交互,session的有效期自然延长了。**
核心代码AccessTokenInfo类:
~~~
public class AccessTokenInfo
{
/// <summary>
/// access_token
/// </summary>
public string access_token { get; set; }
/// <summary>
/// 凭证有效时间,单位:秒
/// </summary>
public long expires_in { get; set; }
/// <summary>
/// 获取时间
/// </summary>
public DateTime GetTime { get; set; }
}
~~~
BLLAccessToken类
~~~
/// <summary>
/// 获取企业登陆access_token
/// </summary>
public static class BLLAccessToken
{
static AccessTokenInfo TokenInfo = null;
public static string GetAccessToken()
{
string AccessToken = "";
DateTime now=DateTime.Now;
if (TokenInfo == null) //首次获取
{
TokenInfo = UpDateAccessToken();
}
else
{
if (TokenInfo.GetTime.AddSeconds(TokenInfo.expires_in - 30) < now) //提前30秒重新获取
{
TokenInfo = UpDateAccessToken();
}
}
AccessToken = TokenInfo.access_token;
return AccessToken;
}
private static AccessTokenInfo UpDateAccessToken()
{
string CorpId = AppIdInfo.GetCorpId();//corpid
string Secret = AppIdInfo.GetSecret(); //corpsecret
AccessTokenInfo info = new AccessTokenInfo();
WebUtils ut = new WebUtils();
/// https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=wxb7f1db8fd6aa9d68&corpsecret=aoxZ7D5-SgLRUbKY2fwQykW36RqxoIdNIn1pIiGy9iSdXgMHwQCzUsniQVAsBCTt
string urlFormat = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={0}&corpsecret={1}";
var url = string.Format(urlFormat, CorpId, Secret);
string temp = ut.DoGet(url);
try
{
AccessTokenInfo tempAccessTokenjson = Tools.JsonStringToObj<AccessTokenInfo>(temp);
info.access_token = tempAccessTokenjson.access_token;
info.expires_in = tempAccessTokenjson.expires_in;
info.GetTime = DateTime.Now;
}
catch(Exception ex)
{
LogInfo.Error("获取AccessToken异常", ex);
}
return info;
}
}
~~~
[微信企业号开发:常用的参数](http://blog.csdn.net/xuexiaodong009/article/details/46906867)
[微信企业号开发:corpsecret到底在哪块呢?](http://blog.csdn.net/xuexiaodong009/article/details/46895401)
[获取AccessToken官方文档](http://qydev.weixin.qq.com/wiki/index.php?title=%E4%B8%BB%E5%8A%A8%E8%B0%83%E7%94%A8)
启用回调模式
最后更新于:2022-04-01 07:05:53
微信企业号开发如何启用回调模式?就是简单的登陆PC版微信,点击应用中心,选择需要应用,再点击回调模式启用?
**似乎不是这么简单!!**
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dd6d5bb.jpg)
可以看到核心的只有三个URL,Token,EncodingAESKey这三个参数可以随便填写吗?
1URL可以随便填写吗?
可以肯定的是,不能随便填写。不信你可以试试。因为点击确定后微信会给这个URL发送信息。因此这个URL必须是外网可以访问的地址。
而且后台还必须处理微信发送过来的信息。例如URL 是http://www.hao123.com/可以在外网方法,但点击保存时就会出现:
**echostr校验失败,请您检查是否正确解密并输出明文echostr**
**2Token可以随便填写吗?**
可以,目前我没有发现有什么特殊的要求
**3EncodingAESKey能随便填写吗?**
不能随便填写,必须是数字字母的组合,而且是43个字符,建议使用微信随机生成的。
我们知道在URL处配置一个外网可以访问的URL,并不能保证保存成功,后台如何处理呢?
例如我配置为http://.../TestWeixin.ashx
则后台的处理方式,需要调用微信的相关加密解密函数
TestWeixin.ashx的后台代码为:
~~~
public void ProcessRequest (HttpContext context) {
if (context.Request.HttpMethod.ToLower() == "post")
{
}
else //点击保存时,微信需要验证时调用
{
Valid();
}
}
private void Valid()
{
string msg_signature = HttpContext.Current.Request.QueryString["msg_signature"];
string timestamp = HttpContext.Current.Request.QueryString["timestamp"];
string nonce = HttpContext.Current.Request.QueryString["nonce"];
string decryptEchoString = ""; // 解析之后的明文
string echoStr = HttpContext.Current.Request.QueryString["echoStr"];
bool isok = CheckSignature(msg_signature, timestamp, nonce, echoStr, ref decryptEchoString);
if (isok)
{
if (!string.IsNullOrEmpty(decryptEchoString))
{
HttpContext.Current.Response.Write(decryptEchoString);
HttpContext.Current.Response.End();
}
}
}
public bool CheckSignature(string signature, string timestamp, string nonce,string echostr, ref string retEchostr)
{
string token = "token"; //配置的token
string corpId = "corpId"; //corpid,
string encodingAESKey = "encodingAESKey"; //配置的tokenencodingAESKey
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAESKey, corpId); //调用微信提供的函数
int result = wxcpt.VerifyURL(signature, timestamp, nonce, echostr, ref retEchostr);//调用微信提供的函数
if (result != 0)
{
LogInfo.Error("ERR: VerifyURL fail, ret: " + result);
return false;
}
return true;
//ret==0表示验证成功,retEchostr参数表示明文,用户需要将retEchostr作为get请求的返回参数,返回给企业号。
}
~~~
常用的参数
最后更新于:2022-04-01 07:05:50
开发微信企业号经常会遇到几个很常见的参数,这几个参数是什么含义?在什么地方呢?有什么作用?
EncodingAESKey,Token,ACCESS_TOKEN,corpid,corpsecret,agentid,userid,部门ID?都表示什么含义呢?
1EncodingAESKey是加密解密使用的,加密解密时要用三个参数EncodingAESKey,Token,CorpID。
具体在PC版微信登陆后,右侧菜单,应用中心,点击自己的应用,选择应用后,在点击模式选择中的回调模式就可以看到。
登陆后,点击应用中心,再点击具体的应用,例如考勤管理
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dcbd0d1.jpg)
进入考勤管理应用后,点击回调模式
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dcdd675.jpg)
点击回调模式后,进入考勤管理回调模式页面,可以看到三个参数URL,Token,EncodingAESKey三个参数
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dd0eb63.jpg)
2Token是加密解密使用的,验证消息时使用,加密解密时要用三个参数EncodingAESKey,Token,CorpID。
EncodingAESKey,Token的位置在一起。
3CorpID是加密解密使用的,验证消息时使用,加密解密时要用三个参数EncodingAESKey,Token,CorpID。
具体位置登陆PC版微信后,点击设置,就可以看到
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dd265ed.jpg)
4ACCESS_TOKEN和Token是两个概念,不是同一个东西。这是调用微信接口的凭证,可以通过corpid,corpsecret获取到
5corpid,corpsecret是获取调用微信接口必须的两个参数
具体位置参考[微信企业号开发:corpsecret到底在哪块呢?](http://blog.csdn.net/xuexiaodong009/article/details/46895401)
6 agentid是企业应用的id,这参数在不少微信接口中存在,不同的agentid表示不能的企业应用,为什么有这个呢?因为一个微信企业号可以拥有多个企业应用,因此需要这个agentid,但在这里边叫做应用ID
具体位置:登陆PC版微信企业号,点击右侧应用中心,再点击具体的应用,进入具体的应用后,就可以看到。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dd42d3d.jpg)
7
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dd42d3d.jpg)
8部门ID,TagID,MsgId,media_id等有的是数值,有的不是数值,具体是什么,还真的好好看看微信的看法文档,不可想当然的认为是数值。
获取数据权限错误如何处理
最后更新于:2022-04-01 07:05:48
开发微信企业号在调用获取成员时,总是提示没有权限的错误,自己知道是没有对应的权限,但如何分配权限呢?
{"errcode":60011,"errmsg":"no privilege to access\/modify contact\/party\/agent "}
第一步登陆PC版企业号,点右侧击设置,再点击权限管理,再点击管理组,再点击通讯录权限的修改。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dc88b31.jpg)
第二步 弹出选择权限的对话框,选择权限后点击确定,然后再测试就不会出现关于权限的错误了
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dca9196.jpg)
corpsecret到底在哪块呢?
最后更新于:2022-04-01 07:05:46
开发微信企业号,获取[ACCESS_TOKEN](https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN)是必须的,但如何获取[ACCESS_TOKEN](https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN)呢?
获取[ACCESS_TOKEN](https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN)需要两个参数,corpid,corpsecret但这两个参数到底在哪块得到呢?
我是找了很长时间都没有找到!!
第一步是登陆手机端的微信企业号,登陆成功后,然后再打开PC端的微信企业号,使用手机端的微信扫一扫PC端的微信企业号的二维码,然后再手机端的微信上点击确定
,PC端的微信企业号就出现了登陆页面了,输入密码后就可以登陆了。
第二步 点击PC端的登陆成功后的企业号,点击右侧菜单设置
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dc2df37.jpg)
第三步向下拖动滚动条,点击权限管理
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dc53b74.jpg)
第四步 点击管理组,然后就可以看到CorpID,Secret了,如果没有管理组就需要创建。
这两个参数就是获取[ACCESS_TOKEN](https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN)需要的corpid,corpsecret了。
corpsecret
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-14_569757dc70649.jpg)
前言
最后更新于:2022-04-01 07:05:43
> 原文出处:[移动开发专栏文章](http://blog.csdn.net/column/details/xuexiaodong2009weixi.html)
> 作者:[薛小东](http://blog.csdn.net/xuexiaodong009)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
#微信企业号开发
> 主要介绍微信企业号开发开发遇到的各种各样的问题,以及微信企业号开发学习过程