天天看点

小程序登录与接口鉴权、支付总结

小程序登录

官方文档链接: https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html

https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html

小程序登录1.前端调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器

小程序登录2.后端调用 auth.code2Session 接口,换取 用户唯一标识 OpenID

小程序登录3.前端获取手机号,后端进行手机号解密

https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html

代码展示

controller

@RestController
@RequestMapping("/auth")
public class AuthController {

    private Logger logger = LoggerFactory.getLogger(AuthController.class);


    @Autowired
    private AuthService authService;

    /**
     * 登陆逻辑:
     *  1.前端传入code,后台去微信请求当前code的sessionStatus。
     *  2.为该sessionStatus生成一个token。
     *  3.将token为key, sessionStatus为value存储到redis。
     *  4.将该token返回到客户端。
     * @param code code
     * @return 携带token的响应
     */
    @GetMapping("/login")
    @ApiOperation("微信小程序授权登录")
    public BaseResult<AuthInfoVo> login(@NotNull @RequestParam String code) {
        try {
            return authService.login(code);
        } catch (Exception e) {

            logger.error("user execute login error code:{}", code, e);
            return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getMsg());
        }
    }


    /**
     * 解密逻辑:
     *  1. 通过前端传入的token,去redis获取sessionStatus。
     *  2. 通过sessionStatus中的信息和前端传入的加密数据,解密出手机号。
     *  3. 将手机号返回给前端页面。
     * @param request
     * @param param param
     * @return 解密后的手机号
     */
    @ApiOperation("解密手机号")
    @PostMapping("/decryptPhone")
    public BaseResult<String> decrypt(HttpServletRequest request, @RequestBody JSONObject param) {
        String token = request.getHeader("Token");
        try {
            return authService.decrypt(token, param);
        } catch (Exception e) {

            logger.error("execute decrypt phone token:{}, param:{}", token, param.toJSONString(), e);
            return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getMsg());
        }
    }

    /**
     * 查询用户信息
     * @param param
     * @return
     */
    @ApiOperation("根据token查询加密的用户信息")
    @PostMapping("/findWxByToken")
    public BaseResult<String> findWxByToken(HttpServletRequest request) {
        try {
            BaseResult<String> result = authService.findWxByToken(request);
            return result;
        } catch (Exception e) {
            logger.error("findWxByToken appear exception, param:", e);
            return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getMsg());
        }
    }

}
           

service

@Service
public class AuthServiceImpl implements AuthService {

    private Logger logger = LoggerFactory.getLogger(AuthServiceImpl.class);

    @Qualifier("stringRedisTemplate")
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private UserWxRepository wxRepository;

    @Autowired
    RestTemplate restTemplate;

    @Autowired
    private IUserDefaultCanteenService iUserDefaultCanteenService;

    @Value("${spring.cache.redis.token-key-prefix}")
    private String redisTokenKeyPrefix;

    @Value("${spring.cache.redis.code-key-prefix}")
    private String redisCodeKeyPrefix;

    @Value("${wechat-pay.appId}")
    private String appid;

    @Value("${wechat-pay.secret}")
    private String secret;

    @Autowired
    private IrdsClient irdsClient;
    @Autowired
    private RedissonClient redisson;
    //锁失效时间
    private long lockLeaseTime = 10000L;
    //加锁超时时间,根据业务情况进行设置,如果只需要一个线程执行,可设置值为很小,如果是想串行化,可根据并发情况设置合理值。
    private long tryGetlockTime = 1L;

    @Autowired
    private UserCanteenService userCanteenService;

    @Autowired
    private CanteenService canteenService;

    public static boolean test = false;
    /**
     * 小程序登录1.调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器
     * @param code 小程序页面调用wx.login获取到的code
     * @return 附带token的返回体
     */
    @Override
    public  BaseResult<AuthInfoVo> login(String code) {
        logger.info("execute user login, code:{}", code);
        RLock lock=null;
        try {
            String body = sessionStatus(code);
            if (body != null && body.length() > 0) {
                JSONObject bodyEntity = JSONObject.parseObject(body);
                String sessionKey = bodyEntity.getString("session_key");
                String openid = bodyEntity.getString("openid");
                String unionId=bodyEntity.getString("unionid");
                lock = redisson.getLock(openid);

                //获取锁
                boolean flag = lock.tryLock(tryGetlockTime, lockLeaseTime, TimeUnit.MILLISECONDS);
                if(flag){
                    // 维护会话状态,返回唯一标识
                    String token = UUIDUtil.generateToken();
                    JSONObject userStatus = new JSONObject();
                    userStatus.put("openid", openid);
                    userStatus.put("sessionKey", sessionKey);
                    userStatus.put("unionid",unionId);
                    String tokenKey = redisTokenKeyPrefix + token;
                    // redis存储token,前端检查token失效后重新调用该接口生成token
                    redisTemplate.opsForValue().set(tokenKey, userStatus.toJSONString());
                    logger.info("小程序微信授权 wx login {}",userStatus.toJSONString());
                    // redisTemplate.opsForValue().setIfAbsent(tokenKey, userStatus.toJSONString(), 1, TimeUnit.DAYS);
                    //将微信用户入库
                    UserWxEntity wxEntity = wxRepository.findByOpenId(openid);
                    if(wxEntity==null){
                        wxEntity=new UserWxEntity();
                        wxEntity.setOpenId(openid);
                        wxEntity.setSessionKey(sessionKey);
                        wxEntity.setUnionId(unionId);
                        wxEntity.setToken(tokenKey);
                        wxEntity.setOrgin(0);
                    }else{
                        wxEntity.setSessionKey(sessionKey);
                        wxEntity.setUnionId(unionId);
                        wxEntity.setToken(tokenKey);
                    }
                    wxRepository.save(wxEntity);
                    AuthInfoVo vo = new AuthInfoVo();
                    vo.setToken(tokenKey);
                    return BaseResult.success(vo);
                }else {
                    logger.info("login repeat ");
                    return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_REPEAT_LOGIN_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_REPEAT_LOGIN_ERROR.getMsg());
                }

            } else {
                logger.info("execute get user session status, body is null");
                return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_REMOTE_API_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_REMOTE_API_ERROR.getMsg());
            }
        } catch (Exception e) {
            logger.error("handle user session status failure", e);
            return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getMsg());
        }finally {
            //释放锁
            if(lock!=null){
                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    }

    /**
     * 解密手机号
     * @param token token
     * @param param 原始参数
     * @return 附带手机号的返回体
     */
    @Override
    public BaseResult<String> decrypt(String token, JSONObject param) {
        try {
            logger.info("will execute decrypt, token:{}, param:{}", token, param.toJSONString());
            if (test) {
                String phoneNumber = UUIDUtil.createPhoneNum();
                return BaseResult.success(phoneNumber);
            }
            String userStatus = redisTemplate.opsForValue().get(token);
            if (StringUtils.isBlank(userStatus)) {
                logger.error("not find sessionStatus about with this token, token:{}", token);
                return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getMsg());
            }
            UserWxEntity wxEntity= wxRepository.findByToken(token);
            if(wxEntity==null){
                return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getMsg());
            }
            // sessionKey
            JSONObject userStatusResp = JSONObject.parseObject(userStatus);
            String sessionKey = userStatusResp.getString("sessionKey");
            // encryptedData iv
            String encryptedData = param.getString("encryptedData");
            String iv = param.getString("iv");
            AESCBCUtil aescbcUtil = new AESCBCUtil(encryptedData, sessionKey, iv);
            String result = aescbcUtil.decrypt();
            JSONObject resultRes = JSONObject.parseObject(result);
            logger.info("execute decrypt end, resultRes:{}", resultRes.toJSONString());
            String phoneNumber = resultRes.get("phoneNumber").toString().replaceAll("\"", "");

            UserWxEntity byPhone = wxRepository.findByPhone(phoneNumber);
            if(byPhone!=null){
                logger.info("will execute decrypt,repeatable phone:{}", JSON.toJSON(byPhone));
                logger.info("will execute decrypt,delete repeatable phone:{}", JSON.toJSON(byPhone));
                wxRepository.delete(byPhone);
            }
            wxEntity.setPhoneNo(phoneNumber);
            wxRepository.save(wxEntity);
            BaseResult<IrdsPersonInfoVo> irdsPersonInfoVoBaseResult = irdsClient.personInfo(phoneNumber);
            JSONObject jsonObject=new JSONObject();
            if(irdsPersonInfoVoBaseResult!=null&&irdsPersonInfoVoBaseResult.getData()!=null){
                jsonObject.put("login",true);
            }else {
                jsonObject.put("login",false);
            }
            jsonObject.put("phoneNo",phoneNumber);
            return BaseResult.success(jsonObject.toJSONString());
        } catch (Exception e) {

            logger.error("decrypt user phone data failure stack:{}, message:{}", e);
            return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_SYSTEM_ERROR.getMsg());
        }
    }

    @Override
    public BaseResult<String> findWxByToken(HttpServletRequest request) throws Exception {
        logger.info("---------------------------findWxByToken start -------------------------------");
        String token = request.getHeader("token");
        if (StringUtils.isBlank(token)) {
            return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_NOT_TOKEN_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_NOT_TOKEN_ERROR.getMsg());
        }
        UserWxEntity userWxEntity = wxRepository.findByToken(token);

        if (userWxEntity == null || StringUtils.isBlank(userWxEntity.getOpenId()) || StringUtils.isBlank(userWxEntity.getPhoneNo())) {
            return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getMsg());
        }
        FindWxByTokenVo byToken = new FindWxByTokenVo();
        BeanUtils.copyProperties(userWxEntity, byToken);
        byToken.setIsAdmin(CommUtil.isNull(userCanteenService.queryByUserId(userWxEntity.getPhoneNo())) ? false : true);
        BaseResult<IrdsPersonInfoVo> personResult = irdsClient.personInfo(userWxEntity.getPhoneNo());


//        UserDefaultCanteenEntity canteenEntity = iUserDefaultCanteenService.getById(byToken.getPhoneNo());
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("openId", AESSecretUtil.encryptToStr(byToken.getOpenId(), AESSecretUtil.DATA_KEY));
        jsonObject.put("phone", byToken.getPhoneNo());
        jsonObject.put("isAdmin", byToken.getIsAdmin());
        jsonObject.put("orgin", byToken.getOrgin());
        jsonObject.put("token", AESSecretUtil.encryptToStr(token, AESSecretUtil.DATA_KEY));
        jsonObject.put("realName", userWxEntity.getRealName());
//        jsonObject.put("canteen", JsonUtil.toJson(canteenEntity));
        if (personResult != null && personResult.getData() != null &&
                StringUtils.isNotBlank(personResult.getData().getPhoneNo())) {

            List<CanteenUserVo> canteenUserVos = canteenService.listMerchantsByPersonId(userWxEntity.getPhoneNo());
            if(canteenUserVos!=null&&canteenUserVos.size()>0){
                jsonObject.put("loginAuth", 1);
                userWxEntity.setLoginAuth(1);
                wxRepository.save(userWxEntity);
            }else {
                jsonObject.put("loginAuth", 0);
                userWxEntity.setLoginAuth(0);
                wxRepository.save(userWxEntity);
            }

        } else {
            jsonObject.put("loginAuth", 0);
            userWxEntity.setLoginAuth(0);
            wxRepository.save(userWxEntity);
        }
        logger.info("---------------------------findWxByToken end -------------------------------");

        return BaseResult.success(jsonObject.toJSONString());
    }

    /**
     * 小程序登录2.调用 auth.code2Session 接口,换取 用户唯一标识 OpenID
     */
    private String sessionStatus(String code) {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity httpEntity = new HttpEntity<>(null, headers);
        String grantType = "authorization_code";
        String loginApi = PayUrlConstant.weiXinJscode2sessionApi;
        String domian = PayUrlConstant.weiXinDomain;
        String loginUrl = domian + loginApi + "?appid=" + appid + "&secret=" + secret + "&js_code=" + code + "&grant_type=" + grantType;
        logger.info("execute request from weixin url:{}", loginUrl);
        String body = "";
        // todo 添加失败重试
        try {
            ResponseEntity<String> respEntity = restTemplate.exchange(loginUrl, HttpMethod.GET, httpEntity, String.class);
            logger.info("execute request from weixin respEntity:{}", JSONObject.toJSONString(respEntity));
            HttpStatus statusCode = respEntity.getStatusCode();
            if (HttpStatus.OK.value() == statusCode.value()) {
                body = respEntity.getBody();
            }
        } catch (Exception e) {
            
            logger.error("execute request from weixin error, url:{}", loginUrl, e);
        }
        return body;
    }
}
           

工具类AESCBCUtil

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.spec.InvalidParameterSpecException;
import java.util.Base64;

/**
 * 写点注释吧
 *
 * @author gaozhilong
 * @date 2021/1/25 15:47
 * @since v1.0.0001
 */
public class AESCBCUtil {

    private Logger logger = LoggerFactory.getLogger(AESCBCUtil.class);

    /**
     * 加密方式
     */
    private static String keyAlgorithm = "AES";
    /**
     * 避免重复new生成多个BouncyCastleProvider对象,因为GC回收不了,会造成内存溢出
     * 只在第一次调用decrypt()方法时才new 对象
     */
    private static boolean initialized = false;
    /**
     * 用于Base64解密
     */
    private Base64.Decoder decoder = Base64.getDecoder();
    /**
     * 待解密的数据
     */
    private String encryptedData;
    /**
     * 会话密钥sessionKey
     */
    private String sessionKey;
    /**
     * 加密算法的初始向量
     */
    private String iv;

    public AESCBCUtil(String encryptedData, String sessionKey, String iv) {
        this.encryptedData = encryptedData;
        this.sessionKey = sessionKey;
        this.iv = iv;
    }

    /**
     * BouncyCastle作为安全提供,防止我们加密解密时候因为jdk内置的不支持改模式运行报错。
     */
    private static void initialize() {
        if (initialized) {
            return;
        }
        Security.addProvider(new BouncyCastleProvider());
        initialized = true;
    }

    /**
     * 生成iv
     * @param iv iv
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidParameterSpecException
     */
    private static AlgorithmParameters generateIV(byte[] iv) throws NoSuchAlgorithmException, InvalidParameterSpecException {
        AlgorithmParameters params = AlgorithmParameters.getInstance(keyAlgorithm);
        params.init(new IvParameterSpec(iv));
        return params;
    }

    public String decrypt() {
        initialize();
        try {
            //数据填充方式
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            Key sKeySpec = new SecretKeySpec(decoder.decode(this.sessionKey), keyAlgorithm);
            // 初始化
            cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(decoder.decode(this.iv)));
            byte[] data = cipher.doFinal(decoder.decode(this.encryptedData));
            return new String(data, StandardCharsets.UTF_8);
        } catch (Exception e) {
            logger.error("execute decrypt error", e);
            return null;
        }
    }
}

           

微信配置信息

wechat-pay.appId=***
wechat-pay.secret=***
wechat-pay.mchId=***
wechat-pay.payNotifyUrl=https://***/wallet/account/notifyUrl
wechat-pay.refundNotifyUrl=
wechat-pay.serialNo=xxxxxxxxxxxx
wechat-pay.apiV3Key=xxxxxxxxxxxxxxxxxxx
wechat-pay.certificateKeyPath=xxxxxxxxxxxxxxxxxxx

ums-pay.appId=8a81c1bd7f93c875017faa165b6b00d7
ums-pay.appKey=c26600c9595742bc801462f20d9ecad8
ums-pay.mid=89810008651023B
ums-pay.tid=DM243149
ums-pay.notifyKey=CRaGxE6i27QKN3nyRt8irXdw8fMS7YASFzcCQ2kRp5KyZWX6
ums-pay.payNotifyUrl=https://pgjghy.bjpg.org/pgjgfbs/wallet/account/notifyUrl
ums-pay.sourceCode=13LT
           
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @description:
 * @author: wangxiaogang10
 * @date: 2022/2/28 15:46
 * @see
 * @since 1.0
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "ums-pay")
public class UmsConfig {
    /**
     * appid must
     */
    @Value("${ums-pay.appId}")
    private String appId;
    /**
     * 小程序唯一凭证密钥
     */
    @Value("${ums-pay.appKey}")
    private String appKey;

    /**
     * 小程序唯一凭证密钥
     */
    @Value("${ums-pay.notifyKey}")
    private String notifyKey;
    /**
     * 终端号 must
     */
    @Value("${ums-pay.tid}")
    private String tid;
    /**
     * 商户号 must
     */
    @Value("${ums-pay.mid}")
    private String mid;
    /**
     * 微信支付回到接口 must
     */
    @Value("${ums-pay.payNotifyUrl}")
    private String payNotifyUrl;

    /**
     * 微信支付回到接口 must
     */
    @Value("${ums-pay.sourceCode}")
    private String sourceCode;
}



import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
@ConfigurationProperties(prefix = "wechat-pay")
public class WxConfig {
    /**
     * appid must
     */
    @Value("${wechat-pay.appId}")
    private String appId;
    /**
     * 小程序唯一凭证密钥
     */
    @Value("${wechat-pay.secret}")
    private String secret;
    /**
     * 商户号 must
     */
    @Value("${wechat-pay.mchId}")
    private String mchId;
    /**
     * 微信支付回到接口 must
     */
    @Value("${wechat-pay.payNotifyUrl}")
    private String payNotifyUrl;
    /**
     * 退款回调接口
     */
    private String refundNotifyUrl;
    /**
     * 序列号
     */
    private String serialNo;
    /**
     * API v3密钥 must
     */
    private String apiV3Key;

    /**
     * 证书在服务器上的路径
     */
    private String certificateKeyPath;
}

           

Token拦截器接口鉴权

import com.hikvision.pgjgfbs.interceptor.TokenInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.ArrayList;
import java.util.List;

/**
 * web配置,配置下过滤器的规则
 *
 */
@Configuration
public class WebAppConfig implements WebMvcConfigurer {

    private Logger logger = LoggerFactory.getLogger(WebAppConfig.class);

    @Qualifier("stringRedisTemplate")
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Value("${component.enable.filter}")
    private boolean enableFilter;


    @Value("${spring.cache.redis.token-key-prefix}")
    private String redisTokenKeyPrefix;

    @Value("${spring.cache.redis.web-token-key-prefix}")
    private String webTokenPrefix;

    private static List<String> webExcludePath = new ArrayList<>();

    private static List<String> defaultExcludePath = new ArrayList<String>();


    private static List<String> defaulAddtPath = new ArrayList<String>();

    static {
        // login登陆接口
        defaultExcludePath.add("/auth/login");
        defaultExcludePath.add("/wallet/account/notifyUrl");
        webExcludePath.add("/index");
        webExcludePath.add("/wallet/hello");
        webExcludePath.add("/**/**/*.html");
        webExcludePath.add("/**/*.html");

        defaulAddtPath.add("/wallet/**");
        defaulAddtPath.add("/auth/decryptPhone");

    }


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加小程序请求拦截器
        if (enableFilter) {
            TokenInterceptor tokenInterceptor = new TokenInterceptor(redisTemplate, redisTokenKeyPrefix);
            InterceptorRegistration registration = registry.addInterceptor(tokenInterceptor);
            registration.addPathPatterns(defaulAddtPath);
            // 忽略默认的几个端口
            String[] swaggerExcludes=new String[]{"/swagger-ui.html","/swagger-resources/**","/webjars/**"};
            registration.excludePathPatterns(defaultExcludePath);
            // 忽略web端的几个接口
            registration.excludePathPatterns(webExcludePath);
            registration.excludePathPatterns(swaggerExcludes);


        }
    }


    private void addExcludePath(InterceptorRegistration registration, String pathMessage) {
        try {
            if (StringUtils.hasText(pathMessage)) {
                String[] paths = pathMessage.split(",");
                for (String path : paths) {
                    registration.excludePathPatterns(path);
                }
            }
        } catch (Exception e) {

            logger.error("add exclude error, pathMessage:{}", pathMessage, e);
        }
    }
}
           
import com.alibaba.fastjson.JSONObject;
import com.hikvision.pgjgfbs.common.BaseResult;
import com.hikvision.pgjgfbs.common.enums.EnumPgjgFbsErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
 *
 * token拦截器
 * @author gaozhilong
         * @date 2021/1/25 17:46
        * @since v1.0.0001
        */
public class TokenInterceptor implements HandlerInterceptor {

    private static final Logger log = LoggerFactory.getLogger(TokenInterceptor.class);

    RedisTemplate<String, String> redisTemplate;

    String redisKeyTokenPrefix;

    public TokenInterceptor(RedisTemplate<String, String> redisTemplate, String redisKeyTokenPrefix) {
        this.redisTemplate = redisTemplate;
        this.redisKeyTokenPrefix = redisKeyTokenPrefix;
    }


    /**
     * 进入controller层之前拦截请求
     * @author gaozhilong
     * @date 2021/1/25 17:46
     * @since v1.0.0001
     * @param request request
     * @param response response
     * @param object object
     * @return res
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
        // 验证token, token不存在或过期,需要用户重新登陆
        String token = request.getHeader("token");
        log.info("---------------------preHandle enter token interceptor---------------------------- token:{}",token);

        if (!StringUtils.hasText(token)) {
            response.setCharacterEncoding("UTF-8");
            PrintWriter printWriter = response.getWriter();
            BaseResult<Object> failRes = BaseResult.fail(EnumPgjgFbsErrorCode.ERR_NOT_TOKEN_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_NOT_TOKEN_ERROR.getMsg());
            String message = JSONObject.toJSONString(failRes);
            printWriter.write(message);
            return false;
        }
        String userStatus = (String)redisTemplate.opsForValue().get(token);
        if (!StringUtils.hasText(userStatus)) {
            response.setCharacterEncoding("UTF-8");
            PrintWriter printWriter = response.getWriter();
            BaseResult<Object> failRes = BaseResult.fail(EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getCode(), EnumPgjgFbsErrorCode.ERR_EXPIRED_TOKEN_ERROR.getMsg());
            String message = JSONObject.toJSONString(failRes);
            printWriter.write(message);
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}

           

小程序银联支付

应用场景

小程序支付是指在商户既有的小程序内通过对接微信支付API,实现用户在小程序内完成交易的场景。客户由于微信支付抽成较高的关系,改对接银联支付,银联入驻微信支付的服务商,实现方式前端调起支付api不变,后端改为对接银联的支付api。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;

/**
 * @description:
 * @author: wangxiaogang10
 * @date: 2022/2/28 10:15
 * @see
 * @since 1.0
 */
@Slf4j
@Service
public class UmsPayServiceImpl implements PayService {
    @Autowired
    private WxConfig wxConfig;

    @Autowired
    private UmsConfig umsConfig;
    @Autowired
    private WalletService walletService;

    @Resource
    private OrderRepository orderRepository;
    @Resource
    private UserWxRepository userWxRepository;

    @Override
    public Map<String, String> unifiedOrder(MiniRechargeParam param) throws Exception {
        //1.通过手机号,查询人员信息 irds
        String personInfoStr = walletService.personInfo(param.getPhoneNo());
        ApiResponse<PersonInfo> personInfoApiResponse = JSON.parseObject(personInfoStr, new TypeReference<ApiResponse<PersonInfo>>() {});
        if(personInfoApiResponse.getCode().equals(CommonConstant.ok)||personInfoApiResponse.getCode().equals(CommonConstant.ok_200)){
            PersonInfo person = personInfoApiResponse.getData();
            if(person!=null){
                String outTradeNo= getOutTradeNo();
                UmsUnifiedParam unifiedOrderParam = getUnifiedOrderParam(param,outTradeNo);
                String result= HttpUmsUtils.sendPost(PayUrlConstant.ums_unified_order, JSON.toJSONString(unifiedOrderParam),
                        getOpenBodySig(JSON.toJSONString(unifiedOrderParam)));
                JSONObject jsonObject = JSONObject.parseObject(result);
                JSONObject miniPayRequestObj = jsonObject.getJSONObject("miniPayRequest");
                String miniPayRequest=miniPayRequestObj.toJSONString();
                if (StringUtils.isBlank(miniPayRequest)) {
                    throw new CustomException("miniPayRequest 为空");
                }
                Map<String, String> umsPayParam = new HashMap<>();
                umsPayParam.put("miniPayRequest",miniPayRequest);
                umsPayParam.put("merOrderId",outTradeNo);
                OrderEntity orderEntity=new OrderEntity();
                orderEntity.setMoney(param.getMoney());
                orderEntity.setPrepayId(miniPayRequestObj.getString("prepayid"));
                orderEntity.setPersonId(person.getPersonId());
                orderEntity.setPhoneNo(param.getPhoneNo());
                orderEntity.setStatus("0");
                orderEntity.setOutTradeNo(outTradeNo);
                orderRepository.save(orderEntity);
                return umsPayParam;


            }else {
                throw new Exception("通过手机号,查询人员信息 irds person null");
            }
        }else {
            throw new Exception("通过手机号,查询人员信息 irds api调用失败");
        }
    }

    /**
     * 支付回调
     * @param request
     * @return
     * @throws Exception
     */
    @Override
    public String PayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {

        /*接收参数*/
        Map<String, String> params = getRequestParams(request);
        log.info("payNotify:{}",params);

        String sign = params.get("sign");

        /*验签*/
        //对通知内容生成sign
        String strSign = makeSign(umsConfig.getNotifyKey(),params);
        //System.out.println("strSign="+strSign);
        //判断签名是否相等
        if (sign.equals(strSign)) {
            // 收到通知后记得返回SUCCESS
            log.info("支付回调{}",JSON.toJSONString(params));

            String status = params.get("status");
            String merOrderId=params.get("merOrderId");
            String totalAmount=params.get("totalAmount");
            //根据支付状态做不同的业务处理
            if ("TRADE_SUCCESS".equals(status)) {
                //todo 支付成功业务逻辑,如将订单状态更新为已支付
                OrderEntity orderEntity= orderRepository.findByOutTradeNo(merOrderId);
                if(orderEntity==null){
                    throw new CustomException("没有此订单");
                }
                if("1".equals(orderEntity.getStatus())){
                    PrintWriter writer = response.getWriter();
                    writer.print("SUCCESS");
                    writer.flush();

                    JSONObject jsonObject=new JSONObject();
                    jsonObject.put("code","SUCCESS");
                    jsonObject.put("message","成功");
                    return jsonObject.toJSONString();
                }
                CemsRechargeParam param=new CemsRechargeParam();
                param.setMoney(Integer.parseInt(totalAmount));
                param.setPersonId(orderEntity.getPersonId());
                param.setRechargeId(merOrderId);
                String rechargeStr = walletService.recharge(param,JSON.toJSONString(params));
                ApiResponse<RechargeInfo> apiResponse = JSON.parseObject(rechargeStr, new TypeReference<ApiResponse<RechargeInfo>>() {});
                if(!apiResponse.getCode().equals(CommonConstant.ok)||apiResponse.getData()==null){
                    orderEntity.setStatus("3");
                    orderRepository.save(orderEntity);
                    log.info("cems recharge 调用失败 response{}",JSON.toJSONString(apiResponse));
                    throw new CustomException("cems recharge 调用失败", EnumPgjgFbsErrorCode.ERR_SYSTEM_CEMS_ERROR.getCode());
                }
                orderEntity.setStatus("1");
                orderRepository.save(orderEntity);

                PrintWriter writer = response.getWriter();
                writer.print("SUCCESS");
                writer.flush();

                JSONObject jsonObject=new JSONObject();
                jsonObject.put("code","SUCCESS");
                jsonObject.put("message","成功");
                return jsonObject.toJSONString();
            } else {
                //主动调用查询交易api
                MiniQueryParam miniQueryParam = new MiniQueryParam();
                miniQueryParam.setOutTradeNo(merOrderId);

                BaseResult baseResult = queryByOutTradeNo(miniQueryParam);
                if(CommonConstant.ok.equals(baseResult.getCode())){
                    PrintWriter writer = response.getWriter();
                    writer.print("SUCCESS");
                    writer.flush();

                    JSONObject jsonObject=new JSONObject();
                    jsonObject.put("code","SUCCESS");
                    jsonObject.put("message","成功");
                    return jsonObject.toJSONString();
                }else {
                    PrintWriter writer = response.getWriter();
                    writer.print("FAILED");
                    writer.flush();
                    JSONObject jsonObject=new JSONObject();
                    jsonObject.put("code","FAILED");
                    jsonObject.put("message","失败");
                    return jsonObject.toJSONString();
                }
            }
        }else {
            log.info("payNotify验签失败{}",strSign);
            PrintWriter writer = response.getWriter();
            writer.print("FAILED");
            writer.flush();
            JSONObject jsonObject=new JSONObject();
            jsonObject.put("code","FAILED");
            jsonObject.put("message","验签失败");
            return jsonObject.toJSONString();
        }



    }
    @Override
    public BaseResult queryByOutTradeNo(MiniQueryParam param) throws Exception {
        JSONObject queryOrderParam=new JSONObject();
        queryOrderParam.put("requestTimestamp",DateUtils.dateToStrFormat(new Date(),DateUtils.FORMAT_yyyyMMddHHmmss));
        queryOrderParam.put("mid",umsConfig.getMid());
        queryOrderParam.put("tid",umsConfig.getTid());
        queryOrderParam.put("merOrderId",param.getOutTradeNo());

        String result= HttpUmsUtils.sendPost(PayUrlConstant.ums_query_order, queryOrderParam.toJSONString(),
                getOpenBodySig(queryOrderParam.toString()));
        JSONObject reponse = JSONObject.parseObject(result);
        String status = reponse.getString("status");
        String merOrderId=reponse.getString("merOrderId");
        String totalAmount=reponse.getString("totalAmount");
        if("TRADE_SUCCESS".equals(status)){
            OrderEntity orderEntity= orderRepository.findByOutTradeNo(merOrderId);
            if(orderEntity==null){
                throw new CustomException("没有此订单");
            }
            if(orderEntity.getStatus().equals("1")){
                return BaseResult.success(result);
            }
            CemsRechargeParam cemsRechargeParam=new CemsRechargeParam();
            cemsRechargeParam.setMoney(Integer.parseInt(totalAmount));
            cemsRechargeParam.setPersonId(orderEntity.getPersonId());
            cemsRechargeParam.setRechargeId(merOrderId);
            String rechargeStr = walletService.rechargeQuery(cemsRechargeParam,result);
            ApiResponse<RechargeInfo> rechargeInfoApiResponse = JSON.parseObject(rechargeStr, new TypeReference<ApiResponse<RechargeInfo>>() {});
            if(!rechargeInfoApiResponse.getCode().equals(CommonConstant.ok)||rechargeInfoApiResponse.getData()==null){
                orderEntity.setStatus("3");
                orderRepository.save(orderEntity);
                throw new CustomException("cems recharge 调用失败", EnumPgjgFbsErrorCode.ERR_SYSTEM_CEMS_ERROR.getCode());
            }
            orderEntity.setStatus("1");
            orderRepository.save(orderEntity);

            return BaseResult.success(result);
        }else {
            return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_WX_PAY_FAIL.getCode(),result);
        }
    }

    public BaseResult closeByOutTradeNo(MiniQueryParam param) throws Exception {
        JSONObject closeOrderParam=new JSONObject();
        closeOrderParam.put("requestTimestamp",DateUtils.dateToStrFormat(new Date(),DateUtils.FORMAT_yyyyMMddHHmmss));
        closeOrderParam.put("mid",umsConfig.getMid());
        closeOrderParam.put("tid",umsConfig.getTid());
        closeOrderParam.put("instMid","MINIDEFAULT");
        closeOrderParam.put("merOrderId",param.getOutTradeNo());

        String result= HttpUmsUtils.sendPost(PayUrlConstant.ums_close_order, closeOrderParam.toJSONString(),
                getOpenBodySig(closeOrderParam.toString()));
        JSONObject reponse = JSONObject.parseObject(result);
        String errCode = reponse.getString("errCode");
        String merOrderId=reponse.getString("merOrderId");
        if("SUCCESS".equals(errCode)){
            OrderEntity orderEntity= orderRepository.findByOutTradeNo(merOrderId);
            if(orderEntity==null){
                throw new CustomException("没有此订单");
            }
            orderEntity.setStatus("2");
            orderRepository.save(orderEntity);
            return BaseResult.success(result);
        }else {
            return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_WX_CLOSE_FAIL.getCode(),result);
        }
    }

    @Override
    public void Refund(Long userId, String orderNo) throws Exception {

    }

    @Override
    public void RefundNotify(HttpServletRequest request) throws Exception {

    }


    private String getOutTradeNo() {

        Random random=new Random();
        StringBuffer buf=new StringBuffer();
        for (int i=0;i<7 ;i++ ){
            buf.append(random.nextInt(10));
        }
        return umsConfig.getSourceCode()+DateUtils.dateToStrFormat(new Date(),DateUtils.FORMAT_yyyyMMddmmHHssSSS+buf.toString());
    }


    /**
     * 拼接下单参数
     * @param param
     * @param outTradeNo
     * @return
     */
    private UmsUnifiedParam getUnifiedOrderParam(MiniRechargeParam param, String outTradeNo) {
        UserWxEntity wxEntity= userWxRepository.findByPhone(param.getPhoneNo());
        if(wxEntity==null){
            throw new CustomException("手机号不存在");
        }
        return UmsUnifiedParam.builder()
                 .merOrderId(outTradeNo)
                .mid(umsConfig.getMid())
                .tid(umsConfig.getTid())
                .subAppId(wxConfig.getAppId())
                .subOpenId(wxEntity.getOpenId())
                .tradeType("MINI")
                .totalAmount(param.getMoney()+"")
                .notifyUrl(umsConfig.getPayNotifyUrl())
                .showUrl("")
                .returnUrl("")
                .msgId(outTradeNo)
                .requestTimestamp(DateUtils.dateToStrFormat(new Date(),DateUtils.FORMAT_yyyyMMddHHmmss))
                .build();

    }
    public  String getOpenBodySig(String body) throws IOException {
        String timestamp=DateUtils.dateToStrFormat(new Date(),DateUtils.FORMAT_yyyyMMddHHmmss);
        String nonce=UUIDUtil.generateCode();
        InputStream is=new ByteArrayInputStream(body.getBytes("UTF-8"));
        String sha256 = DigestUtils.sha256Hex(is);
        log.info(sha256);
        String append=umsConfig.getAppId()+timestamp+nonce+sha256;
        byte[] localsignature = hmacSHA256(append.getBytes(), umsConfig.getAppKey().getBytes());
        String signature = Base64.encodeBase64String(localsignature);

        return "OPEN-BODY-SIG AppId="+"\""+umsConfig.getAppId()+"\""+", Timestamp="+"\""+timestamp+"\""+", Nonce="+"\""+nonce+"\""+
                ", Signature="+"\""+signature+"\"";
    }
    public static String makeSign(String md5Key, Map<String, String> params) throws UnsupportedEncodingException {

        String preStr = buildSignString(params); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
        String text = preStr + md5Key;
        return DigestUtils.sha256Hex(getContentBytes(text)).toUpperCase();
    }
    public byte[] hmacSHA256(byte[] data, byte[] key){

        try {
            Mac mac= Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(key,"HmacSHA256"));
            return mac.doFinal(data);
        }catch (Exception e){
            log.error("getOpenBodySig error ",e);
            return null;
        }

    }

    /**
     * 解析request中的数据
     *
     * @param request HttpServletRequest
     * @return request中的数据
     */
    private String getRequestBody(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try (ServletInputStream stream = request.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (Exception e) {
            log.error("failed e", e);
            throw new CustomException("操作失败");
        }
        return sb.toString();
    }


    // 获取HttpServletRequest里面的参数
    public static Map<String, String> getRequestParams(HttpServletRequest request) {
        Map<String, String[]> params = request.getParameterMap();
        Map<String, String> params2 = new HashMap<>();
        for (String key : params.keySet()) {
            String[] values = params.get(key);
            if (values.length > 0) {
                params2.put(key, request.getParameter(key));
            }
        }
        return params2;
    }

    // 构建签名字符串
    public static String buildSignString(Map<String, String> params) {
        if (params == null || params.size() == 0) {
            return "";
        }

        List<String> keys = new ArrayList<>(params.size());

        for (String key : params.keySet()) {
            if ("sign".equals(key))
                continue;
            if (StringUtils.isEmpty(params.get(key)))
                continue;
            keys.add(key);
        }

        Collections.sort(keys);

        StringBuilder buf = new StringBuilder();

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);

            if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符
                buf.append(key + "=" + value);
            } else {
                buf.append(key + "=" + value + "&");
            }
        }

        return buf.toString();
    }

    // 根据编码类型获得签名内容byte[]
    public static byte[] getContentBytes(String content) throws UnsupportedEncodingException {
            return content.getBytes("UTF-8");

    }
}

           

微信小程序支付 未验证

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;


import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;

/**
 * 不需要签名和验签
 */
@Slf4j
@Service
public class WxPayServiceImpl implements PayService {
    @Resource
    private WxConfig wxConfig;
    
    @Autowired
    private WalletService walletService;

    @Resource
    private OrderRepository orderRepository;
      @Resource
    private UserWxRepository userWxRepository;


    /**
     * 统一下单
     *
     * @return 小程序调起微信支付控件的参数
     * @throws Exception 异常
     */
    @Override
    public Map<String, String> unifiedOrder(MiniRechargeParam param) throws Exception {
        //1.通过手机号,查询人员信息 irds
        String personInfoStr = walletService.personInfo(param.getPhoneNo());
        ApiResponse<PersonInfo> personInfoApiResponse = JSON.parseObject(personInfoStr, new TypeReference<ApiResponse<PersonInfo>>() {});
        if(personInfoApiResponse.getCode().equals(CommonConstant.ok)||personInfoApiResponse.getCode().equals(CommonConstant.ok_200)){
            PersonInfo person = personInfoApiResponse.getData();
            if(person!=null){
                String outTradeNo= SnowFlake.getNextId32();
                UnifiedOrderParam unifiedOrderParam = getUnifiedOrderParam(param,outTradeNo);
                HttpResponse response = post(PayUrlConstant.UNIFIED_ORDER, JSONObject.toJSONString(unifiedOrderParam));
                String bodyAsString = EntityUtils.toString(response.getEntity());
                JSONObject jsonObject = JSONObject.parseObject(bodyAsString);
                String prepay_id = jsonObject.get("prepay_id").toString();
                if (StringUtils.isBlank(prepay_id)) {
                    throw new CustomException("prepay_id 为空");
                }
                Map<String, String> wxPayParam = getWxPayParam(prepay_id,outTradeNo);
                OrderEntity orderEntity=new OrderEntity();
                orderEntity.setMoney(param.getMoney());
                orderEntity.setPrepayId(prepay_id);
                orderEntity.setPersonId(person.getPersonId());
                orderEntity.setPhoneNo(param.getPhoneNo());
                orderEntity.setStatus("0");
                orderEntity.setOutTradeNo(wxPayParam.get("nonceStr"));
                orderRepository.save(orderEntity);
                return wxPayParam;
            }else {
                throw new Exception("通过手机号,查询人员信息 irds person null");
            }
        }else {
            throw new Exception("通过手机号,查询人员信息 irds api调用失败");
        }
    }

    /**
     * 统一下单参数拼接
     * @return 统一下单参数
     */
    private UnifiedOrderParam getUnifiedOrderParam(MiniRechargeParam param, String outTradeNo) {

        UserWxEntity wxEntity= userWxRepository.findByPhone(param.getPhoneNo());
        if(wxEntity==null){
            throw new CustomException("手机号不存在");
        }
        // 订单金额
        UnifiedOrderAmount amount = UnifiedOrderAmount.builder().total(param.getMoney()).currency("CNY").build();
        //支付者
        UnifiedOrderPayer payer = UnifiedOrderPayer.builder().openid(wxEntity.getOpenId()).build();
        return UnifiedOrderParam
                .builder()
                .appid(wxConfig.getAppId())
                .mchid(wxConfig.getMchId())
                .description("商品描述")
                .out_trade_no(outTradeNo)
                .notify_url(wxConfig.getPayNotifyUrl())
                .amount(amount)
                .payer(payer)
                .build();
    }

    /**
     * 拼接小程序调起微信支付控件的参数
     *
     * @param prePayId 预支付编码
     * @return 参数
     * @throws Exception 异常
     */
    public Map<String, String> getWxPayParam(String prePayId,String nonceStr) throws Exception {
        //String nonceStr = RandomStringUtils.randomAlphanumeric(32);
        long timestamp = System.currentTimeMillis() / 1000;
        //包装prepay_id 等 返给前端由前端拉起支付
        HashMap<String, String> params = new HashMap<>();
        params.put("appId", wxConfig.getAppId());
        params.put("nonceStr", nonceStr);
        params.put("package", "prepay_id=" + prePayId);
        params.put("signType", "RSA");
        params.put("timeStamp", String.valueOf(timestamp));
        //重新签名
        String signMessage = spliceNewline(wxConfig.getAppId(), timestamp, nonceStr, "prepay_id=" + prePayId);
        String paySign = sign(signMessage.getBytes(StandardCharsets.UTF_8));
        params.put("paySign", paySign);
        return params;
    }


    /**
     * 支付回调(回调接口和获取平台证书的接口都需要做解密)
     *
     * @param request HttpServletRequest
     * @throws Exception 异常
     */
    @Override
    public String PayNotify(HttpServletRequest request,HttpServletResponse response) throws Exception {
        PaySuccessNoticeVo paySuccessNoticeVo = buildCallbackResult(request, PaySuccessNoticeVo.class);
        log.info("支付回调{}",JSONObject.toJSONString(paySuccessNoticeVo));
        //根据支付状态做不同的业务处理
        if (TradeStateEnum.SUCCESS.getCode().equals(paySuccessNoticeVo.getTrade_state())) {
            //todo 支付成功业务逻辑,如将订单状态更新为已支付

            String out_trade_no = paySuccessNoticeVo.getOut_trade_no();

            OrderEntity orderEntity= orderRepository.findByOutTradeNo(out_trade_no);
            if(orderEntity==null){
                throw new CustomException("没有此订单");
            }
            if(orderEntity.getStatus().equals("1")){
                JSONObject jsonObject=new JSONObject();
                jsonObject.put("code","SUCCESS");
                jsonObject.put("message","成功");
                return jsonObject.toJSONString();
            }
            CemsRechargeParam param=new CemsRechargeParam();
            param.setMoney(paySuccessNoticeVo.getAmount().getPayer_total());
            param.setPersonId(orderEntity.getPersonId());
            param.setRechargeId(out_trade_no);
            String rechargeStr = walletService.recharge(param,"");
            ApiResponse<RechargeInfo> apiResponse = JSON.parseObject(rechargeStr, new TypeReference<ApiResponse<RechargeInfo>>() {});
            if(!apiResponse.getCode().equals(CommonConstant.ok)){
                throw new CustomException("cems recharge 调用失败");
            }
            RechargeInfo rechargeInfo = apiResponse.getData();
            if(rechargeInfo==null){
                throw new CustomException("cems recharge 调用成功,返回数据为null");
            }
            orderEntity.setStatus("1");
            orderRepository.save(orderEntity);
            JSONObject jsonObject=new JSONObject();
            jsonObject.put("code","SUCCESS");
            jsonObject.put("message","成功");
            return jsonObject.toJSONString();
        } else {
            JSONObject jsonObject=new JSONObject();
            jsonObject.put("code","FAIL");
            jsonObject.put("message","失败");
            return jsonObject.toJSONString();
        }
    }

    /**
     * 申请退款
     *
     * @param userId  用户id
     * @param orderNo 商户订单号
     * @throws Exception 异常
     */
    @Override
    public void Refund(Long userId, String orderNo) throws Exception {
        //查询订单信息
        Optional<OrderInfo> optional = null;
        if (!optional.isPresent()) {
            throw new CustomException("无效订单");
        }
        if (optional.get().getOrderStatus() != 0) {//可退款状态
            throw new CustomException("订单处于不可退款状态!!!,请联系客服.");
        }

        //执行退款
        RefundOrderParam param = getRefundOrderParam();
        HttpResponse response = post(PayUrlConstant.UNIFIED_ORDER, JSONObject.toJSONString(param));
        String bodyAsString = EntityUtils.toString(response.getEntity());
        RefundSuccessVo refundSuccessVo = JSON.parseObject(bodyAsString, RefundSuccessVo.class);
        if ("SUCCESS".equals(refundSuccessVo.getStatus())) {
            //todo 申请退款成功业务逻辑
        }
    }

    /**
     * 退款参数拼接
     *
     * @return 退款参数
     */
    private RefundOrderParam getRefundOrderParam() {
        // 退款金额
        RefundOrderAmount amount = RefundOrderAmount
                .builder()
                //原订单金额 去订单表查询此次订单金额
                .total(0)
                //退款金额 实际退款金额 根据业务需求计算
                .refund(0)
                .currency("CNY")
                .build();
        return RefundOrderParam
                .builder()
                .transaction_id("微信支付订单号")
                .out_trade_no("商户订单号")
                .out_refund_no("商户退款单号")
                .reason("用户退款")
                //退款回调接口地址
                .notify_url(wxConfig.getRefundNotifyUrl())
                .funds_account("AVAILABLE")
                .amount(amount)
                .build();
    }

    /**
     * 退款回调
     *
     * @param request HttpServletRequest
     * @throws Exception 异常
     */
    @Override
    public void RefundNotify(HttpServletRequest request) throws Exception {
        RefundSuccessNoticeVo refundSuccessNoticeVo = buildCallbackResult(request, RefundSuccessNoticeVo.class);
        if ("SUCCESS".equals(refundSuccessNoticeVo.getRefund_status())) {
            //todo 退款成功业务逻辑处理
        }
    }

    @Override
    public BaseResult queryByOutTradeNo(MiniQueryParam param) throws Exception {

        String url = PayUrlConstant.QueryByOutTradeNo + param.getOutTradeNo() + "?mchid=" +wxConfig.getMchId() ;
        HttpResponse response = get(url);
        String bodyAsString = EntityUtils.toString( response.getEntity());
        log.info("queryByOutTradeNo param{} return{}",JSONObject.toJSONString(param),bodyAsString);
        //JSONObject jsonObject = JSONObject.parseObject(bodyAsString);
        //String trade_state = jsonObject.get("trade_state").toString();

        PaySuccessNoticeVo paySuccessNoticeVo = JSON.parseObject(bodyAsString, PaySuccessNoticeVo.class);

        if(TradeStateEnum.SUCCESS.getCode().equals(paySuccessNoticeVo.getTrade_state())){
            OrderEntity orderEntity= orderRepository.findByOutTradeNo(paySuccessNoticeVo.getOut_trade_no());
            if(orderEntity==null){
                throw new CustomException("没有此订单");
            }
            if(orderEntity.getStatus().equals("1")){
                return BaseResult.success(bodyAsString);
            }
            CemsRechargeParam cemsRechargeParam=new CemsRechargeParam();
            cemsRechargeParam.setMoney(paySuccessNoticeVo.getAmount().getPayer_total());
            cemsRechargeParam.setPersonId(orderEntity.getPersonId());
            cemsRechargeParam.setRechargeId(paySuccessNoticeVo.getOut_trade_no());
            String rechargeStr = walletService.recharge(cemsRechargeParam,"");
            ApiResponse<RechargeInfo> rechargeInfoApiResponse = JSON.parseObject(rechargeStr, new TypeReference<ApiResponse<RechargeInfo>>() {});
            if(!rechargeInfoApiResponse.getCode().equals(CommonConstant.ok)){
                throw new CustomException("cems recharge 调用失败");
            }
            RechargeInfo rechargeInfo = rechargeInfoApiResponse.getData();
            if(rechargeInfo==null){
                throw new CustomException("cems recharge 调用成功,返回数据为null");
            }
            orderEntity.setStatus("1");
            orderRepository.save(orderEntity);

            return BaseResult.success(bodyAsString);
        }else {
            return BaseResult.fail(EnumPgjgFbsErrorCode.ERR_WX_PAY_FAIL.getCode(),bodyAsString);
        }
    }

    /**
     * 获取平台证书列表
     *
     * @return 平台证书列表
     * @throws Exception 异常
     */
    public String getPlatformCertificateList() throws Exception {
        HttpResponse httpResponse = get(PayUrlConstant.GET_CERTIFICATE_LIST);
        JSONObject response = JSON.parseObject(EntityUtils.toString(httpResponse.getEntity()));
        JSONArray resultArr = (Objects.isNull(response) || Objects.isNull(response.get("data"))) ? new JSONArray() : JSONObject.parseArray(response.get("data").toString());
        JSONObject result = JSON.parseObject((Objects.isNull(resultArr) || Objects.isNull(resultArr.get(0))) ? StringUtils.EMPTY : resultArr.get(0).toString());
        EncryptMessageVo resource = JSON.parseObject(JSON.toJSONString(result.get("encrypt_certificate")), EncryptMessageVo.class);
        AesUtil aesUtil = new AesUtil(wxConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        return aesUtil.decryptToString(resource.getAssociated_data().getBytes(StandardCharsets.UTF_8),
                resource.getNonce().getBytes(StandardCharsets.UTF_8),
                resource.getCiphertext());
    }

    /**
     * 解析request中的数据
     *
     * @param request HttpServletRequest
     * @return request中的数据
     */
    private String getRequestBody(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try (ServletInputStream stream = request.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (Exception e) {
            log.error("failed e", e);
            throw new CustomException("操作失败");
        }
        return sb.toString();
    }

    /**
     * 处理回调接口返回结果
     *
     * @param request HttpServletRequest
     * @param clazz   结果类型
     * @return 回调接口返回结果
     * @throws Exception 异常
     */
    private <T> T buildCallbackResult(HttpServletRequest request, Class<T> clazz) throws Exception {
        NoticeVo noticeVo = JSON.parseObject(getRequestBody(request), NoticeVo.class);
        EncryptMessageVo resource = noticeVo.getResource();
        AesUtil aesUtil = new AesUtil(wxConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String result = aesUtil.decryptToString(resource.getAssociated_data().getBytes(StandardCharsets.UTF_8),
                resource.getNonce().getBytes(StandardCharsets.UTF_8),
                resource.getCiphertext());
        return JSON.parseObject(result, clazz);
    }

    /**
     * 拼接换行符
     *
     * @param param 参数
     * @return 拼接后的结果
     */
    private String spliceNewline(Object... param) {
        StringBuilder result = new StringBuilder();
        for (Object str : param) {
            result.append(str).append(StringUtils.LF);
        }
        return result.toString();
    }

    /**
     * 生成签名
     *
     * @param message 签名信息
     * @return 签名
     * @throws Exception 异常
     */
    private String sign(byte[] message) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey());
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }

    /**
     * post请求
     *
     * @param url      url地址
     * @param jsonBody 参数体
     * @return 返回结果
     */
    private HttpResponse post(String url, String jsonBody) throws Exception {
        URIBuilder uriBuilder = new URIBuilder(url);
        HttpPost httpPost = new HttpPost(uriBuilder.build());
        //header封装
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-Type", "application/json");
        StringEntity entity = new StringEntity(jsonBody, StandardCharsets.UTF_8);
        httpPost.setEntity(entity);
        //执行方法
        // 创建httpClient
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpResponse response = httpClient.execute(httpPost);
        //状态判断
        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
            throw new CustomException("操作失败");
        }
        return response;
    }

    /**
     * get请求
     *
     * @param url 访问url
     * @return 返回结果
     * @throws Exception 异常
     */
    private HttpResponse get(String url) throws Exception {
        URIBuilder uriBuilder = new URIBuilder(url);
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept", "application/json");
        CloseableHttpClient httpClient = HttpClients.createDefault();

        HttpResponse response = httpClient.execute(httpGet);
        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
            throw new CustomException("操作失败");
        }
        return response;
    }

    /***
     * 构建httpclient
     * @return HttpClient
     * @throws Exception 异常
     */
    private HttpClient buildHttpClient() throws Exception {
        ClassPathResource resource = new ClassPathResource("证书所在路径");
        X509Certificate weChatPayCertificate = PemUtil
                .loadCertificate(resource.getInputStream());
        return WechatPayHttpClientBuilder.create()
                .withMerchant(wxConfig.getMchId(), wxConfig.getSerialNo(), getPrivateKey())
                .withWechatpay(Collections.singletonList(weChatPayCertificate))//证书
                .build();
    }

    /***
     * 构建httpclient(自动更新证书)
     * @return HttpClient
     */
    private HttpClient buildHttpClientAutoUpdateCertificate() throws IOException {
        //不需要传入微信支付证书了
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(wxConfig.getMchId(), new PrivateKeySigner(wxConfig.getSerialNo(), getPrivateKey())),
                wxConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        return WechatPayHttpClientBuilder.create()
                .withMerchant(wxConfig.getMchId(), wxConfig.getSerialNo(), getPrivateKey())
                .withValidator(new WechatPay2Validator(verifier))
                .build();
    }

    /**
     * 获取私钥。
     *
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey() throws IOException {
        ClassPathResource resource = new ClassPathResource("private_key.pem");
        String content = new String(Files.readAllBytes(Paths.get(resource.getPath())), StandardCharsets.UTF_8);
        try {
            String privateKey = content.replaceAll("\\s+", StringUtils.EMPTY);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }
}