From 5ff781e1336374505403df1285f82b0d600b2ffb Mon Sep 17 00:00:00 2001 From: xiang2lin <251481237@qq.com> Date: Tue, 24 Sep 2024 08:42:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fw-weixin/pom.xml | 54 ++++ .../src/main/java/com/hzya/frame/Main.java | 5 + .../frame/wecom/service/IWeComService.java | 54 ++++ .../wecom/service/impl/WeComServiceImpl.java | 243 ++++++++++++++++++ .../frame/wecom/util/WeComAccessToken.java | 67 +++++ .../target/classes/com/hzya/frame/Main.class | Bin 0 -> 576 bytes .../classes/com/hzya/frame/wecom/Test.class | Bin 0 -> 279 bytes .../frame/wecom/service/IWeComService.class | Bin 0 -> 428 bytes .../wecom/service/impl/WeComServiceImpl.class | Bin 0 -> 5880 bytes .../frame/wecom/util/WeComAccessToken.class | Bin 0 -> 2807 bytes 10 files changed, 423 insertions(+) create mode 100644 fw-weixin/pom.xml create mode 100644 fw-weixin/src/main/java/com/hzya/frame/Main.java create mode 100644 fw-weixin/src/main/java/com/hzya/frame/wecom/service/IWeComService.java create mode 100644 fw-weixin/src/main/java/com/hzya/frame/wecom/service/impl/WeComServiceImpl.java create mode 100644 fw-weixin/src/main/java/com/hzya/frame/wecom/util/WeComAccessToken.java create mode 100644 fw-weixin/target/classes/com/hzya/frame/Main.class create mode 100644 fw-weixin/target/classes/com/hzya/frame/wecom/Test.class create mode 100644 fw-weixin/target/classes/com/hzya/frame/wecom/service/IWeComService.class create mode 100644 fw-weixin/target/classes/com/hzya/frame/wecom/service/impl/WeComServiceImpl.class create mode 100644 fw-weixin/target/classes/com/hzya/frame/wecom/util/WeComAccessToken.class diff --git a/fw-weixin/pom.xml b/fw-weixin/pom.xml new file mode 100644 index 00000000..5a3c515b --- /dev/null +++ b/fw-weixin/pom.xml @@ -0,0 +1,54 @@ + + + + com.hzya.frame + kangarooDataCenterV3 + ${revision} + + + 4.0.0 + fw-weixin + jar + ${revision} + + + + com.hzya.frame + base-service + ${revision} + + + mysql + mysql-connector-java + ${mysql-connector-java} + + + + + 8 + 8 + UTF-8 + + + + + org.springframework.boot + spring-boot-maven-plugin + + none + execute + true + + + + + repackage + + + + + + + \ No newline at end of file diff --git a/fw-weixin/src/main/java/com/hzya/frame/Main.java b/fw-weixin/src/main/java/com/hzya/frame/Main.java new file mode 100644 index 00000000..1817eb3e --- /dev/null +++ b/fw-weixin/src/main/java/com/hzya/frame/Main.java @@ -0,0 +1,5 @@ +package com.hzya.frame;public class Main { + public static void main(String[] args) { + System.out.println("Hello world!"); + } +} \ No newline at end of file diff --git a/fw-weixin/src/main/java/com/hzya/frame/wecom/service/IWeComService.java b/fw-weixin/src/main/java/com/hzya/frame/wecom/service/IWeComService.java new file mode 100644 index 00000000..aec0dd6d --- /dev/null +++ b/fw-weixin/src/main/java/com/hzya/frame/wecom/service/IWeComService.java @@ -0,0 +1,54 @@ +package com.hzya.frame.wecom.service; + +import com.alibaba.fastjson.JSONObject; +import com.hzya.frame.sysnew.application.entity.SysExtensionApiEntity; +import com.hzya.frame.web.entity.JsonResultEntity; + +/** + * @Description 企业微信service + * @Author xiangerlin + * @Date 2024/9/23 14:23 + **/ +public interface IWeComService { + /** + * 获取accessToken + * 该方法会缓存accessToken + * https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET + * @param jsonObject + * @return + */ + JSONObject accessToken(JSONObject jsonObject); + + /** + * 发送消息 + * https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN + * @param jsonObject + * @return + */ + JSONObject messageSend(JSONObject jsonObject); + + /** + * 根据授权码获取用户信息 + * 单点登录的时候用 + * https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=ACCESS_TOKEN&code=CODE + * @param jsonObject + * @return + */ + JSONObject getUserInfoByAuthCode(JSONObject jsonObject); + + /** + * 根据userid读取成员信息 + * https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&userid=USERID + * @param jsonObject + * @return + */ + JSONObject getUserInfoByUserId(JSONObject jsonObject); + + /** + * 根据手机号获取userid + * https://qyapi.weixin.qq.com/cgi-bin/user/getuserid?access_token=ACCESS_TOKEN + * @param jsonObject + * @return + */ + JSONObject getUserIdByMobile(JSONObject jsonObject); +} diff --git a/fw-weixin/src/main/java/com/hzya/frame/wecom/service/impl/WeComServiceImpl.java b/fw-weixin/src/main/java/com/hzya/frame/wecom/service/impl/WeComServiceImpl.java new file mode 100644 index 00000000..f8b36791 --- /dev/null +++ b/fw-weixin/src/main/java/com/hzya/frame/wecom/service/impl/WeComServiceImpl.java @@ -0,0 +1,243 @@ +package com.hzya.frame.wecom.service.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import com.alibaba.fastjson.JSONObject; +import com.hzya.frame.sysnew.application.entity.SysExtensionApiEntity; +import com.hzya.frame.web.entity.BaseResult; +import com.hzya.frame.web.entity.JsonResultEntity; +import com.hzya.frame.wecom.service.IWeComService; +import com.hzya.frame.wecom.util.WeComAccessToken; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +/** + * @Description 企业微信service + * @Author xiangerlin + * @Date 2024/9/23 14:24 + **/ +@Service(value = "weComServiceImpl") +public class WeComServiceImpl implements IWeComService { + static Logger logger = LoggerFactory.getLogger(WeComServiceImpl.class); + /** + * 获取accessToken + * https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET + * @param json + * @return + */ + @Override + public JSONObject accessToken(JSONObject json) { + JSONObject jsonObject = json.getJSONObject("jsonStr"); + if (null == jsonObject){ + return this.error("参数不能为空"); + } + String corpid = jsonObject.getString("corpid"); + String corpsecret = jsonObject.getString("corpsecret"); + if (StrUtil.isEmpty(corpid)) { + return this.error("corpid不能为空"); + } + if (StrUtil.isEmpty(corpsecret)) { + return this.error("corpsecret不能为空"); + } + try { + String accessToken = WeComAccessToken.getAccessToken(corpid, corpsecret); + return this.ok(accessToken); + }catch(Exception e){ + logger.error("获取accessToken出错",e); + } + return this.error("系统异常"); + } + + /** + * 发送消息 + * https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN + * @param json + * @return + */ + @Override + public JSONObject messageSend(JSONObject json) { + JSONObject jsonObject = json.getJSONObject("jsonStr"); + if (null == jsonObject){ + return this.error("参数不能为空"); + } + String agentid = jsonObject.getString("agentid"); + String text = jsonObject.getString("text"); + String access_token = jsonObject.getString("access_token"); + if (StrUtil.isEmpty(agentid)){ + return this.error("agentid不能为空"); + } + if (StrUtil.isEmpty(text)){ + return this.error("消息内容不能为空"); + } + if (StrUtil.isEmpty(access_token)){ + return this.error("access_token不能为空"); + } + String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token="+access_token; + jsonObject.remove("access_token"); + String param = jsonObject.toString(); + String res = HttpRequest.post(url).body(param).timeout(30000).execute().body(); + if (StrUtil.isNotEmpty(res)){ + JSONObject msgResponse = JSONObject.parseObject(res); + String errcode = msgResponse.getString("errcode"); + String errmsg = msgResponse.getString("errmsg"); + if ("0".equals(errcode)){ + return ok(); + }else { + return error(errmsg); + } + } + return this.error("操作失败"); + } + + /** + * 根据授权码获取用户信息 + * 单点登录的时候用 + * https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=ACCESS_TOKEN&code=CODE + * + * @param json + * @return + */ + @Override + public JSONObject getUserInfoByAuthCode(JSONObject json) { + JSONObject jsonObject = json.getJSONObject("jsonStr"); + if (null == jsonObject){ + return this.error("参数不能为空"); + } + String access_token = jsonObject.getString("access_token"); + String code = jsonObject.getString("code"); + if (StrUtil.isEmpty(access_token)){ + return error("access_token不能为空"); + } + if (StrUtil.isEmpty(code)){ + return error("code不能为空"); + } + String url = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token="+access_token+"&code="+code; + String res = HttpRequest.get(url).timeout(30000).execute().body(); + if (StrUtil.isNotEmpty(res)){ + JSONObject msgResponse = JSONObject.parseObject(res); + String errcode = msgResponse.getString("errcode"); + String errmsg = msgResponse.getString("errmsg"); + String userid = msgResponse.getString("userid"); + if ("0".equals(errcode)){ + return ok(userid); + }else { + return error(errmsg); + } + } + return null; + } + + /** + * 根据userid读取成员信息 + * https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&userid=USERID + * + * @param json + * @return + */ + @Override + public JSONObject getUserInfoByUserId(JSONObject json) { + JSONObject jsonObject = json.getJSONObject("jsonStr"); + if (null == jsonObject){ + return this.error("参数不能为空"); + } + String access_token = jsonObject.getString("access_token"); + String userid = jsonObject.getString("userid"); + if (StrUtil.isEmpty(access_token)){ + return error("access_token不能为空"); + } + if (StrUtil.isEmpty(userid)){ + return error("userid不能为空"); + } + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token="+access_token+"&userid="+userid; + String res = HttpRequest.get(url).timeout(30000).execute().body(); + if (StrUtil.isNotEmpty(res)){ + JSONObject msgResponse = JSONObject.parseObject(res); + String errcode = msgResponse.getString("errcode"); + String errmsg = msgResponse.getString("errmsg"); + if ("0".equals(errcode)){ + return ok(res); + }else { + return error(errmsg); + } + } + return null; + } + + /** + * 根据手机号获取userid + * https://qyapi.weixin.qq.com/cgi-bin/user/getuserid?access_token=ACCESS_TOKEN + * + * @param json + * @return + */ + @Override + public JSONObject getUserIdByMobile(JSONObject json) { + JSONObject jsonObject = json.getJSONObject("jsonStr"); + if (null == jsonObject){ + return this.error("参数不能为空"); + } + String access_token = jsonObject.getString("access_token"); + String mobile = jsonObject.getString("mobile"); + if (StrUtil.isEmpty(access_token)){ + return error("access_token不能为空"); + } + if (StrUtil.isEmpty(mobile)){ + return error("mobile不能为空"); + } + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserid?access_token="+access_token; + jsonObject.remove("access_token"); + String param = jsonObject.toString(); + String res = HttpRequest.post(url).body(param).timeout(30000).execute().body(); + if (StrUtil.isNotEmpty(res)){ + JSONObject msgResponse = JSONObject.parseObject(res); + String errcode = msgResponse.getString("errcode"); + String errmsg = msgResponse.getString("errmsg"); + String userid = msgResponse.getString("userid"); + if ("0".equals(errcode)){ + return ok(userid); + }else { + return error(errmsg); + } + } + return null; + } + + /** + * 成功 + * @return + */ + private static JSONObject ok(){ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code","200"); + jsonObject.put("msg","成功"); + jsonObject.put("data",""); + return jsonObject; + } + + /** + * 成功 + * @param data 返回数据 + * @return + */ + private static JSONObject ok(String data){ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code","200"); + jsonObject.put("msg","成功"); + jsonObject.put("data",data); + return jsonObject; + } + + /** + * 失败 + * @param msg 失败原因 + * @return + */ + private static JSONObject error(String msg){ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code","500"); + jsonObject.put("msg",msg); + jsonObject.put("data",""); + return jsonObject; + } +} diff --git a/fw-weixin/src/main/java/com/hzya/frame/wecom/util/WeComAccessToken.java b/fw-weixin/src/main/java/com/hzya/frame/wecom/util/WeComAccessToken.java new file mode 100644 index 00000000..78b6dc19 --- /dev/null +++ b/fw-weixin/src/main/java/com/hzya/frame/wecom/util/WeComAccessToken.java @@ -0,0 +1,67 @@ +package com.hzya.frame.wecom.util; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import com.alibaba.fastjson.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.time.Instant; + +/** + * @Description 获取企业微信accesToken + * @Author xiangerlin + * @Date 2024/9/23 14:08 + **/ +public class WeComAccessToken { + + static Logger logger = LoggerFactory.getLogger(WeComAccessToken.class); + //token + private static String accessToken; + //过期时间 + private static Instant expireTime; + private static final Long CACHE_EXPIRY_TIME = 7000L; // 缓存有效时间(秒) + + + /** + * 获取accessToken + * + * @param corpid 企业ID + * @param corpsecret 应用的凭证密钥 + * @return + */ + public static String getAccessToken(String corpid,String corpsecret) { + //判断是否过期 如果没过期直接返回 + if (null != accessToken && expireTime != null && Instant.now().isBefore(expireTime)) { + return accessToken; + } + //获取新的accessToken + accessToken = fetchNewAccessToken(corpid,corpsecret); + //过期时间设置成当前事件+7000s,预留200s的时间 + expireTime = Instant.now().plusSeconds(CACHE_EXPIRY_TIME); + return accessToken; + } + + /** + * 获取信的token + * @param corpid + * @param corpsecret + * @return + */ + private static String fetchNewAccessToken(String corpid, String corpsecret) { + String url = " https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid="+corpid+"&corpsecret="+corpsecret; + String response = HttpRequest.get(url).timeout(30000).execute().body(); + if (StrUtil.isNotEmpty(response)){ + JSONObject json = JSONObject.parseObject(response); + String accessToken = json.getString("access_token"); + return accessToken; + } + return null; + } + + public static void main(String[] args) { + for (int i=0; i<2; i++){ + String accessToken1 = WeComAccessToken.getAccessToken("wwb46c3f5e6ffe3e2b", "oON2ELxNVyl7wc37LeA9bNOsv_jyuFXdrvD9e0yogbQ"); + System.out.println(accessToken1); + } + } +} diff --git a/fw-weixin/target/classes/com/hzya/frame/Main.class b/fw-weixin/target/classes/com/hzya/frame/Main.class new file mode 100644 index 0000000000000000000000000000000000000000..fcf604dc32d90998f8f726d809a0b84c22d4e9c1 GIT binary patch literal 576 zcmZuuyH3ME5S+`)u`whB!ut`R2^6?Nq5#n#A&T&jg3?f3k|Q}}`;g;+;IE(niGmN{ zqYz_*6p&)cyWZKonH}Hf*ZT*6J#0BBplV|d^F=H;Sj3WrWeY0;g+m?c_(&jI-);%y zj!jnylo~oz=fj|_qAS_FT9bh410 zi0mf3E-Vx+th!jkx`2DCeBaa_OyqYrEYw_VV3R6icBIN^v5WSt>csz`=Hnn%0soB| zvQhnUp^bM*3o(r-8PMiT%JCl-uQYZI^#6}s@#4CLBG|xmw Ij2X;+1H{>R5C8xG literal 0 HcmV?d00001 diff --git a/fw-weixin/target/classes/com/hzya/frame/wecom/Test.class b/fw-weixin/target/classes/com/hzya/frame/wecom/Test.class new file mode 100644 index 0000000000000000000000000000000000000000..32e87025ee472c7091e1e997a4904190ce3d2942 GIT binary patch literal 279 zcmZ{ey>7xl5QJxqe}XZ=qC=_ZAVsc87nBBx%7Oxm{(Ky8$Ul-XLFBDcMWWyV@=#Is z5N&p)(axvc(f<5it^i&UhiIW2;4wgt;7?3xns-8LG+q(hX|>jbUSdi;KNgv;Q<>#V z`bni^zLK@E{tn${XAXp?L{){@eV(P*)Uwdxq%BDGp?M>O%j#GwJu^0@ZQ;vae#i(e zJi_4LQ$lR_L@vv%SY&&x8Vup^^R|ea16X}_;^qPG`5W}bz(K(3GX@^Gf#^1SXd`4t J=&(A7{s5q@I*0%O literal 0 HcmV?d00001 diff --git a/fw-weixin/target/classes/com/hzya/frame/wecom/service/IWeComService.class b/fw-weixin/target/classes/com/hzya/frame/wecom/service/IWeComService.class new file mode 100644 index 0000000000000000000000000000000000000000..4914de8e1c430e2b1ec33525303f9954a4e994d7 GIT binary patch literal 428 zcmaiwy-veG5QOJK5`)7(;vIm3Yv`y*BBUTfkPMNz zGt&0@{9lK_;goD*qDNJ!f-F_FfbVkF5nNhurHUz_G7>nA$v)RqP0G0$Ha82Kj7&rV zCmVd_VE554jfTzCglW!BKQN0*)9p$ePhMzbih!zRs_8OQOC@mO&Pv`JzRB$*-^c+j zbso7f!RxyjnX59E>y7&Y0v&!F(CZ3pq074Glt3R~e| literal 0 HcmV?d00001 diff --git a/fw-weixin/target/classes/com/hzya/frame/wecom/service/impl/WeComServiceImpl.class b/fw-weixin/target/classes/com/hzya/frame/wecom/service/impl/WeComServiceImpl.class new file mode 100644 index 0000000000000000000000000000000000000000..cab0982e9d8e6924e4e2052c54105b875784532b GIT binary patch literal 5880 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