DateUtils

代码:

  1. import org.joda.time.DateTime;
  2. import org.joda.time.Duration;
  3. import org.joda.time.format.DateTimeFormat;
  4. import org.joda.time.format.DateTimeFormatter;
  5. /**
  6. * @author miaohuanju001
  7. * @date 2021-06-18
  8. * @description
  9. */
  10. public class DateUtils {
  11. public static final String DATE_PATTERN_1 = "yyyy-MM-dd";
  12. public static final String DATE_PATTERN_2 = "yyyy-MM-dd HH:mm:ss";
  13. public static final String DATE_PATTERN_3 = "yyyyMMdd";
  14. public static final int WHOLE_DAY_MILLIS = 24 * 60 * 60 * 1000;
  15. public static final int WHOLE_DAY_SECONDS = 24 * 60 * 60;
  16. /**
  17. * 获取当前时间戳
  18. */
  19. public static long getCurrentTimestamp() {
  20. return System.currentTimeMillis();
  21. }
  22. /**
  23. * 获取当前日期的格式化形式
  24. * @param pattern "yyyy-MM-dd" / "yyyy-MM-dd HH:mm:ss" / ...
  25. * @return 当前日期的格式化形式
  26. */
  27. public static String now(String pattern){
  28. DateTime dateTime = DateTime.now();
  29. DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(pattern);
  30. return dateTimeFormatter.print(dateTime);
  31. }
  32. /**
  33. * 获取当前日期 -1d 的格式化形式
  34. * @param pattern "yyyy-MM-dd" / "yyyy-MM-dd HH:mm:ss" / ...
  35. */
  36. public static String yesterday(String pattern){
  37. String now = now(pattern);
  38. return datePlus(now, -1, pattern);
  39. }
  40. /**
  41. * 获取当前日期 +1d 的格式化形式
  42. * @param pattern "yyyy-MM-dd" / "yyyy-MM-dd HH:mm:ss" / ...
  43. */
  44. public static String tomorrow(String pattern){
  45. String now = now(pattern);
  46. return datePlus(now, 1, pattern);
  47. }
  48. /**
  49. * 获取当前日期的格式化形式 ,格式为 "yyyy-MM-dd HH:mm:ss"
  50. */
  51. public static String now(){
  52. return now(DATE_PATTERN_2);
  53. }
  54. /**
  55. * 获取当前时间的指定格式化格式
  56. *
  57. * @param pattern "yyyy-MM-dd" / "yyyy-MM-dd HH:mm:ss" / ...
  58. */
  59. public static String getFormatCurrentTime(String pattern) {
  60. DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(pattern);
  61. DateTime dateTime = DateTime.now();
  62. return dateTimeFormatter.print(dateTime);
  63. }
  64. /**
  65. * 格式化的日期转换为时间戳
  66. *
  67. * @param formatTime 格式化的日期
  68. * 如 2021-06-19 或 2021-06-19T00:00:00
  69. * @param pattern "yyyy-MM-dd" / "yyyy-MM-dd HH:mm:ss" / ...
  70. * @return 时间戳
  71. */
  72. public static long formatTime2Timestamp(String formatTime, String pattern) {
  73. DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(pattern);
  74. return DateTime.parse(formatTime, dateTimeFormatter).getMillis();
  75. }
  76. /**
  77. * 时间戳转换为格式化的日期
  78. *
  79. * @param timestamp 时间戳
  80. * @param pattern "yyyy-MM-dd" / "yyyy-MM-dd HH:mm:ss" 等
  81. * @return 格式化的日期
  82. */
  83. public static String timestamp2FormatTime(long timestamp, String pattern) {
  84. DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(pattern);
  85. return dateTimeFormatter.print(timestamp);
  86. }
  87. /**
  88. * 获取一天的最后一毫秒对应的时间戳
  89. * @param formatTime 格式化的日期
  90. * @param pattern "yyyy-MM-dd" / "yyyy-MM-dd HH:mm:ss" / ...
  91. * @return 传入日期当天的最后一毫秒对应的时间戳
  92. */
  93. public static long getLastMillisOfDay(String formatTime, String pattern){
  94. DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(pattern);
  95. DateTime dateTime = DateTime.parse(formatTime, dateTimeFormatter);
  96. return dateTime.millisOfDay().withMaximumValue().getMillis();
  97. }
  98. /**
  99. * 获取一天的第一毫秒对应的时间戳
  100. * @param formatTime 格式化的日期
  101. * @param pattern "yyyy-MM-dd" / "yyyy-MM-dd HH:mm:ss" / ...
  102. * @return 传入日期当天的第一毫秒对应的时间戳
  103. */
  104. public static long getFirstMillisOfDay(String formatTime, String pattern){
  105. DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(pattern);
  106. DateTime dateTime = DateTime.parse(formatTime, dateTimeFormatter);
  107. return dateTime.millisOfDay().withMinimumValue().getMillis();
  108. }
  109. /**
  110. * 判断 当前时间 是否达到(≥) 传入日期当日的开始时间
  111. * @param formatTime 格式化的日期
  112. * @param pattern "yyyy-MM-dd" / "yyyy-MM-dd HH:mm:ss" / ...
  113. * @return true/false
  114. */
  115. public static boolean nowReachDayStart(String formatTime, String pattern){
  116. return getCurrentTimestamp() >= getFirstMillisOfDay(formatTime, pattern);
  117. }
  118. /**
  119. * 判断 当前时间 是否达到(≥) 传入日期当日的结束时间
  120. * @param formatTime 格式化的日期
  121. * @param pattern "yyyy-MM-dd" / "yyyy-MM-dd HH:mm:ss" / ...
  122. * @return true/false
  123. */
  124. public static boolean nowReachDayEnd(String formatTime, String pattern){
  125. return getCurrentTimestamp() >= getLastMillisOfDay(formatTime, pattern);
  126. }
  127. /**
  128. * 判断当前时间是否达到指定时间
  129. * @param formatTime 格式化的日期
  130. * @param pattern "yyyy-MM-dd" / "yyyy-MM-dd HH:mm:ss" / ...
  131. * @return true/false
  132. */
  133. public static boolean nowReachSpecificTime(String formatTime, String pattern){
  134. return getCurrentTimestamp() >= formatTime2Timestamp(formatTime, pattern);
  135. }
  136. /**
  137. * formatTime2 - formatTime1 ,计算两者之间间隔多少天
  138. * @param formatTime1 格式化的日期1
  139. * @param formatTime2 格式化的日期2
  140. * @param pattern "yyyy-MM-dd" / "yyyy-MM-dd HH:mm:ss" / ...
  141. * @return 间隔天数
  142. */
  143. public static long dateDiff(String formatTime1, String formatTime2, String pattern){
  144. DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(pattern);
  145. DateTime dateTime1 = DateTime.parse(formatTime1, dateTimeFormatter);
  146. DateTime dateTime2 = DateTime.parse(formatTime2, dateTimeFormatter);
  147. long millisDiff = dateTime2.getMillis() - dateTime1.getMillis();
  148. return millisDiff / WHOLE_DAY_MILLIS;
  149. }
  150. /**
  151. *
  152. * @param formatTime 格式化的基础日期
  153. * @param plusDays 在基础日期上 增加(+)/减去(-) 的天数
  154. * @param pattern 输入及输出的日期格式 "yyyy-MM-dd" / "yyyy-MM-dd HH:mm:ss" / ...
  155. * @return 基础日期增加后的日期
  156. */
  157. public static String datePlus(String formatTime, int plusDays, String pattern){
  158. DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(pattern);
  159. DateTime dateTime = DateTime.parse(formatTime, dateTimeFormatter);
  160. DateTime newDateTime = dateTime.plusDays(plusDays);
  161. return dateTimeFormatter.print(newDateTime);
  162. }
  163. /**
  164. * 展示时间类的一些用法
  165. */
  166. private void demo(){
  167. // 2021-06-19T09:38:08.963+08:00
  168. DateTime dateTime_Now = DateTime.now();
  169. /*
  170. 2021-06-19T00:00:00.000+08:00
  171. 如果不加pattern,日期之间必须用"-"分割
  172. */
  173. DateTime dateTime_0619_000000 = DateTime.parse("2021-06-19");
  174. /*
  175. 2021-06-19T15:00:00.000+08:00
  176. 如果不加pattern,日期和时间之间必须有T
  177. */
  178. DateTime dateTime_0619_150000 = DateTime.parse("2021-06-19T15:00:00");
  179. DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy/MM/dd HH_mm_ss");
  180. // 2021-06-19T22:22:22.000+08:00
  181. DateTime dateTime_0619_222222 = DateTime.parse("2021/06/19 22_22_22", dateTimeFormatter);
  182. // 2021-06-20T00:00:00.000+08:00
  183. DateTime dateTime_0620_000000 = DateTime.parse("2021-06-19").plusDays(1);
  184. // 2021-06-18T00:00:00.000+08:00
  185. DateTime dateTime_0618_000000 = DateTime.parse("2021-06-19").minusDays(1);
  186. // 获取长度为12个小时的duration
  187. Duration duration_12h = Duration.standardHours(12);
  188. // 获取长度为120分钟的duration
  189. Duration duration_120m = Duration.standardMinutes(120);
  190. // 获取 两个dateTime之间的duration
  191. Duration duration_betweenDateTime = new Duration(dateTime_0618_000000, dateTime_0619_000000);
  192. // 将duration转换为seconds 【86400】
  193. long duration_seconds = duration_betweenDateTime.getStandardSeconds();
  194. // 2021-06-19T12:00:00.000+08:00
  195. DateTime dateTime_0619_120000 = DateTime.parse("2021-06-19").plus(duration_12h);
  196. // 2021-06-19T02:00:00.000+08:00
  197. DateTime dateTime_0619_020000 = DateTime.parse("2021-06-19").plus(duration_120m);
  198. // 将 DateTime 转换为 时间戳
  199. // 2021-06-19 00:00:00 对应的时间戳 【1624032000000】
  200. long millis_0619_000000 = dateTime_0619_000000.getMillis();
  201. // 将 时间戳 转换为 DateTime
  202. // 时间戳 【1624032000000】 对应的 DateTime 为 2021-06-19T00:00:00.000+08:00
  203. dateTime_0619_000000 = new DateTime(millis_0619_000000);
  204. // 2021-06-19 00:00:00 对应的当天的时间戳 【0】
  205. // 相当于 dateTime_0619_000000.millisOfDay().get()
  206. int dayMillis_0619_000000 = dateTime_0619_000000.getMillisOfDay();
  207. // 2021-06-19T23:59:59.999+08:00 , 一天的最晚时间
  208. DateTime dateTime_0619_235959 = dateTime_0619_000000.millisOfDay().withMaximumValue();
  209. // 2021-06-19T00:00:00.000+08:00 , 一天的最早时间
  210. dateTime_0619_000000 = dateTime_0619_120000.millisOfDay().withMinimumValue();
  211. dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss:SSS");
  212. // 2021-06-19 22:22:22:000
  213. String str_0618_000000 = dateTimeFormatter.print(dateTime_0618_000000);
  214. // 2021-06-19 00:00:00:000
  215. String str_1624032000000L = dateTimeFormatter.print(1624032000000L);
  216. }
  217. }

pom依赖:

  1. <dependency>
  2. <groupId>joda-time</groupId>
  3. <artifactId>joda-time</artifactId>
  4. <version>2.9.4</version>
  5. </dependency>

HttpUtils

代码:

  1. import com.alibaba.fastjson.JSONObject;
  2. import org.springframework.http.*;
  3. import org.springframework.web.client.RestTemplate;
  4. import java.util.Map;
  5. /**
  6. * @author miaohuanju001
  7. * @date 2021-06-20
  8. * @description 依赖fastJSON 和 spring-web
  9. */
  10. public class HTTPUtils {
  11. private static final JSONObject EMPTY_JSON_OBJECT = new JSONObject();
  12. /**
  13. * 发送request请求
  14. *
  15. * @param domain 域名,例:http://localhost:8080
  16. * @param path 路径, 例:/api/addUser
  17. * @param paramMap 请求参数
  18. * @param httpMethod HttpMethod.POST/HttpMethod.GET/...
  19. * @return res
  20. */
  21. public static JSONObject sendRequest(String domain, String path, Map<String, Object> paramMap, HttpMethod httpMethod) {
  22. if (domain == null || "".equals(domain)){
  23. return EMPTY_JSON_OBJECT;
  24. }
  25. if (path == null){
  26. path = "";
  27. }
  28. String url = domain + path;
  29. return sendRequest(url, paramMap, httpMethod);
  30. }
  31. /**
  32. * 发送request请求
  33. *
  34. * @param url 请求url,例: http://localhost:8080/api/addUser
  35. * @param paramMap 请求参数
  36. * @param httpMethod HttpMethod.POST/HttpMethod.GET/...
  37. * @return res
  38. */
  39. public static JSONObject sendRequest(String url, Map<String, Object> paramMap, HttpMethod httpMethod) {
  40. if (url == null || "".equals(url)){
  41. return EMPTY_JSON_OBJECT;
  42. }
  43. RestTemplate restTemplate = new RestTemplate();
  44. // 请求头设置
  45. HttpHeaders httpHeaders = new HttpHeaders();
  46. httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); // 表示发送请求内容的格式
  47. httpHeaders.set("ucid", String.valueOf(1000000026751020L)); // 想请求头中添加一些自定义参数
  48. httpHeaders.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_UTF8_VALUE); // 设置接收的内容格式
  49. HttpEntity<Map<String, Object>> httpEntity;
  50. String requestUrl;
  51. JSONObject resJson = null;
  52. if (HttpMethod.GET == httpMethod) {
  53. httpEntity = new HttpEntity<>(httpHeaders);
  54. StringBuilder requestUrlBuilder = new StringBuilder(url);
  55. requestUrlBuilder.append('?');
  56. for (String key : paramMap.keySet()) {
  57. requestUrlBuilder.append(key).append('=').append(paramMap.get(key)).append('&');
  58. }
  59. requestUrlBuilder.deleteCharAt(requestUrlBuilder.length() - 1);
  60. requestUrl =requestUrlBuilder.toString();
  61. ResponseEntity<JSONObject> responseEntity = restTemplate.exchange(requestUrl, httpMethod, httpEntity, JSONObject.class);
  62. resJson = responseEntity.getBody();
  63. } else if (HttpMethod.POST == httpMethod) {
  64. requestUrl = url;
  65. httpEntity = new HttpEntity<>(paramMap, httpHeaders);
  66. ResponseEntity<JSONObject> responseEntity = restTemplate.exchange(requestUrl, httpMethod, httpEntity, JSONObject.class);
  67. resJson = responseEntity.getBody();
  68. }
  69. return resJson == null ? EMPTY_JSON_OBJECT : resJson;
  70. }
  71. }

pom依赖:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. <version>2.1.9.RELEASE</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.alibaba</groupId>
  8. <artifactId>fastjson</artifactId>
  9. <version>1.2.28</version>
  10. </dependency>

JSONUtils

代码:

  1. import com.alibaba.fastjson.JSONObject;
  2. /**
  3. * @author miaohuanju001
  4. * @date 2021-07-07
  5. * @description
  6. */
  7. public class JSONUtils {
  8. public static final JSONObject EMPTY_JSON_OBJECT = new JSONObject();
  9. public static JSONObject javaObject2JsonObject(Object object){
  10. return (JSONObject)JSONObject.toJSON(object);
  11. }
  12. public static <T> T jsonObject2JavaObject(JSONObject jsonObject, Class<T> clazz) {
  13. return jsonObject.toJavaObject(clazz);
  14. }
  15. public static String object2JsonStr(Object object){
  16. return JSONObject.toJSONString(object);
  17. }
  18. public static JSONObject jsonStr2JsonObject(String jsonStr){
  19. return (JSONObject)(JSONObject.parse(jsonStr));
  20. }
  21. }

pom依赖:

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>fastjson</artifactId>
  4. <version>1.2.28</version>
  5. </dependency>

OfficialAccountsUtils

发送企微公众号,只适用于**贝壳找房**

代码:

  1. import com.alibaba.fastjson.JSONObject;
  2. import lombok.Data;
  3. import org.springframework.http.HttpMethod;
  4. import org.springframework.http.MediaType;
  5. import org.springframework.util.DigestUtils;
  6. import org.springframework.util.LinkedMultiValueMap;
  7. import org.springframework.util.MultiValueMap;
  8. import java.util.ArrayList;
  9. import java.util.List;
  10. import java.util.Map;
  11. /**
  12. * @author miaohuanju001
  13. * @date 2021-07-10
  14. * @description 发送消息到企微公众号
  15. * https://wiki.lianjia.com/pages/viewpage.action?pageId=499305440
  16. */
  17. public class OfficialAccountsUtils {
  18. private static final String OFFICIAL_ACCOUNTS_URL = "https://wxwork.ke.com/api/v1/wechat/message/news";
  19. private static final String ODIN_APPID = "odin_notice";
  20. private static final String ODIN_TOKEN = "F6_Ibms5bx63rIBnDz5_dVhrQDGFAzo1G28lcwXopwQ=";
  21. /**
  22. * 发送到自定义企微公众号
  23. *
  24. * @param title title
  25. * @param usercodeList 工号list
  26. * @param appId 公众号唯一appId
  27. * @param token 公众号token
  28. * @param url 点击图片后的跳转链接
  29. * @param picurl 图片链接
  30. * @return 成功/错误信息
  31. */
  32. public static String sendToCustomOfficialAccounts(String title, List<String> usercodeList,
  33. String appId, String token,
  34. String url, String picurl) {
  35. StringBuilder sb = new StringBuilder();
  36. String touser;
  37. for (String usercode : usercodeList) {
  38. if (usercode.startsWith("10000000")) {
  39. String usercode1 = usercode.replace("10000000", "");
  40. sb.append(usercode1).append('|');
  41. } else {
  42. sb.append(usercode).append('|');
  43. }
  44. }
  45. if (sb.length() > 0) {
  46. touser = sb.deleteCharAt(sb.length() - 1).toString();
  47. } else {
  48. return "touser is invalid";
  49. }
  50. OfficialAccountsMessage officialAccountsMessage = createParam(title, touser, appId, token, url, picurl);
  51. MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
  52. JSONObject messageJson = JSONObject.parseObject(JSONObject.toJSONString(officialAccountsMessage));
  53. paramMap.setAll(messageJson.toJavaObject(Map.class));
  54. JSONObject jsonObject = HttpUtils2.sendRequest(OFFICIAL_ACCOUNTS_URL, paramMap, HttpMethod.POST, MediaType.APPLICATION_FORM_URLENCODED);
  55. String errorMsg = jsonObject.getString("errmsg");
  56. if (!Constants.OK.equals(errorMsg)) {
  57. return errorMsg;
  58. }
  59. return Constants.SUCCESS;
  60. }
  61. /**
  62. * 发送到奥丁企微公众号
  63. */
  64. public static String sendToOdinOfficialAccounts(String title, List<String> usercodeList, String url, String picurl) {
  65. return sendToCustomOfficialAccounts(title, usercodeList, ODIN_APPID, ODIN_TOKEN, url, picurl);
  66. }
  67. /**
  68. * 构建 参数
  69. * 公众号 发送图文消息
  70. *
  71. * @param url 点击图片后的跳转链接
  72. * @param picurl 图片地址链接
  73. * @param touser 用 | 分割的系统号
  74. */
  75. private static OfficialAccountsMessage createParam(String title, String touser,
  76. String appId, String token,
  77. String url, String picurl) {
  78. if (title == null || "".equals(title) ) {
  79. title = "title";
  80. }
  81. OfficialAccountsMessage officialAccountsMessage = new OfficialAccountsMessage();
  82. StringBuilder builder = new StringBuilder();
  83. long timestamp = DateUtils.getCurrentTimestamp(DateUtils.UNIT_SECOND);
  84. builder.append(timestamp);
  85. builder.append(appId);
  86. builder.append(token);
  87. String signature = DigestUtils.md5DigestAsHex(builder.toString().getBytes());
  88. officialAccountsMessage.setTouser(touser);
  89. officialAccountsMessage.setAppId(appId);
  90. officialAccountsMessage.setTimestamp(timestamp);
  91. officialAccountsMessage.setSignature(signature);
  92. officialAccountsMessage.setUrl(url);
  93. officialAccountsMessage.setTitle(title);
  94. officialAccountsMessage.setPicurl(picurl);
  95. return officialAccountsMessage;
  96. }
  97. /**
  98. * 企微公众号的消息内容
  99. */
  100. @Data
  101. public static class OfficialAccountsMessage {
  102. /**
  103. * 企微公众号唯一标识
  104. * 必填
  105. */
  106. private String appId;
  107. /**
  108. * 时间戳到秒
  109. * 必填
  110. */
  111. private long timestamp;
  112. /**
  113. * 签名:MD5(时间戳+appId+messageToken)
  114. * 必填
  115. */
  116. private String signature;
  117. /**
  118. * 接收人的链家系统号,比如:23009448,多个接收者用"|"分隔,最多支持1000个,特殊情况:指定为@all,则向该企业应用的全部成员发送
  119. * 非必填
  120. */
  121. private String touser;
  122. /**
  123. * 部门ID列表,多个用"|"分隔,最多支持100个,消息会发送给该部门及其所有子部门的员工。当touser为@all时忽略本参数
  124. * 非必填
  125. */
  126. private String toparty;
  127. /**
  128. * 标签ID列表,多个用"|"分隔,最多支持100个。当touser为@all时忽略本参数
  129. * 非必填
  130. */
  131. private String totag;
  132. /**
  133. * 点击图片后的跳转链接
  134. * 必填
  135. */
  136. private String url;
  137. /**
  138. * 图片链接
  139. * 必填
  140. */
  141. private String picurl;
  142. /**
  143. * title
  144. * 必填
  145. */
  146. private String title;
  147. }
  148. }

WeChatRobotUtils

发送企微群机器人消息

代码:

  1. import com.alibaba.fastjson.JSONObject;
  2. import lombok.Data;
  3. import org.codehaus.plexus.util.Base64;
  4. import org.springframework.http.*;
  5. import org.springframework.util.DigestUtils;
  6. import org.springframework.web.client.RestTemplate;
  7. import java.io.ByteArrayOutputStream;
  8. import java.io.File;
  9. import java.io.FileInputStream;
  10. import java.util.ArrayList;
  11. import java.util.List;
  12. /**
  13. * @author miaohuanju001
  14. * @date 2021-07-06
  15. * @description
  16. /**
  17. * 纯文本类型:
  18. * {
  19. * "msgtype": "text",
  20. * "text": {
  21. * "content": "广州今日天气:29度,大部分多云,降雨概率:60%",
  22. * "mentioned_list":["wangqing","@all"],
  23. * "mentioned_mobile_list":["13800001111","@all"]
  24. * }
  25. * }
  26. *
  27. * markdown类型:
  28. * {
  29. * "msgtype": "markdown",
  30. * "markdown": {
  31. * "content": "实时新增用户反馈<font color=\"warning\">132例</font>,请相关同事注意。\n
  32. * >类型:<font color=\"comment\">用户反馈</font>
  33. * >普通用户反馈:<font color=\"comment\">117例</font>
  34. * >VIP用户反馈:<font color=\"comment\">15例</font>"
  35. * }
  36. * }
  37. *
  38. * 图文类型:
  39. * {
  40. * "msgtype": "news",
  41. * "news": {
  42. * "articles" : [
  43. * {
  44. * "title" : "中秋节礼品领取",
  45. * "description" : "今年中秋节公司有豪礼相送",
  46. * "url" : "www.qq.com",
  47. * "picurl" : "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png"
  48. * }
  49. * ]
  50. * }
  51. * }
  52. *
  53. * 图片类型
  54. * {
  55. * "msgtype": "image",
  56. * "image": {
  57. * "base64": "DATA",
  58. * "md5": "MD5"
  59. * }
  60. * }
  61. */
  62. public class WeChatRobotUtils {
  63. private static RestTemplate restTemplate = new RestTemplate();
  64. /**
  65. * 发送图片
  66. */
  67. public static String sendPicture(File file, String webhookurl) {
  68. RobotMessage robotMessage = generatePicture(file);
  69. if (robotMessage == null){
  70. return "error";
  71. }
  72. return send(robotMessage,webhookurl);
  73. }
  74. /**
  75. * 发送图文类消息
  76. */
  77. public static String sendRobotNews(List<Article> articleList, String webhookurl) {
  78. RobotMessage robotMessage = generateNews(articleList);
  79. return send(robotMessage, webhookurl);
  80. }
  81. /**
  82. * 发送图文类消息
  83. */
  84. public static String sendRobotNews(Article article, String webhookurl) {
  85. RobotMessage robotMessage = generateNews(article);
  86. return send(robotMessage, webhookurl);
  87. }
  88. /**
  89. * 发送图文类消息
  90. */
  91. public static String sendRobotNews(String title, String description, String url, String picturl, String webhookurl) {
  92. Article article = new Article();
  93. article.setTitle(title);
  94. article.setDescription(description);
  95. article.setUrl(url);
  96. article.setPicurl(picturl);
  97. RobotMessage robotMessage = generateNews(article);
  98. return send(robotMessage, webhookurl);
  99. }
  100. /**
  101. * 发送markdown消息
  102. */
  103. public static String sendRobotMarkdown(String content, String webhookurl) {
  104. RobotMessage robotMessage = generateMarkdown(content);
  105. return send(robotMessage, webhookurl);
  106. }
  107. /**
  108. * 发送text消息, 不@任何人
  109. */
  110. public static String sendRobotText(String content, String webhookurl) {
  111. RobotMessage robotMessage = generateText(content);
  112. return send(robotMessage, webhookurl);
  113. }
  114. /**
  115. * 发送text消息,@人
  116. */
  117. public static String sendRobotText(String content, List<String> mentionedList, List<String> mentionedMobileList, String webhookurl) {
  118. RobotMessage robotMessage = generateText(content, mentionedList, mentionedMobileList);
  119. return send(robotMessage, webhookurl);
  120. }
  121. /**
  122. * 生成 图文类型的robot消息
  123. */
  124. private static RobotMessage generateNews(List<Article> articleList) {
  125. RobotMessage robotMessage = new RobotMessage();
  126. robotMessage.setMsgtype(RobotMessage.TYPE_NEWS);
  127. JSONObject jsonObject = new JSONObject();
  128. jsonObject.put("articles", articleList);
  129. robotMessage.setNews(jsonObject);
  130. return robotMessage;
  131. }
  132. /**
  133. * 生成 图文类型的robot消息
  134. */
  135. private static RobotMessage generateNews(Article article) {
  136. List<Article> articleList = new ArrayList<>();
  137. articleList.add(article);
  138. return generateNews(articleList);
  139. }
  140. /**
  141. * 生成 text消息
  142. *
  143. * @param content 消息内容
  144. * @param mentionedList @哪些人,传miaohuanju001这种
  145. * @param mentionedMobileList @哪些人,传手机号
  146. */
  147. private static RobotMessage generateText(String content, List<String> mentionedList, List<String> mentionedMobileList) {
  148. RobotMessage robotMessage = new RobotMessage();
  149. robotMessage.setMsgtype(RobotMessage.TYPE_TEXT);
  150. JSONObject jsonObject = new JSONObject();
  151. jsonObject.put("content", content);
  152. jsonObject.put("mentioned_list", mentionedList);
  153. jsonObject.put("mentioned_mobile_list", mentionedMobileList);
  154. robotMessage.setText(jsonObject);
  155. return robotMessage;
  156. }
  157. /**
  158. * 生成 text消息
  159. *
  160. * @param content 消息内容
  161. */
  162. private static RobotMessage generateText(String content) {
  163. return generateText(content, null, null);
  164. }
  165. /**
  166. * 生成 markdown消息
  167. *
  168. * @param content 消息内容
  169. */
  170. private static RobotMessage generateMarkdown(String content) {
  171. RobotMessage robotMessage = new RobotMessage();
  172. robotMessage.setMsgtype(RobotMessage.TYPE_TEXT);
  173. JSONObject jsonObject = new JSONObject();
  174. jsonObject.put("content", content);
  175. robotMessage.setMarkdown(jsonObject);
  176. return robotMessage;
  177. }
  178. private static RobotMessage generatePicture(File file){
  179. byte[] data;
  180. try {
  181. FileInputStream fis = new FileInputStream(file);
  182. ByteArrayOutputStream bos = new ByteArrayOutputStream(1024*1000*1000);
  183. byte[] b = new byte[1024*200];
  184. int n;
  185. while ((n = fis.read(b)) != -1) {
  186. bos.write(b, 0, n);
  187. }
  188. fis.close();
  189. data = bos.toByteArray();
  190. bos.close();
  191. String md5 = DigestUtils.md5DigestAsHex(data);
  192. String base64 = new String(Base64.encodeBase64(data));
  193. RobotMessage robotMessage = new RobotMessage();
  194. robotMessage.setMsgtype(RobotMessage.TYPE_IMAGE);
  195. JSONObject imageJson = new JSONObject();
  196. imageJson.put("md5",md5);
  197. imageJson.put("base64",base64);
  198. robotMessage.setImage(imageJson);
  199. return robotMessage;
  200. } catch (Exception e) {
  201. e.printStackTrace();
  202. return null;
  203. }
  204. }
  205. /**
  206. * 发送robot消息
  207. */
  208. private static String send(RobotMessage robotMessage, String webhookurl) {
  209. String jsonMessage = JSONObject.toJSONString(robotMessage);
  210. HttpHeaders httpHeaders = new HttpHeaders();
  211. httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); //发送内容的类型为 json
  212. HttpEntity<String> entity = new HttpEntity<>(jsonMessage, httpHeaders);
  213. ResponseEntity<String> resp = restTemplate.exchange(webhookurl, HttpMethod.POST, entity, String.class);
  214. String body = resp.getBody();
  215. JSONObject bodyJson = (JSONObject) (JSONObject.parse(body));
  216. String errorMsg = bodyJson.getString("errmsg");
  217. if (!"ok".equals(errorMsg)) {
  218. return errorMsg;
  219. }
  220. return "success";
  221. }
  222. @Data
  223. public static class Article {
  224. private String title;
  225. private String description;
  226. /**
  227. * 点击图片后的跳转链接
  228. */
  229. private String url;
  230. /**
  231. * 图片链接
  232. */
  233. private String picurl;
  234. }
  235. /**
  236. * 纯文本类型:
  237. * {
  238. * "msgtype": "text",
  239. * "text": {
  240. * "content": "广州今日天气:29度,大部分多云,降雨概率:60%",
  241. * "mentioned_list":["wangqing","@all"],
  242. * "mentioned_mobile_list":["13800001111","@all"]
  243. * }
  244. * }
  245. *
  246. * markdown类型:
  247. * {
  248. * "msgtype": "markdown",
  249. * "markdown": {
  250. * "content": "实时新增用户反馈<font color=\"warning\">132例</font>,请相关同事注意。\n
  251. * >类型:<font color=\"comment\">用户反馈</font>
  252. * >普通用户反馈:<font color=\"comment\">117例</font>
  253. * >VIP用户反馈:<font color=\"comment\">15例</font>"
  254. * }
  255. * }
  256. *
  257. * 图文类型:
  258. * {
  259. * "msgtype": "news",
  260. * "news": {
  261. * "articles" : [
  262. * {
  263. * "title" : "中秋节礼品领取",
  264. * "description" : "今年中秋节公司有豪礼相送",
  265. * "url" : "www.qq.com",
  266. * "picurl" : "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png"
  267. * }
  268. * ]
  269. * }
  270. * }
  271. *
  272. * 图片类型
  273. * {
  274. * "msgtype": "image",
  275. * "image": {
  276. * "base64": "DATA",
  277. * "md5": "MD5"
  278. * }
  279. * }
  280. */
  281. @Data
  282. public static class RobotMessage {
  283. public static final String TYPE_TEXT = "text";
  284. public static final String TYPE_MARKDOWN = "markdown";
  285. /**
  286. * 图文类型
  287. */
  288. public static final String TYPE_NEWS = "news";
  289. public static final String TYPE_IMAGE = "image";
  290. /**
  291. * 消息类型,
  292. * 文本:text
  293. * markdown
  294. * 图文:news
  295. */
  296. private String msgtype;
  297. private JSONObject text;
  298. private JSONObject markdown;
  299. /**
  300. * "base64"
  301. * "md5"
  302. */
  303. private JSONObject image;
  304. /**
  305. * "news":{"articles": [{article1},{article1}] }
  306. */
  307. private JSONObject news;
  308. public void makeNews(List<Article> articleList) {
  309. this.news = new JSONObject();
  310. news.put("articles", articleList);
  311. }
  312. }
  313. }

ZKUtils

代码:

  1. package zk;
  2. import org.apache.curator.RetryPolicy;
  3. import org.apache.curator.framework.CuratorFramework;
  4. import org.apache.curator.framework.CuratorFrameworkFactory;
  5. import org.apache.curator.framework.imps.CuratorFrameworkState;
  6. import org.apache.curator.framework.recipes.locks.InterProcessMutex;
  7. import org.apache.curator.retry.ExponentialBackoffRetry;
  8. import org.apache.zookeeper.CreateMode;
  9. import org.apache.zookeeper.KeeperException;
  10. import org.apache.zookeeper.data.Stat;
  11. import org.slf4j.Logger;
  12. import org.slf4j.LoggerFactory;
  13. import java.nio.charset.StandardCharsets;
  14. import java.util.ArrayList;
  15. import java.util.List;
  16. /**
  17. * @author miaohuanju001
  18. * @date 2021-07-20
  19. * @description zk对节点操作的api封装
  20. */
  21. public class ZKUtils {
  22. private static final Logger logger = LoggerFactory.getLogger(ZKUtils.class);
  23. public static int baseSleepTimeMs = 10; // 初始sleep时间
  24. public static int maxRetry = 3; // 最大重试次数
  25. public static String zkUrl = "zk01-test.xxx.com:2181"; // zk集群url
  26. public static String scheme = "digest"; // addauth digest #{auth}
  27. public static String auth = "xxxxxxxx"; // 为节点增加权限
  28. public static int sessionTimeoutMs = 20000; // 会话超时时间 20000ms
  29. public static int connectTimeoutMs = 15000; // 连接超时时间 15000ms
  30. public static CuratorFramework zkClient;
  31. static {
  32. RetryPolicy retryPolicy = new ExponentialBackoffRetry(baseSleepTimeMs, maxRetry);
  33. zkClient = CuratorFrameworkFactory.builder()
  34. .connectString(zkUrl)
  35. .authorization(scheme, auth.getBytes())
  36. .sessionTimeoutMs(sessionTimeoutMs)
  37. .connectionTimeoutMs(connectTimeoutMs)
  38. .retryPolicy(retryPolicy).build();
  39. zkClient.start();
  40. }
  41. /**
  42. * 获取 zk客户端的状态
  43. *
  44. * @return LATENT、STARTED、STOPPED
  45. */
  46. public static CuratorFrameworkState getZKClientState() {
  47. return zkClient.getState();
  48. }
  49. /**
  50. * 获取指定路径对应节点的信息
  51. *
  52. * @param path 节点路径,例如 /testZK/push/resource/10.224.65.216
  53. * @return 节点信息
  54. */
  55. public static String getNodeMessage(final String path) {
  56. try {
  57. byte[] bytes = zkClient.getData().forPath(path);
  58. return new String(bytes, StandardCharsets.UTF_8);
  59. } catch (Exception ex) {
  60. logger.error("get key : {}", path, ex);
  61. }
  62. return null;
  63. }
  64. /**
  65. * 获取指定路径下的子节点
  66. *
  67. * @param path 节点路径,例如 /testZK/push/resource
  68. * @return 指定节点的子节点信息
  69. */
  70. public static List<String> getChildrenNode(final String path) {
  71. try {
  72. return zkClient.getChildren().forPath(path);
  73. } catch (KeeperException.NoNodeException ex) {
  74. return new ArrayList<>();
  75. } catch (InterruptedException ex) {
  76. logger.error("getChildrenKeys path : {} InterruptedException", path);
  77. throw new IllegalStateException(ex);
  78. } catch (Exception ex) {
  79. logger.error("getChildrenKeys path : {}", path, ex);
  80. throw new RuntimeException(ex);
  81. }
  82. }
  83. /**
  84. * 判断指定路径下是否有节点
  85. *
  86. * @param path 指定路径
  87. */
  88. public static boolean hasChildren(final String path) {
  89. Stat stat;
  90. try {
  91. stat = zkClient.checkExists().forPath(path);
  92. return stat.getNumChildren() >= 1;
  93. } catch (Exception ex) {
  94. throw new IllegalStateException(ex);
  95. }
  96. }
  97. /**
  98. * 判断指定路径对应的节点是否存在
  99. *
  100. * @param path 指定的节点路径
  101. */
  102. public static boolean isExisted(final String path) {
  103. try {
  104. return zkClient.checkExists().forPath(path) != null;
  105. } catch (Exception ex) {
  106. logger.error("isExisted key : {}", path, ex);
  107. }
  108. return false;
  109. }
  110. /**
  111. * 创建永久节点,如果节点已存在则用输入信息覆盖
  112. *
  113. * @param path 指定的节点路径
  114. * @param nodeMessage 节点信息
  115. */
  116. public static void createPersistNode(final String path, final String nodeMessage) {
  117. try {
  118. if (!isExisted(path)) {
  119. zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path, nodeMessage.getBytes(StandardCharsets.UTF_8));
  120. } else {
  121. update(path, nodeMessage);
  122. }
  123. } catch (Exception ex) {
  124. logger.error("persist path : {} , nodeMessage : {}", path, nodeMessage, ex);
  125. }
  126. }
  127. /**
  128. * 创建临时节点
  129. *
  130. * @param path 指定的节点路径
  131. * @param nodeMessage 节点信息
  132. * @param overwrite 如果指定路径已存在,是否覆盖原有节点 true-覆盖 / false-不覆盖
  133. */
  134. public static void createEphemeralNode(final String path, final String nodeMessage, final boolean overwrite) {
  135. try {
  136. if (overwrite) {
  137. createEphemeralNode(path, nodeMessage);
  138. } else {
  139. if (!isExisted(path)) {
  140. zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path, nodeMessage.getBytes(StandardCharsets.UTF_8));
  141. }
  142. }
  143. } catch (final Exception ex) {
  144. logger.error("persistEphemeral path : {} , nodeMessage : {}, overwrite : {}", path, nodeMessage, overwrite, ex);
  145. }
  146. }
  147. /**
  148. * 更新节点信息,只更新节点本身存储的信息,不改变节点的子节点
  149. * 如果节点不存在就会抓到exception
  150. *
  151. * @param path 指定的节点路径
  152. * @param nodeMessage 节点信息
  153. */
  154. public static void update(final String path, final String nodeMessage) {
  155. try {
  156. zkClient.inTransaction().check().forPath(path).and().setData().forPath(path, nodeMessage.getBytes(StandardCharsets.UTF_8)).and().commit();
  157. } catch (Exception ex) {
  158. logger.error("update path : {} , nodeMessage : {}", path, nodeMessage, ex);
  159. }
  160. }
  161. /**
  162. * 创建带序列号的临时节点
  163. * <p>
  164. * exam: ("/zk/testNode", "this is a serial Ephemeral node") 以该入参执行3次会创建如下节点
  165. * [/zk/testNode0000000001, /zk/testNode0000000002, /zk/testNode0000000003]
  166. *
  167. * @param path 指定的节点路径
  168. * @param nodeMessage 节点信息
  169. */
  170. public static void persistEphemeralSequential(final String path, String nodeMessage) {
  171. try {
  172. zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path, nodeMessage.getBytes(StandardCharsets.UTF_8));
  173. } catch (final Exception ex) {
  174. logger.error("persistEphemeralSequential path : {}", path, ex);
  175. }
  176. }
  177. /**
  178. * 删除指定节点
  179. *
  180. * @param path 指定的节点路径
  181. */
  182. public static void remove(final String path) {
  183. try {
  184. if (isExisted(path)) {
  185. zkClient.delete().deletingChildrenIfNeeded().forPath(path);
  186. }
  187. } catch (KeeperException.NoNodeException ignore) {
  188. //NOP
  189. } catch (final Exception ex) {
  190. logger.error("remove path : {}", path, ex);
  191. }
  192. }
  193. /**
  194. * 创建临时节点
  195. *
  196. * @param path 指定的节点路径
  197. * @param nodeMessage 节点信息
  198. */
  199. private static void createEphemeralNode(final String path, final String nodeMessage) {
  200. try {
  201. if (isExisted(path)) {
  202. try {
  203. zkClient.delete().deletingChildrenIfNeeded().forPath(path);
  204. } catch (KeeperException.NoNodeException ignore) {
  205. //NOP
  206. }
  207. }
  208. zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path, nodeMessage.getBytes(StandardCharsets.UTF_8));
  209. } catch (final Exception ex) {
  210. logger.error("persistEphemeral path : {} , nodeMessage : {}", path, nodeMessage, ex);
  211. }
  212. }
  213. /**
  214. * 在指定节点下生成serial节点的方式获取锁,
  215. * 持有锁的线程可重入,其余抢锁线程会在 mutex.acquire() 后阻塞
  216. * 调用该方法后需要在finally中 mutex.release()释放锁
  217. *
  218. * @param path 指定的节点路径
  219. * @return InterProcessMutex mutex
  220. */
  221. public static InterProcessMutex getMasterBlockMutex(String path){
  222. InterProcessMutex mutex = null;
  223. try {
  224. mutex = new InterProcessMutex(zkClient, path);
  225. mutex.acquire();
  226. } catch (Exception e) {
  227. e.printStackTrace();
  228. }
  229. return mutex;
  230. }
  231. public static void main(String[] args) {
  232. remove("/testZK/asdfasfasdf");
  233. int x = 0;
  234. }
  235. }

pom依赖:

  1. <dependency>
  2. <groupId>org.apache.curator</groupId>
  3. <artifactId>curator-recipes</artifactId>
  4. <version>2.13.0</version>
  5. </dependency>