1
0
Prechádzať zdrojové kódy

Merge branch 'feature/ticket' of csk/wxbase into master

Scott Chen 2 rokov pred
rodič
commit
cfe0d08ddd

+ 5 - 0
src/main/java/com/ematou/wxbase/common/constant/RedisKey.java

@@ -13,4 +13,9 @@ public class RedisKey {
      * 获取E证通 token 缓存键前缀
      */
     public static final String GET_EID_TOKEN = "get_eid_token:";
+
+    /**
+     * 获取 wx_ticket 缓存键
+     */
+    public static final String GET_WX_TICKET = "get_wx_ticket";
 }

+ 1 - 0
src/main/java/com/ematou/wxbase/common/constant/TokenType.java

@@ -18,6 +18,7 @@ public enum TokenType {
 
     WX_TOKEN("0", "微信 token"),
     EID_TOKEN("1", "E证通 token"),
+    WX_TICKET("2", "微信 ticket"),
     ;
 
 

+ 11 - 12
src/main/java/com/ematou/wxbase/config/WxGeneralConfig.java

@@ -1,18 +1,6 @@
 package com.ematou.wxbase.config;
 
-import com.ematou.wxbase.common.utils.DateUtils;
-import com.ematou.wxbase.entity.OperationRecord;
-import com.ematou.wxbase.entity.TokenRecord;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Component;
-import org.springframework.web.client.RestTemplate;
-
-import java.util.Date;
 
 /**
  * @author lhm
@@ -33,6 +21,9 @@ public class WxGeneralConfig {
     @Value("${wx.general.url}")
     private String url;
 
+    @Value("${wx.general.ticketUrl}")
+    private String ticketUrl;
+
     public String getAppId() {
         return appId;
     }
@@ -64,4 +55,12 @@ public class WxGeneralConfig {
     public void setUrl(String url) {
         this.url = url;
     }
+
+    public String getTicketUrl() {
+        return ticketUrl;
+    }
+
+    public void setTicketUrl(String ticketUrl) {
+        this.ticketUrl = ticketUrl;
+    }
 }

+ 52 - 0
src/main/java/com/ematou/wxbase/controller/TicketController.java

@@ -0,0 +1,52 @@
+package com.ematou.wxbase.controller;
+
+import com.ematou.wxbase.common.web.R;
+import com.ematou.wxbase.service.TicketRecordService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 获取微信 ticket 控制器
+ *
+ * @author frankeleyn
+ * @email lvjian@qhdswl.com
+ * @date 2023/5/11 15:49
+ */
+@RestController
+public class TicketController {
+
+    @Autowired
+    private TicketRecordService ticketRecordService;
+
+    /**
+     * 获取微信票据
+     *
+     * @param url
+     * @return
+     */
+    @GetMapping("/wxbase/getTicket")
+    public R<?> getTicket(String url) {
+        try {
+            return new R<>().success(ticketRecordService.getTicket(url));
+        } catch (Exception e) {
+            return new R<>().error("获取wxTicket失败!errorMessage: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 生成微信票据
+     *
+     * @param url
+     * @return
+     */
+    @GetMapping("/wxbase/genTicket")
+    public R<?> genTicket(String url) {
+        try {
+            return new R<>().success(ticketRecordService.genTicket(url));
+        } catch (Exception e) {
+            return new R<>().error("获取wxTicket失败!errorMessage: " + e.getMessage());
+        }
+    }
+
+}

+ 33 - 0
src/main/java/com/ematou/wxbase/entity/TicketRecord.java

@@ -0,0 +1,33 @@
+package com.ematou.wxbase.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 微信票据记录实体类
+ *
+ * @author frankeleyn
+ * @email lvjian@qhdswl.com
+ * @date 2023/5/11 17:04
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class TicketRecord {
+
+    private Integer id;
+    private String appId;// appid
+    private String ticket;// 微信票据
+    private String generationTime;// 生成时间
+    private String expireTime;// 过期时间
+    private Integer expiresIn;// 有效时间(单位:秒)
+    private String timeStamp;// 时间戳
+    private String nonceStr;// 随机字符串(用于签名)
+    private String sign;// 签名
+    private String reqUrl;// 请求的 Url
+
+
+}

+ 25 - 0
src/main/java/com/ematou/wxbase/mapper/TicketRecordMapper.java

@@ -0,0 +1,25 @@
+package com.ematou.wxbase.mapper;
+
+import com.ematou.wxbase.entity.TicketRecord;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 微信票据 Mapper
+ *
+ * @author frankeleyn
+ * @email lvjian@qhdswl.com
+ * @date 2023/5/11 19:56
+ */
+@Mapper
+public interface TicketRecordMapper {
+
+    /**
+     * 插入微信票据
+     *
+     * @param record
+     * @return
+     */
+    int insertRecord(TicketRecord record);
+
+
+}

+ 28 - 0
src/main/java/com/ematou/wxbase/service/TicketRecordService.java

@@ -0,0 +1,28 @@
+package com.ematou.wxbase.service;
+
+import com.ematou.wxbase.entity.TicketRecord;
+
+/**
+ * 微信 ticket 业务类
+ *
+ * @author frankeleyn
+ * @email lvjian@qhdswl.com
+ * @date 2023/5/11 15:57
+ */
+public interface TicketRecordService {
+
+    /**
+     * 获取微信票据
+     * @param url 请求 wxJSsdk 的网页 url
+     * @return
+     */
+    TicketRecord getTicket(String url);
+
+    /**
+     * 生成微信票据
+     *
+     * @param url 请求 wxJSsdk 的网页 url
+     */
+    TicketRecord genTicket(String url);
+
+}

+ 285 - 0
src/main/java/com/ematou/wxbase/service/impl/TicketRecordServiceImpl.java

@@ -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 +
+                "&timestamp=" + 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;
+    }
+
+}

+ 6 - 3
src/main/resources/application-test.yml

@@ -5,9 +5,12 @@ spring:
   # 数据源配置
   datasource:
     driver-class-name: com.mysql.cj.jdbc.Driver
-    username: tuser
-    password: Qq!123
-    url: jdbc:mysql://47.112.115.196:3306/wx_base?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
+#    username: tuser
+#    password: Qq!123
+#    url: jdbc:mysql://47.112.115.196:3306/wx_base?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
+    username: wx_base
+    password: goZ7ooGmxV
+    url: jdbc:mysql://out-rm-wz92efl25x02n44xego.mysql.rds.aliyuncs.com:3306/wx_base?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
   # redis 配置
   redis:
     database: 1

+ 1 - 0
src/main/resources/application.yml

@@ -4,6 +4,7 @@ wx:
     appSecret: 78413a82d0332ecbf7fdf475d0a8b08e
     grantType: client_credential
     url: https://api.weixin.qq.com/cgi-bin/token?grant_type=%s&appid=%s&secret=%s
+    ticketUrl: https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi
 
 spring:
   # 环境切换

+ 4 - 9
src/main/resources/logback.xml

@@ -18,7 +18,7 @@
     <!-- 2.如果日期没有变化,但是当前日志文件的大小超过1kb时,对当前日志进行分割 重名名 -->
     <appender name="syslog" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <!--<File>${catalina.base}/mylog/sys.log</File>-->
-        <File>/app/project/wx_base/logs/wxbase.log</File>
+        <File>logs/wxbase.log</File>
         <!-- rollingPolicy:当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名。 -->
         <!-- TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动 -->
         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
@@ -28,8 +28,7 @@
             <!-- 每产生一个日志文件,该日志文件的保存期限为30天 -->
             <maxHistory>30</maxHistory>
             <timeBasedFileNamingAndTriggeringPolicy  class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
-                <!-- maxFileSize:这是活动文件的大小,默认值是10MB,本篇设置为1KB,只是为了演示 -->
-                <maxFileSize>1KB</maxFileSize>
+                <maxFileSize>10MB</maxFileSize>
             </timeBasedFileNamingAndTriggeringPolicy>
         </rollingPolicy>
         <encoder>
@@ -44,11 +43,7 @@
     <!-- 控制台日志输出级别 -->
     <root level="info">
         <appender-ref ref="STDOUT" />
-    </root>
-    <!-- 指定项目中某个包,当有日志操作行为时的日志记录级别 -->
-    <!-- com.appley为根包,也就是只要是发生在这个根包下面的所有日志操作行为的权限都是DEBUG -->
-    <!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE  -->
-    <logger name="com.yjlc.service.impl.seckill" level="DEBUG">
         <appender-ref ref="syslog" />
-    </logger>
+    </root>
+
 </configuration>

+ 64 - 0
src/main/resources/mybatis/TicketRecordMapper.xml

@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ematou.wxbase.mapper.TicketRecordMapper">
+
+    <!-- 插入微信票据 -->
+    <insert id="insertRecord">
+        insert into wx_ticket_record
+        <trim prefix="(" suffix=")" suffixOverrides="," >
+            <if test="ticket != null">
+                ticket,
+            </if>
+            <if test="generationTime != null">
+                generation_time,
+            </if>
+            <if test="expireTime != null">
+                expire_time,
+            </if>
+            <if test="expiresIn != null">
+                expires_in,
+            </if>
+            <if test="timeStamp != null">
+                time_stamp,
+            </if>
+            <if test="nonceStr != null">
+                nonce_str,
+            </if>
+            <if test="sign != null">
+                sign,
+            </if>
+            <if test="reqUrl != null">
+                req_url,
+            </if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides="," >
+            <if test="ticket != null">
+                #{ticket},
+            </if>
+            <if test="generationTime != null">
+                #{generationTime},
+            </if>
+            <if test="expireTime != null">
+                #{expireTime},
+            </if>
+            <if test="expiresIn != null">
+                #{expiresIn},
+            </if>
+            <if test="timeStamp != null">
+                #{timeStamp},
+            </if>
+            <if test="nonceStr != null">
+                #{nonceStr},
+            </if>
+            <if test="sign != null">
+                #{sign},
+            </if>
+            <if test="reqUrl != null">
+                #{reqUrl},
+            </if>
+        </trim>
+    </insert>
+
+</mapper>