|
@@ -0,0 +1,285 @@
|
|
|
+package com.ematou.wxbase.service.impl;
|
|
|
+
|
|
|
+import com.ematou.wxbase.common.constant.RedisKey;
|
|
|
+import com.ematou.wxbase.common.constant.TokenType;
|
|
|
+import com.ematou.wxbase.config.WxGeneralConfig;
|
|
|
+import com.ematou.wxbase.entity.OperationRecord;
|
|
|
+import com.ematou.wxbase.entity.TicketRecord;
|
|
|
+import com.ematou.wxbase.entity.TokenRecord;
|
|
|
+import com.ematou.wxbase.exception.Assert;
|
|
|
+import com.ematou.wxbase.exception.ServiceException;
|
|
|
+import com.ematou.wxbase.mapper.OperationRecordMapper;
|
|
|
+import com.ematou.wxbase.mapper.TicketRecordMapper;
|
|
|
+import com.ematou.wxbase.service.TicketRecordService;
|
|
|
+import com.ematou.wxbase.service.TokenRecordService;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.security.MessageDigest;
|
|
|
+import java.text.ParseException;
|
|
|
+import java.time.Duration;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 微信票据接口实现类
|
|
|
+ *
|
|
|
+ * @author frankeleyn
|
|
|
+ * @email lvjian@qhdswl.com
|
|
|
+ * @date 2023/5/11 16:04
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class TicketRecordServiceImpl implements TicketRecordService {
|
|
|
+
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(TicketRecordServiceImpl.class);
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RestTemplate restTemplate;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private WxGeneralConfig wxGeneralConfig;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private TokenRecordService tokenRecordService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private OperationRecordMapper operationRecordMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private TicketRecordMapper ticketRecordMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RedisTemplate<String, Object> redisTemplate;
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取微信票据
|
|
|
+ *
|
|
|
+ * @param url
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public TicketRecord getTicket(String url) {
|
|
|
+ // 获取缓存中的 wxTicket
|
|
|
+ TicketRecord ticketRecord =
|
|
|
+ (TicketRecord) redisTemplate.opsForValue().get(RedisKey.GET_WX_TICKET);
|
|
|
+
|
|
|
+ if (Objects.nonNull(ticketRecord)) {
|
|
|
+ // 如果缓存有数据直接返回
|
|
|
+ return ticketRecord;
|
|
|
+ }else {
|
|
|
+ // 如果没有缓存就生成一个
|
|
|
+ TicketRecord record = genTicket(url);
|
|
|
+ // 将E证通 token 设置进 Redis,并设置过期时间
|
|
|
+ redisTemplate.opsForValue().set(RedisKey.GET_WX_TICKET, record, Duration.ofSeconds(7200L));
|
|
|
+ return record;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成微信票据
|
|
|
+ *
|
|
|
+ * @param url
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public TicketRecord genTicket(String url) {
|
|
|
+
|
|
|
+ Map<String, String> result = new HashMap<>();
|
|
|
+ // 获取微信 token
|
|
|
+ TokenRecord latestToken = null;
|
|
|
+ try {
|
|
|
+ latestToken = tokenRecordService.getLatestToken();
|
|
|
+ } catch (ParseException e) {
|
|
|
+ logger.error("获取微信 token 失败", e);
|
|
|
+ throw new RuntimeException("获取微信 token 失败!");
|
|
|
+ }
|
|
|
+
|
|
|
+ String accessToken = latestToken.getAccessToken();
|
|
|
+ logger.info("微信返回的 accessToken => {}", accessToken);
|
|
|
+
|
|
|
+ // 请求微信 url
|
|
|
+ String reqWxUrl = String.format(wxGeneralConfig.getTicketUrl(), accessToken);
|
|
|
+ logger.info("请求微信票据 url => {}", reqWxUrl);
|
|
|
+
|
|
|
+ Map wxRes = null;
|
|
|
+ try {
|
|
|
+ wxRes = restTemplate.getForObject(reqWxUrl, Map.class);
|
|
|
+ logger.info("请求微信票据返回信息 => {}", wxRes );
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("请求微信票据失败 ", e);
|
|
|
+ throw new ServiceException("请求微信票据失败!");
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.info("微信返回信息 => {}", wxRes);
|
|
|
+
|
|
|
+ if (null == wxRes) {
|
|
|
+ logger.error("请求微信票据失败!errorMessage:response is null");
|
|
|
+ throw new ServiceException("请求微信票据失败,微信响应为空!");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取返回的 ticket 和过期时间
|
|
|
+ String ticket = (String) wxRes.get("ticket");
|
|
|
+ Integer expiresIn = (Integer) wxRes.get("expires_in");
|
|
|
+
|
|
|
+ if(!StringUtils.hasLength(ticket) && expiresIn == null){
|
|
|
+ // 如果票据和过期时间不存在
|
|
|
+ Object errcode = wxRes.get("errcode");
|
|
|
+ Object errmsg = wxRes.get("errmsg");
|
|
|
+ logger.error("请求微信票据失败! => {}", wxRes);
|
|
|
+ throw new ServiceException("请求微信票据失败! errorCode: " + errcode + ",errorMessage:" + errmsg);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成随机字符串
|
|
|
+ String nonceStr = createNonceStr();
|
|
|
+ // 生成时间戳
|
|
|
+ String timestamp = createTimeStamp();
|
|
|
+ // 组装签名串
|
|
|
+ String signParam = getSignParam(url, ticket, nonceStr, timestamp);
|
|
|
+ // 签名
|
|
|
+ String signature = getSign(signParam);
|
|
|
+ logger.info("签名 => {}", signature);
|
|
|
+ // 插入操作记录
|
|
|
+ insertOprationRecord();
|
|
|
+ // 插入微信 ticket
|
|
|
+ TicketRecord ticketRecord = insertTicketRecord(url, ticket, expiresIn, nonceStr, timestamp, signature);
|
|
|
+ result.put("appId", wxGeneralConfig.getAppId());
|
|
|
+ result.put("ticket", ticket);
|
|
|
+ result.put("timestamp", timestamp);
|
|
|
+ result.put("nonceStr", nonceStr);
|
|
|
+ result.put("signature", signature);
|
|
|
+
|
|
|
+ // 获取微信票据
|
|
|
+ return ticketRecord;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 插入 wx ticket
|
|
|
+ * @param url
|
|
|
+ * @param ticket
|
|
|
+ * @param expiresIn
|
|
|
+ * @param nonceStr
|
|
|
+ * @param timestamp
|
|
|
+ * @param signature
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private TicketRecord insertTicketRecord(String url, String ticket, Integer expiresIn, String nonceStr, String timestamp, String signature) {
|
|
|
+ // 生成时间
|
|
|
+ LocalDateTime generationTime = LocalDateTime.now();
|
|
|
+ // 过期时间
|
|
|
+ LocalDateTime expireTime = generationTime.plusSeconds(expiresIn.longValue());
|
|
|
+ // 对时间进行格式化
|
|
|
+ String generationTimeStr = generationTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
|
|
+ String expireTimeStr = expireTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
|
|
+
|
|
|
+ // 插入ticket 记录
|
|
|
+ TicketRecord ticketRecord =
|
|
|
+ TicketRecord.builder()
|
|
|
+ .appId(wxGeneralConfig.getAppId())
|
|
|
+ .ticket(ticket)
|
|
|
+ .generationTime(generationTimeStr)
|
|
|
+ .expireTime(expireTimeStr)
|
|
|
+ .expiresIn(expiresIn)
|
|
|
+ .timeStamp(timestamp)
|
|
|
+ .nonceStr(nonceStr)
|
|
|
+ .sign(signature)
|
|
|
+ .reqUrl(url)
|
|
|
+ .build();
|
|
|
+ int insert = ticketRecordMapper.insertRecord(ticketRecord);
|
|
|
+ Assert.notTrue(insert < 1, "插入微信 ticket 记录失败!");
|
|
|
+ return ticketRecord;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 插入操作记录
|
|
|
+ */
|
|
|
+ private void insertOprationRecord() {
|
|
|
+ // 插入操作记录
|
|
|
+ OperationRecord operationRecord =
|
|
|
+ OperationRecord.builder()
|
|
|
+ .appCode("暂无")
|
|
|
+ .tokenType(TokenType.WX_TICKET.getItem())
|
|
|
+ .build();
|
|
|
+ operationRecordMapper.insertRecord(operationRecord);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装加签字符串
|
|
|
+ *
|
|
|
+ * @param url
|
|
|
+ * @param ticket
|
|
|
+ * @param nonceStr
|
|
|
+ * @param timestamp
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String getSignParam(String url, String ticket, String nonceStr, String timestamp) {
|
|
|
+ // 加签字符串
|
|
|
+ String string1;
|
|
|
+ //注意这里参数名必须全部小写,且必须有序
|
|
|
+ string1 = "jsapi_ticket=" + ticket +
|
|
|
+ "&noncestr=" + nonceStr +
|
|
|
+ "×tamp=" + timestamp +
|
|
|
+ "&url=" + url;
|
|
|
+ logger.info("加签字符串 => {}", string1);
|
|
|
+ return string1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 签名
|
|
|
+ *
|
|
|
+ * @param string1 加签字符串
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String getSign(String string1) {
|
|
|
+ try {
|
|
|
+ MessageDigest crypt = MessageDigest.getInstance("SHA-1");
|
|
|
+ crypt.reset();
|
|
|
+ crypt.update(string1.getBytes("UTF-8"));
|
|
|
+ return byteToHex(crypt.digest());
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("签名失败!", e);
|
|
|
+ throw new RuntimeException("请求微信票据签名失败!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 产生随机串--由程序自己随机产生
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private static String createNonceStr() {
|
|
|
+ return UUID.randomUUID().toString().replace("-", "");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 由程序自己获取当前时间
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private static String createTimeStamp() {
|
|
|
+ return Long.toString(System.currentTimeMillis() / 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 随机加密
|
|
|
+ * @param hash
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private static String byteToHex(final byte[] hash) {
|
|
|
+ Formatter formatter = new Formatter();
|
|
|
+ for (byte b : hash)
|
|
|
+ {
|
|
|
+ formatter.format("%02x", b);
|
|
|
+ }
|
|
|
+ String result = formatter.toString();
|
|
|
+ formatter.close();
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|