公司要將移動端審批流程接入企業微信,員工通過企業微信的自建審批應用就可以在微信端審批單據,要審批單據就先得讓企業微信跟用戶綁定起來,實現無感自動登錄系統內。由於企業的員工已經被手動的從微信拉入企業微信內,這樣就不可以通過企業微信的創建人員接口將人員推到微信內。只能在用戶第一次登錄審批系統的時候去微信獲取用戶ID(userId)並與審批系統的人員關聯起來。綁定好后就可以實現自動登錄。
企業微信API:https://work.weixin.qq.com/api/doc#90001/90143/91201
員工點擊應用后自動登錄步驟如下:
1.攔截用戶是否登錄
在過濾器或者攔截器內檢查用戶是否登錄,如果沒有登錄跳轉到微信獲取用戶的身份信息
企業微信API地址:https://work.weixin.qq.com/api/doc#90001/90143/91120
獲取企業微信授權信息URL:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&agentid=AGENTID&state=STATE#wechat_redirec
頁面跳轉到企業微信后企業微信處理完成后,頁面將重新跳轉至 redirect_uri參數指定的回調地址並帶上參數code=CODE&state=STATE,企業可根據code參數獲得員工的userid。code長度最大為512字節。
public static final String OAUTH_CODE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=%s#wechat_redirect";
Integer serverPort = request.getServerPort();
String port = (null != serverPort && serverPort == 80) ? "" : ":" + serverPort.toString();
String url = "http://" + request.getServerName() // 服務器地址
+ port // 端口號
+ request.getContextPath(); // 項目名稱
url = url + "/weixin/auth/weixinLogin/oauthLogin.html";
try{
url = URLEncoder.encode(url, "utf-8");
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
String weixinUrl = String.format(OAUTH_CODE_URL, "企業ID", url, "state"");
response.sendRedirect(weixinUrl);
2.獲取用戶信息並登錄
微信回調接收到CODE值並用CODE去交換真實的用戶信息,如果用戶與系統綁定就表示登錄成功,登錄成功后將用戶信息放入SESSION中。這樣就可以實現自動登錄審批系統了。
/**
* 微信回調接口
* @return
*/
public String oauthLogin(){
try{
Map<String, Object> returnMap = WeixinUtil.getUserInfo(code, Constants.APPROVAL);
if (null != returnMap){
//得到微信用戶ID
String userid = MapUtils.getString(returnMap, "UserId");
if (!StringUtil.isRealEmpty(userid)){
//用戶與微信ID綁定實體
UserWx userWx = userWxManager.findByWXID(userid);
if (userWx != null){
if (StringUtil.isRealEmpty(userWx.getAvatar())){
WxUser wxUser = WeixinUtil.getWxUser(userid, Constants.APPROVAL);
if (null != wxUser && !StringUtil.isRealEmpty(wxUser.getAvatar())){
// +64取小頭像 ;+0取大頭像
userWx.setAvatar(wxUser.getAvatar());
userWxManager.save(userWx);
}
}
//登錄成功,將用戶信息放入SESSION等后續操作
.......
return null;
}else{
//顯示未綁定的邏輯
return customMethod("unbind");
}
}else{
return customMethod("error");
}
}else{
return customMethod("error");
}
}catch (Exception e){
return customMethod("error");
}
}
WeixinUtil的代碼如下:
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.URL;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.alibaba.fastjson.JSON;
import cn.boxbank.WxUser;
public class WeixinUtil
{
private static Log log = LogFactory.getLog(WeixinUtil.class);
/**
* 發起https請求並獲取結果
*
* @param requestUrl 請求地址
* @param requestMethod 請求方式(GET、POST)
* @param outputStr 提交的數據
* @return JSONObject(通過JSONObject.get(key)的方式獲取json對象的屬性值)
*/
public static Map<String, Object> HttpRequest(String requestUrl, String requestMethod, String outputStr)
{
// log.error("發起https請求並獲取結果 :"+requestUrl+","+requestMethod+","+outputStr);
Map<String, Object> map = new HashMap<String, Object>();
StringBuffer buffer = new StringBuffer();
try
{
// 創建SSLContext對象,並使用我們指定的信任管理器初始化
TrustManager[] tm = { new EwX509TrustManager() };
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
// 從上述SSLContext對象中得到SSLSocketFactory對象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
httpUrlConn.setSSLSocketFactory(ssf);
httpUrlConn.setDoOutput(true);
httpUrlConn.setDoInput(true);
httpUrlConn.setUseCaches(false);
httpUrlConn.setConnectTimeout(60 * 1000);
httpUrlConn.setReadTimeout(60 * 1000);
// 設置請求方式(GET/POST)
httpUrlConn.setRequestMethod(requestMethod);
httpUrlConn.connect();
// if ("GET".equalsIgnoreCase(requestMethod))
// 當有數據需要提交時
if (null != outputStr)
{
OutputStream outputStream = httpUrlConn.getOutputStream();
// 注意編碼格式,防止中文亂碼
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 將返回的輸入流轉換成字符串
InputStream inputStream = httpUrlConn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
while ((str = bufferedReader.readLine()) != null)
{
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
// 釋放資源
inputStream.close();
inputStream = null;
httpUrlConn.disconnect();
// 返回map
// map = JsonUtil.json2Map(buffer.toString(), Map.class, String.class, Object.class);
map = (Map<String, Object>) JSON.parseObject(buffer.toString(), Map.class);
}
catch (ConnectException ce)
{
System.out.println("Connection Timed Out......");
}
catch (Exception e)
{
String result = String.format("Https Request Error:%s", e);
System.out.println(result);
}
return map;
}
/**
* 根據code調微信接口獲取USERID
*
* @param code
* @return
*/
public static Map<String, Object> getUserInfo(String code, String agentFlag)
{
String OAUTH_GETUSERINFO_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s";
String url = String.format(OAUTH_GETUSERINFO_URL, getAccessToken(agentFlag), code);
Map<String, Object> result = HttpRequest(url, "GET", null);
return result;
}
/**
* 從配置參數中獲取
* @param agentFlag
* @return
*/
public static String getAccessToken(String agentFlag)
{
//這里是獲取應用的ACCESS_TOKEN,可以將獲取ACCESS_TOKEN放到定時線程內,每2個小時去重新獲取一次。
//從數據庫或者緩存中拿出應用的ACCESS_TOKEN
......
}
/**
* 通過userid 從微信獲取用戶
*
* @param userid
* @return
*/
public static WxUser getWxUser(String userid, String agentFlag)
{
String GET_USER_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s";
WxUser user = null;
String url = String.format(Constants.GET_USER_URL, getAccessToken(agentFlag), userid);
Map<String, Object> returnMap = HttpRequest(url, "GET", null);
int errcode = MapUtils.getIntValue(returnMap, "errcode");
if (errcode == 0)
{
user = JSON.toJavaObject((JSON) JSON.toJSON(returnMap), WxUser.class);
}
return user;
}
/**
* 調用微信接口獲取accessToken
*
* @param cropid 企業號ID
* @param cropsecret 企業號管理組對應的secret
* @return AccessToken
*/
public static void getAccessToken(String cropid, String cropsecret)
{
String ACCESS_TOKEN_URL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s";
// 添加URL中的cropid和cropsecret的值
String url = String.format(ACCESS_TOKEN_URL, cropid, cropsecret);
// 根據url通過https請求獲取accesstoken
Map<String, Object> returnMap = WeixinUtil.HttpRequest(url, "GET", null);
if (null != returnMap && returnMap.size() > 0)
{
// access_token
String access_token = MapUtils.getString(returnMap, "access_token");
// accessToken有效時間expires_in:7200秒
Integer expires_in = MapUtils.getInteger(returnMap, "expires_in");
//可將取到的ACCESS_TOKEN存入數據庫或者緩存中。每2個小時更新一次
........
}
else
{
log.info("accesstoken獲取失敗");
}
}
/**
* 微信接口返回代碼轉換信息
*
* @param errorcode
* @return
*/
public static String convertErrorCode2Msg(int errorcode)
{
String errmsg = "";
switch (errorcode)
{
case -1:
errmsg = "系統繁忙 ";
break;
case 0:
errmsg = "請求成功 ";
break;
case 40001:
errmsg = "獲取access_token時Secret錯誤,或者access_token無效 ";
break;
case 40002:
errmsg = "不合法的憑證類型";
break;
case 40003:
errmsg = "不合法的UserID";
break;
case 40004:
errmsg = "不合法的媒體文件類型 ";
break;
case 40005:
errmsg = "不合法的文件類型 ";
break;
case 40006:
errmsg = "不合法的文件大小";
break;
case 40007:
errmsg = "不合法的媒體文件id";
break;
case 40008:
errmsg = "不合法的消息類型 ";
break;
case 40013:
errmsg = "不合法的corpid ";
break;
case 40014:
errmsg = "不合法的access_token";
break;
case 40015:
errmsg = "不合法的菜單類型";
break;
case 40016:
errmsg = "不合法的按鈕個數";
break;
case 40017:
errmsg = "不合法的按鈕類型";
break;
case 40018:
errmsg = "不合法的按鈕名字長度";
break;
case 40019:
errmsg = "不合法的按鈕KEY長度";
break;
case 40020:
errmsg = "不合法的按鈕URL長度 ";
break;
case 40021:
errmsg = "不合法的菜單版本號 ";
break;
case 40022:
errmsg = "不合法的子菜單級數";
break;
case 40023:
errmsg = "不合法的子菜單按鈕個數";
break;
case 40024:
errmsg = "不合法的子菜單按鈕類型";
break;
case 40025:
errmsg = "不合法的子菜單按鈕名字長度";
break;
case 40026:
errmsg = "不合法的子菜單按鈕KEY長度";
break;
case 40027:
errmsg = "不合法的子菜單按鈕URL長度";
break;
case 40028:
errmsg = "不合法的自定義菜單使用員工";
break;
case 40029:
errmsg = "不合法的oauth_code";
break;
case 40031:
errmsg = "不合法的UserID列表";
break;
case 40032:
errmsg = "不合法的UserID列表長度";
break;
case 40033:
errmsg = "不合法的請求字符,不能包含\\uxxxx格式的字符 ";
break;
case 40035:
errmsg = "不合法的參數 ";
break;
case 40038:
errmsg = "不合法的請求格式 ";
break;
case 40039:
errmsg = "不合法的URL長度";
break;
case 40040:
errmsg = "不合法的插件token";
break;
case 40041:
errmsg = "不合法的插件id";
break;
case 40042:
errmsg = "不合法的插件會話";
break;
case 40048:
errmsg = "url中包含不合法domain";
break;
case 40054:
errmsg = "不合法的子菜單url域名";
break;
case 40055:
errmsg = "不合法的按鈕url域名 ";
break;
case 40056:
errmsg = "不合法的agentid";
break;
case 40057:
errmsg = "不合法的callbackurl";
break;
case 40058:
errmsg = "不合法的紅包參數 ";
break;
case 40059:
errmsg = "不合法的上報地理位置標志位 ";
break;
case 40060:
errmsg = "設置上報地理位置標志位時沒有設置callbackurl";
break;
case 40061:
errmsg = "設置應用頭像失敗";
break;
case 40062:
errmsg = "不合法的應用模式";
break;
case 40063:
errmsg = "紅包參數為空";
break;
case 40064:
errmsg = "管理組名字已存在";
break;
case 40065:
errmsg = "不合法的管理組名字長度";
break;
case 40066:
errmsg = "不合法的部門列表";
break;
case 40067:
errmsg = "標題長度不合法 ";
break;
case 40068:
errmsg = "不合法的標簽ID";
break;
case 40069:
errmsg = "不合法的標簽ID列表";
break;
case 40070:
errmsg = "列表中所有標簽(用戶)ID都不合法 ";
break;
case 40071:
errmsg = "不合法的標簽名字,標簽名字已經存在 ";
break;
case 40072:
errmsg = "不合法的標簽名字長度";
break;
case 40073:
errmsg = "不合法的openid";
break;
case 40074:
errmsg = "news消息不支持指定為高保密消息";
break;
case 41001:
errmsg = "缺少access_token參數 ";
break;
case 41002:
errmsg = "缺少corpid參數";
break;
case 41003:
errmsg = "缺少refresh_token參數";
break;
case 41004:
errmsg = "缺少secret參數";
break;
case 41005:
errmsg = "缺少多媒體文件數據";
break;
case 41006:
errmsg = "缺少media_id參數";
break;
case 41007:
errmsg = "缺少子菜單數據";
break;
case 41008:
errmsg = "缺少oauth code";
break;
case 41009:
errmsg = "缺少UserID";
break;
case 41010:
errmsg = "缺少url";
break;
case 41011:
errmsg = "缺少agentid";
break;
case 41012:
errmsg = "缺少應用頭像mediaid";
break;
case 41013:
errmsg = "缺少應用名字";
break;
case 41014:
errmsg = "缺少應用描述";
break;
case 41015:
errmsg = "缺少Content";
break;
case 41016:
errmsg = "缺少標題";
break;
case 41017:
errmsg = "缺少標簽ID";
break;
case 41018:
errmsg = "缺少標簽名字 ";
break;
case 42001:
errmsg = "access_token超時 ";
break;
case 42002:
errmsg = "refresh_token超時";
break;
case 42003:
errmsg = "oauth_code超時 ";
break;
case 42004:
errmsg = "插件token超時";
break;
case 43001:
errmsg = "需要GET請求";
break;
case 43002:
errmsg = "需要POST請求";
break;
case 43003:
errmsg = "需要HTTPS";
break;
case 43004:
errmsg = "需要接收者關注";
break;
case 43005:
errmsg = "需要好友關系";
break;
case 43006:
errmsg = "需要訂閱";
break;
case 43007:
errmsg = "需要授權";
break;
case 43008:
errmsg = "需要支付授權";
break;
case 43009:
errmsg = "需要員工已關注";
break;
case 43010:
errmsg = "需要處於回調模式";
break;
case 43011:
errmsg = "需要企業授權";
break;
case 44001:
errmsg = "多媒體文件為空";
break;
case 44002:
errmsg = "POST的數據包為空";
break;
case 44003:
errmsg = "圖文消息內容為空";
break;
case 44004:
errmsg = "文本消息內容為空";
break;
case 45001:
errmsg = "多媒體文件大小超過限制";
break;
case 45002:
errmsg = "消息內容超過限制";
break;
case 45003:
errmsg = "標題字段超過限制";
break;
case 45004:
errmsg = "描述字段超過限制";
break;
case 45005:
errmsg = "鏈接字段超過限制";
break;
case 45006:
errmsg = "圖片鏈接字段超過限制";
break;
case 45007:
errmsg = "語音播放時間超過限制";
break;
case 45008:
errmsg = "圖文消息超過限制";
break;
case 45009:
errmsg = "接口調用超過限制";
break;
case 45010:
errmsg = "創建菜單個數超過限制";
break;
case 45015:
errmsg = "回復時間超過限制";
break;
case 45016:
errmsg = "系統分組,不允許修改";
break;
case 45017:
errmsg = "分組名字過長";
break;
case 45018:
errmsg = "分組數量超過上限";
break;
case 45024:
errmsg = "賬號數量超過上限";
break;
case 46001:
errmsg = "不存在媒體數據";
break;
case 46002:
errmsg = "不存在的菜單版本";
break;
case 46003:
errmsg = "不存在的菜單數據";
break;
case 46004:
errmsg = "不存在的員工";
break;
case 47001:
errmsg = "解析JSON/XML內容錯誤";
break;
case 48002:
errmsg = "Api禁用";
break;
case 50001:
errmsg = "redirect_uri未授權";
break;
case 50002:
errmsg = "員工不在權限范圍";
break;
case 50003:
errmsg = "應用已停用";
break;
case 50004:
errmsg = "員工狀態不正確(未關注狀態) ";
break;
case 50005:
errmsg = "企業已禁用";
break;
case 60001:
errmsg = "部門長度不符合限制";
break;
case 60002:
errmsg = "部門層級深度超過限制";
break;
case 60003:
errmsg = "部門不存在";
break;
case 60004:
errmsg = "父親部門不存在";
break;
case 60005:
errmsg = "不允許刪除有成員的部門";
break;
case 60006:
errmsg = "不允許刪除有子部門的部門";
break;
case 60007:
errmsg = "不允許刪除根部門";
break;
case 60008:
errmsg = "部門名稱已存在";
break;
case 60009:
errmsg = "部門名稱含有非法字符";
break;
case 60010:
errmsg = "部門存在循環關系";
break;
case 60011:
errmsg = "管理員權限不足,(user/department/agent)無權限";
break;
case 60012:
errmsg = "不允許刪除默認應用";
break;
case 60013:
errmsg = "不允許關閉應用";
break;
case 60014:
errmsg = "不允許開啟應用";
break;
case 60015:
errmsg = "不允許修改默認應用可見范圍";
break;
case 60016:
errmsg = "不允許刪除存在成員的標簽";
break;
case 60017:
errmsg = "不允許設置企業";
break;
case 60102:
errmsg = "UserID已存在";
break;
case 60103:
errmsg = "手機號碼不合法";
break;
case 60104:
errmsg = "手機號碼已存在";
break;
case 60105:
errmsg = "郵箱不合法";
break;
case 60106:
errmsg = "郵箱已存在";
break;
case 60107:
errmsg = "微信號不合法";
break;
case 60108:
errmsg = "微信號已存在";
break;
case 60109:
errmsg = "QQ號已存在";
break;
case 60110:
errmsg = "部門個數超出限制";
break;
case 60111:
errmsg = "UserID不存在";
break;
case 60112:
errmsg = "成員姓名不合法";
break;
case 60113:
errmsg = "身份認證信息(微信號/手機/郵箱)不能同時為空 ";
break;
case 60114:
errmsg = "性別不合法";
break;
default:
errmsg = "沒有此錯誤碼! ";
break;
}
return errmsg;
}
}
WxUser類的結構如下,省get\set
public class WxUser
{
// 必填, 成員UserID。對應管理端的帳號,企業內必須唯一。不區分大小寫,長度為1~64個字節
private String userid;
// 必填,成員名稱。長度為1~64個字節
private String name;
// 必填,成員所屬部門id列表,不超過20個
private String[] department;
private String deptId;
// 職位信息。長度為0~64個字節
private String position;
// 手機號碼。企業內必須唯一,mobile/weixinid/email三者不能同時為空
private String mobile;
// 性別。1表示男性,2表示女性
private String gender;
// 郵箱。長度為0~64個字節。企業內必須唯一
private String email;
// 微信號。企業內必須唯一。(注意:是微信號,不是微信的名字)
private String weixinid;
// 成員頭像的mediaid,通過多媒體接口上傳圖片獲得的mediaid
private String avatar_mediaid;
private String avatar;
private String status;
}
