Java 接入微信支付API V3 接口开发案例

2023-11-04

关于API v3

为了在保证支付安全的前提下,带给商户简单、一致且易用的开发体验,我们推出了全新的微信支付API v3。

相较于之前的微信支付API,主要区别是:

  • 遵循统一的REST的设计风格
  • 使用JSON作为数据交互的格式,不再使用XML
  • 使用基于非对称密钥的SHA256-RSA的数字签名算法,不再使用MD5或HMAC-SHA256
  • 不再要求携带HTTPS客户端证书(仅需携带证书序列号)
  • 使用AES-256-GCM,对回调中的关键信息进行加密保护

最近接微信支付API v3接口,踩了一些坑,分享一下,帮助码友避免采坑,话不多少,直接上代码。

WeiXinPaySignUtils

public class WeiXinPaySignUtils {

    /**
     * 生成组装请求头
     *
     * @param method             请求方式
     * @param url                请求地址
     * @param mercId             商户ID
     * @param serial_no          证书序列号
     * @param privateKeyFilePath 私钥路径
     * @param body               请求体
     * @return 组装请求的数据
     * @throws Exception
     */
    public static String getToken(String method, HttpUrl url, String mercId,
                                  String serial_no, String privateKeyFilePath, String body) throws Exception {
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        long timestamp = System.currentTimeMillis() / 1000;
        String message = buildMessage(method, url, timestamp, nonceStr, body);
        String signature = sign(message.getBytes("UTF-8"), privateKeyFilePath);
        return "mchid=\"" + mercId + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + serial_no + "\","
                + "signature=\"" + signature + "\"";
    }


    /**
     * 生成签名
     *
     * @param message            请求体
     * @param privateKeyFilePath 私钥的路径
     * @return 生成base64位签名信息
     * @throws Exception
     */
    public static String sign(byte[] message, String privateKeyFilePath) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey(privateKeyFilePath));
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }

    /**
     * 组装签名加载
     *
     * @param method    请求方式
     * @param url       请求地址
     * @param timestamp 请求时间
     * @param nonceStr  请求随机字符串
     * @param body      请求体
     * @return 组装的字符串
     */
    public static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }
        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }

    /**
     * 获取私钥。
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey(String filename) throws IOException {
        String content = new String(Files.readAllBytes(Paths.get(filename)), "UTF-8");
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            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("无效的密钥格式");
        }
    }

    /**
     * 构造签名串
     *
     * @param signMessage 待签名的参数
     * @return 构造后带待签名串
     */
    public static String buildSignMessage(ArrayList<String> signMessage) {
        if (signMessage == null || signMessage.size() <= 0) {
            return null;
        }
        StringBuilder sbf = new StringBuilder();
        for (String str : signMessage) {
            sbf.append(str).append("\n");
        }
        return sbf.toString();
    }

    /**
     * v3 支付异步通知验证签名
     *
     * @param body 异步通知密文
     * @param key  api 密钥
     * @return 异步通知明文
     * @throws Exception 异常信息
     */
    public static String verifyNotify(String body, String key) throws Exception {
        // 获取平台证书序列号
        cn.hutool.json.JSONObject resultObject = JSONUtil.parseObj(body);
        cn.hutool.json.JSONObject resource = resultObject.getJSONObject("resource");
        String cipherText = resource.getStr("ciphertext");
        String nonceStr = resource.getStr("nonce");
        String associatedData = resource.getStr("associated_data");
        AesUtil aesUtil = new AesUtil(key.getBytes(StandardCharsets.UTF_8));
        // 密文解密
        return aesUtil.decryptToString(
                associatedData.getBytes(StandardCharsets.UTF_8),
                nonceStr.getBytes(StandardCharsets.UTF_8),
                cipherText
        );
    }

    /**
     * 处理返回对象
     *
     * @param request
     * @return
     */
    public static String readData(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

AesUtil

public class AesUtil {
    static final int KEY_LENGTH_BYTE = 32;
    static final int TAG_LENGTH_BIT = 128;
    private final byte[] aesKey;

    /**
     * @param key APIv3 密钥
     */
    public AesUtil(byte[] key) {
        if (key.length != KEY_LENGTH_BYTE) {
            throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
        }
        this.aesKey = key;
    }

    /**
     * 证书和回调报文解密
     *
     * @param associatedData associated_data
     * @param nonce          nonce
     * @param cipherText     ciphertext
     * @return {String} 平台证书明文
     * @throws GeneralSecurityException 异常
     */
    public String decryptToString(byte[] associatedData, byte[] nonce, String cipherText) throws GeneralSecurityException {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData);
            return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)), StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * 敏感信息加密
     *
     * @param message
     * @param certificate
     * @return
     * @throws IllegalBlockSizeException
     * @throws IOException
     */
    public static String rsaEncryptOAEP(String message, X509Certificate certificate)
            throws IllegalBlockSizeException, IOException {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());

            byte[] data = message.getBytes("utf-8");
            byte[] cipherdata = cipher.doFinal(data);
            return Base64.getEncoder().encodeToString(cipherdata);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的证书", e);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
        }
    }

    /**
     * 敏感信息解密
     *
     * @param ciphertext
     * @param privateKey
     * @return
     * @throws BadPaddingException
     * @throws IOException
     */
    public static String rsaDecryptOAEP(String ciphertext, PrivateKey privateKey)
            throws BadPaddingException, IOException {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            byte[] data = Base64.getDecoder().decode(ciphertext);
            return new String(cipher.doFinal(data), "utf-8");
        } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的私钥", e);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            throw new BadPaddingException("解密失败");
        }
    }

}

WeiXinV3FundinFacade

下单:

@PostMapping("/pay")
	public ResultWrapper<Map<String,Object>>  fundin(@RequestBody String request){
		logger.info("PayChannelOrder->Channel微信V3支付渠道请求参数:"+request);
		ChannelFundResult result = new ChannelFundResult();
		ChannelFundRequest req = JSON.parseObject(request, ChannelFundRequest.class);
		logger.info("PayChannelOrder->Channel微信V3支付渠道请求参数转换对象:"+req);
		Properties properties = propertyHelper.getProperties(req.getFundChannelCode());
		//判断mock开关是否打开,是否要返回mock数据
        String mock_switch = properties.getProperty(WXPAYFundChannelKey.MOCK_SWITCH);
        if("true".equals(mock_switch)){//开关开启返回mock数据
        	result.setApiType(req.getApiType());
        	result.setRealAmount(req.getAmount());
 			result.setInstOrderNo(req.getInstOrderNo());
 			result.setProcessTime(new Date());
        	result = MockResultData.mockResule(result);
        	logger.info("注意这是mock数据!");
        	return ResultWrapper.ok().putData(result);
        }
        try {
			H5V3WxPayVO h5V3WxPayVO = new H5V3WxPayVO();
			String appId = properties.getProperty(WXPAYFundChannelKey.KEY_WEIXIN_APPID);
			h5V3WxPayVO.setAppid(appId);
			logger.info("【微信V3支付配置】->【微信appID】:"+appId);
			String mchId = properties.getProperty(WXPAYFundChannelKey.KEY_WEIXIN_MCHID);
			h5V3WxPayVO.setMchid(mchId);
			logger.info("【微信V3支付配置】->【微信商户ID】:"+mchId);
			String notifyUrl = properties.getProperty(WXPAYFundChannelKey.KEY_WEIXIN_NOTIFYURL);
			h5V3WxPayVO.setNotify_url(notifyUrl);
			logger.info("【微信V3支付配置】->【异步通知URL】:"+notifyUrl);
			String description = req.getExtension().get("description");
			h5V3WxPayVO.setDescription(description);
			logger.info("【微信V3支付配置】->【商品描述】:"+description);
			String outTradeNo = req.getInstOrderNo();
			h5V3WxPayVO.setOut_trade_no(outTradeNo);
			logger.info("【微信V3支付配置】->【商户订单号】:"+outTradeNo);
			String attach = req.getExtension().get("attach");
			if(StringUtils.isNotBlank(attach)){
				h5V3WxPayVO.setAttach(attach);
			}

			AmountVO amount = new AmountVO();
			amount.setTotal(MoneyUtil.Yuan2Fen(req.getAmount().doubleValue()));
			amount.setCurrency("CNY");
			h5V3WxPayVO.setAmount(amount);
			PayerVO payer = new PayerVO();
			String openId = req.getExtension().get("openId");
			payer.setOpenid(openId);
			h5V3WxPayVO.setPayer(payer);
			String isDetail = req.getExtension().get("isDetail");
			if("true".equals(isDetail)){
				DetailVO detail = new DetailVO();
				int costPrice =  MoneyUtil.Yuan2Fen(req.getAmount().doubleValue());
				detail.setCostprice(costPrice);
				String invoiceId = req.getExtension().get("invoiceId");
				detail.setInvoiceId(invoiceId);
				String goodsDetailJson = req.getExtension().get("goodsDetail");
				List<GoodsDetailVO> goodsDetailVOList = JSON.parseArray(goodsDetailJson,GoodsDetailVO.class);
				detail.setGoods_detail(goodsDetailVOList);
				h5V3WxPayVO.setDetail(detail);
			}
			SceneInfoVO sceneInfoVO = new SceneInfoVO();
			String payerClientIp = req.getExtension().get("payerClientIp");
			sceneInfoVO.setPayer_client_ip(payerClientIp);
			String deviceId = req.getExtension().get("deviceId");
			if(StringUtils.isNotBlank(deviceId)){
				sceneInfoVO.setDevice_id(deviceId);
			}
			String storeInfoJson = req.getExtension().get("storeInfo");
			if(StringUtils.isNotBlank(storeInfoJson)){
				StoreInfoVO storeInfo = JSON.parseObject(storeInfoJson,StoreInfoVO.class);
				sceneInfoVO.setStore_info(storeInfo);
				h5V3WxPayVO.setScene_info(sceneInfoVO);
			}
			SettleInfoVO settleInfo = new SettleInfoVO();
			String profitSharing = req.getExtension().get("profitSharing");
			if("true".equals(profitSharing)){
				settleInfo.setProfit_sharing(true);
			}else{
				settleInfo.setProfit_sharing(false);
			}
			h5V3WxPayVO.setSettle_info(settleInfo);
			String jsonStr = JSON.toJSONString(h5V3WxPayVO);
			logger.info("【微信V3支付】->请求参数JSON:{}",jsonStr);
			// 发送请求
			String url =properties.getProperty(WXPAYFundChannelKey.JSAPI_CREAT_URL);
			logger.info("【微信V3支付配置】->【请求URL】:{}",url);
			//创建httpclient对象
			CloseableHttpClient client = HttpClients.createDefault();
			//创建post方式请求对象
			HttpPost httpPost = new HttpPost(url_prex + url);
			//装填参数
			StringEntity s = new StringEntity(jsonStr, charset);
			s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,
					"application/json"));
			//设置参数到请求对象中
			httpPost.setEntity(s);
			String mchSerialNo = properties.getProperty(WXPAYFundChannelKey.MCH_SERIAL_NO);
			String privateKeyFilePath = properties.getProperty(WXPAYFundChannelKey.PRIVATE_KEY_FILE_PATH);
			String token = WeiXinPaySignUtils.getToken("POST", HttpUrl.parse(url_prex + url), mchId, mchSerialNo, privateKeyFilePath, jsonStr);
			//设置header信息
			//指定报文头【Content-type】、【User-Agent】
			httpPost.setHeader("Content-type", "application/json");
			httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
			httpPost.setHeader("Accept", "application/json");
			httpPost.setHeader("Authorization",
					"WECHATPAY2-SHA256-RSA2048 " + token);
			//执行请求操作,并拿到结果(同步阻塞)
			CloseableHttpResponse response = client.execute(httpPost);
			//获取结果实体
			HttpEntity entity = response.getEntity();
			String body = "";
			if (entity != null) {
				//按指定编码转换结果实体为String类型
				body = EntityUtils.toString(entity, charset);
			}
			EntityUtils.consume(entity);
			//释放链接
			response.close();
			String responseJson = JSONObject.fromObject(body).getString("prepay_id");
			logger.info("【微信V3支付】->返回结果->prepay_id:{}",responseJson);
			StatusLine statusLine = response.getStatusLine();
			if(StringUtils.isBlank(responseJson)){
				result.setApiResultCode(String.valueOf(statusLine.getStatusCode()));
				result.setApiResultMessage(statusLine.getReasonPhrase());
				result.setResultMessage(statusLine.getReasonPhrase());
				result.setSuccess(false);
				result.setRealAmount(req.getAmount());
				result.setProcessTime(new Date());
				result.setFundChannelCode(req.getFundChannelCode());
				result.setApiType(FundChannelApiType.DEBIT);
				result.setExtension("");
				result.setInstOrderNo(req.getInstOrderNo());
				logger.info("返回支付平台结果:"+JSON.toJSONString(result));
				return ResultWrapper.ok().putData(result);
			}else{
				//
				JSONObject jsonObject = WxTuneUp(responseJson, appId, privateKeyFilePath);
				result.setApiResultCode("0000");
				result.setApiResultSubCode("SUCCESS");
				result.setApiResultMessage("微信支付下单成功");
				result.setResultMessage("微信支付下单成功");
				result.setSuccess(true);
				result.setRealAmount(req.getAmount());
				result.setProcessTime(new Date());
				result.setFundChannelCode(req.getFundChannelCode());
				result.setApiType(FundChannelApiType.DEBIT);
				result.setExtension(jsonObject.toString());
				result.setInstOrderNo(req.getInstOrderNo());
				logger.info("返回支付平台结果:"+JSON.toJSONString(result));
				return ResultWrapper.ok().putData(result);
			}
        	
        }catch (Exception e) {
        	logger.error("资金源[" + req.getFundChannelCode() + "]支付异常", e);
        	Map<String, String> map = new HashMap<String,String>();
            map.put("fundsChannel", req.getFundChannelCode());
            result.setExtension(JSON.toJSONString(map));
            result = builFalidFundinResponse(req, "支付异常", ReturnCode.FAILED, ReturnCode.FAILED,
                StringUtils.EMPTY_STRING);
            ResultWrapper.error().putData(result);
        }
		return null;
	}
/**
	 * 微信调起支付参数
	 * 返回参数如有不理解 请访问微信官方文档
	 * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_1_4.shtml
	 *
	 * @param prepayId           微信下单返回的prepay_id
	 * @param appId              应用ID(appid)
	 * @param privateKeyFilePath 私钥的地址
	 * @return 当前调起支付所需的参数
	 * @throws Exception
	 */
	private JSONObject WxTuneUp(String prepayId, String appId, String privateKeyFilePath) throws Exception {
		String time = System.currentTimeMillis() / 1000 + "";
		String nonceStr = UUID.randomUUID().toString().replace("-", "");
		String packageStr = "prepay_id=" + prepayId;
		ArrayList<String> list = new ArrayList<>();
		list.add(appId);
		list.add(time);
		list.add(nonceStr);
		list.add(packageStr);
		//加载签名
		String packageSign = WeiXinPaySignUtils.sign(WeiXinPaySignUtils.buildSignMessage(list).getBytes(), privateKeyFilePath);
		JSONObject jsonObject = new JSONObject();
		jsonObject.put("appId", appId);
		jsonObject.put("timeStamp", time);
		jsonObject.put("nonceStr", nonceStr);
		jsonObject.put("packages", packageStr);
		jsonObject.put("signType", "RSA");
		jsonObject.put("paySign", packageSign);
		return jsonObject;
	}
	

查询:

@PostMapping("/query")
	public ResultWrapper<Map<String,Object>>  query(@RequestBody String request) {
		
		logger.info("PayChannelOrder->Channel微信V3支付结果查询请求参数:"+request);
		ChannelFundResult result = new ChannelFundResult();
		QueryRequest req = JSON.parseObject(request, QueryRequest.class);
		result.setApiType(req.getApiType());
		logger.info("PayChannelOrder->Channel微信V3支付结果查询请求参数转换对象:"+req);
		Properties properties = propertyHelper.getProperties(req.getFundChannelCode());
        try {
        	String mock_switch = properties.getProperty(WXPAYFundChannelKey.MOCK_SWITCH);
            if("true".equals(mock_switch)){//开关开启返回mock数据
            	result.setFundChannelCode(req.getFundChannelCode());
    			result.setInstOrderNo(req.getInstOrderNo());
    			result.setSuccess(true);
    			result.setApiType(req.getApiType());
    			result.setRealAmount(req.getAmount());
    			result.setInstOrderNo(req.getInstOrderNo());
    			result.setApiResultCode("0000");
    			result.setApiResultSubCode("SUCCESS");
    			result.setApiResultMessage("注意:当前为mock数据!:查询成功");
    			result.setResultMessage("注意:当前为mock数据!:交易成功");
    			result.setApiResultSubMessage("注意:当前为mock数据!:交易成功");
            	logger.info("注意这是mock数据!");
            	return ResultWrapper.ok().putData(result);
            }
			String mchId = properties.getProperty(WXPAYFundChannelKey.KEY_WEIXIN_MCHID);
			String url =properties.getProperty(WXPAYFundChannelKey.QUERY_ORDER_URL);
			url = url.replace("{out_trade_no}",req.getInstOrderNo());
			url = url.concat("?mchid=").concat(mchId);
			logger.info("【微信V3支付】->请求URL:{}",url);
			String mchSerialNo = properties.getProperty(WXPAYFundChannelKey.MCH_SERIAL_NO);
			String privateKeyFilePath = properties.getProperty(WXPAYFundChannelKey.PRIVATE_KEY_FILE_PATH);
			String token = WeiXinPaySignUtils.getToken("GET", HttpUrl.parse(url_prex + url),
					mchId, mchSerialNo, privateKeyFilePath, "");
			//创建httpclient对象
			CloseableHttpClient client = HttpClients.createDefault();
			HttpGet httpGet = new HttpGet(url_prex + url);
			//设置header信息
			//指定报文头【Content-type】、【User-Agent】
			httpGet.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
			httpGet.setHeader("Accept", "application/json");
			httpGet.setHeader("Authorization",
					"WECHATPAY2-SHA256-RSA2048 " + token);
			CloseableHttpResponse response = client.execute(httpGet);
			String bodyString = EntityUtils.toString(response.getEntity());//得到我的这个请求的body请求信息
			logger.info("【微信V3支付】->返回结果->Entity:{}",bodyString);
			Map<String, String> resultMap = MapUtil.jsonToMap(bodyString);
			int statusCode = response.getStatusLine().getStatusCode();
			if (statusCode == 200) {
				if("SUCCESS".equals(resultMap.get("trade_state"))){
					result.setFundChannelCode(req.getFundChannelCode());
					result.setInstOrderNo(req.getInstOrderNo());
					result.setApiResultCode(resultMap.get("trade_state"));
					result.setRealAmount(req.getAmount());
					result.setApiResultSubCode(resultMap.get("trade_state"));
					result.setResultMessage(resultMap.get("trade_state_desc"));
					result.setApiResultMessage(resultMap.get("trade_state_desc"));
					result.setApiResultSubMessage(resultMap.get("trade_state_desc"));
					result.setSuccess(true);
					result.setInstReturnOrderNo(resultMap.get("transaction_id"));
					result.setExtension(bodyString);
					logger.info("查询响应结果:"+JSON.toJSONString(result));
					return ResultWrapper.ok().putData(result);
				}else{
					result.setFundChannelCode(req.getFundChannelCode());
					result.setInstOrderNo(req.getInstOrderNo());
					result.setApiResultCode(resultMap.get("trade_state"));
					result.setRealAmount(req.getAmount());
					result.setApiResultSubCode(resultMap.get("trade_state"));
					result.setResultMessage(resultMap.get("trade_state_desc"));
					result.setApiResultMessage(resultMap.get("trade_state_desc"));
					result.setApiResultSubMessage(resultMap.get("trade_state_desc"));
					result.setSuccess(false);
					result.setInstReturnOrderNo(resultMap.get("transaction_id"));
					result.setExtension(bodyString);
					logger.info("查询响应结果:"+JSON.toJSONString(result));
					return ResultWrapper.ok().putData(result);
				}
			}else if(statusCode == 204){
				logger.info("请求状态码为204");
			}else {
				logger.info("查询订单失败,响应码 ==>{},响应信息是===>{}",statusCode,bodyString);
				result.setFundChannelCode(req.getFundChannelCode());
				result.setInstOrderNo(req.getInstOrderNo());
				result.setApiResultCode(String.valueOf(statusCode));
				result.setRealAmount(req.getAmount());
				result.setApiResultMessage(bodyString);
				result.setResultMessage(bodyString);
				result.setSuccess(false);
				result.setExtension(bodyString);
				logger.info("查询响应结果:"+JSON.toJSONString(result));
				return ResultWrapper.ok().putData(result);
			}
        }catch (Exception ex) {
            logger.error("查询异常", ex);
            result = buildFaildChannelFundResult("签约支付异常", ReturnCode.FAILED, FundChannelApiType.SINGLE_QUERY);
           return ResultWrapper.error().putData(result);
        }
        return null;
	}

支付成功异步通知:

@PostMapping("/notify/{fundChannelCode}")
	public Object  notify(@PathVariable("fundChannelCode") String fundChannelCode,@RequestBody String data) {
    	logger.info("通知数据:"+data);
    	logger.info("fundChannelCode:"+fundChannelCode);
    	ChannelRequest channelRequest = new ChannelRequest();
    	channelRequest.setFundChannelCode(fundChannelCode);
    	channelRequest.setApiType(FundChannelApiType.DEBIT);
    	channelRequest.getExtension().put("notifyMsg", data);
		Properties properties = propertyHelper.getProperties(channelRequest.getFundChannelCode());
		String v3key =properties.getProperty(WXPAYFundChannelKey.KEY_WEIXIN_MCHSECRETKEY);
    	ChannelFundResult result = wxPayResultNotifyService.v3notify(channelRequest,v3key);
    	//调用发送MQ消息,更新订单状态
    	Map<String,Object> map = new HashMap<String,Object>();
		map.put("message", result);
		//消息被序列化后发送
		AmqoRequrst requrst = new AmqoRequrst();
    	requrst.setExchange("exchange.payresult.process");
    	requrst.setRoutingKey("key.payresult.process");
    	requrst.setMap(map);
    	logger.info("发送MQ消息:"+JSON.toJSONString(requrst));
		amqpService.sendMessage(requrst);
		logger.info("MQ消息发送完毕");
        //通知业务系统
        //resultNotifyFacade.notifyBiz(instOrderResult.getInstOrderNo(),xmlToMap);
        String return_result = "{  \n" +
				"    \"code\": \"SUCCESS\",\n" +
				"    \"message\": \"成功\"\n" +
				"}";
        return return_result;
    }

退款:

@PostMapping("/refund")
	public ResultWrapper<Map<String,Object>>  refund(@RequestBody String request) {
		logger.info("PayChannelOrder->Channel微信支付V3退款渠道请求参数:"+request);
		ChannelFundResult result = new ChannelFundResult();
		ChannelFundRequest req = JSON.parseObject(request, ChannelFundRequest.class);
		logger.info("PayChannelOrder->Channel微信支付V3退款渠道请求参数转换对象:"+req);
		Properties properties = propertyHelper.getProperties(req.getFundChannelCode());
		//判断mock开关是否打开,是否要返回mock数据
		String mock_switch = properties.getProperty(WXPAYFundChannelKey.MOCK_SWITCH);
		if("true".equals(mock_switch)){//开关开启返回mock数据
			result.setApiType(req.getApiType());
			result.setRealAmount(req.getAmount());
			result.setInstOrderNo(req.getInstOrderNo());
			result.setProcessTime(new Date());
			result = MockResultData.mockResule(result);
			logger.info("注意这是mock数据!");
			return ResultWrapper.ok().putData(result);
		}
		try {
			RefundVO refundVO = new RefundVO();
			// transaction_id
			String transactionId = req.getExtension().get("transactionId");
			if(StringUtils.isNotBlank(transactionId)){
				refundVO.setTransaction_id(transactionId);
			}
			String outTradeNo = req.getExtension().get("originalOutTradeNo");
			if(StringUtils.isNotBlank(outTradeNo)){
				refundVO.setOut_trade_no(outTradeNo);
			}
			refundVO.setOut_refund_no(req.getInstOrderNo());
			String refundReason = req.getExtension().get("refundReason");
			if(StringUtils.isNotBlank(refundReason)){
				refundVO.setReason(refundReason);
			}
			String refundNotifyUrl = req.getExtension().get("refundNotifyUrl");
			if(StringUtils.isNotBlank(refundNotifyUrl)){
				refundVO.setNotify_url(refundNotifyUrl);
			}
			refundVO.setFunds_account("AVAILABLE");
			RefounAmount amount = new RefounAmount();
			String originalAmount = req.getExtension().get("originalAmount");
			String refounAmount = req.getExtension().get("refounAmount");
			int total = Integer.parseInt(AmountUtils.Yuan2Fen(originalAmount));
			int refund = Integer.parseInt(AmountUtils.Yuan2Fen(refounAmount));
			amount.setTotal(total);
			amount.setCurrency("CNY");
			amount.setRefund(refund);
			refundVO.setAmount(amount);
			String jsonStr = JSON.toJSONString(refundVO);
			logger.info("【微信V3支付】->退款请求参数JSON:{}",jsonStr);
			// 发送请求
			String url =properties.getProperty(WXPAYFundChannelKey.REFUNDS_QUERY_URL);
			logger.info("【微信V3支付配置】->【退款请求URL】:{}",url);
			//创建httpclient对象
			CloseableHttpClient client = HttpClients.createDefault();
			//创建post方式请求对象
			HttpPost httpPost = new HttpPost(url_prex + url);
			//装填参数
			StringEntity s = new StringEntity(jsonStr, charset);
			s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,
					"application/json"));
			//设置参数到请求对象中
			httpPost.setEntity(s);
			String mchSerialNo = properties.getProperty(WXPAYFundChannelKey.MCH_SERIAL_NO);
			String privateKeyFilePath = properties.getProperty(WXPAYFundChannelKey.PRIVATE_KEY_FILE_PATH);
			String mchId = properties.getProperty(WXPAYFundChannelKey.KEY_WEIXIN_MCHID);
			logger.info("【微信V3支付配置】->【微信商户ID】:"+mchId);
			String token = WeiXinPaySignUtils.getToken("POST", HttpUrl.parse(url_prex + url), mchId, mchSerialNo, privateKeyFilePath, jsonStr);
			//设置header信息
			//指定报文头【Content-type】、【User-Agent】
			httpPost.setHeader("Content-type", "application/json");
			httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
			httpPost.setHeader("Accept", "application/json");
			httpPost.setHeader("Authorization",
					"WECHATPAY2-SHA256-RSA2048 " + token);
			//执行请求操作,并拿到结果(同步阻塞)
			CloseableHttpResponse response = client.execute(httpPost);
			//获取结果实体
			HttpEntity entity = response.getEntity();
			String body = "";
			if (entity != null) {
				//按指定编码转换结果实体为String类型
				body = EntityUtils.toString(entity, charset);
			}
			EntityUtils.consume(entity);
			//释放链接
			response.close();
			JSONObject jsonObject = JSONObject.fromObject(body);
			logger.info("【微信V3支付】->返回结果->:{}",jsonObject);
			StatusLine statusLine = response.getStatusLine();
			logger.info("【微信支付】发起退款, request={}", JsonUtil.toJson(refundVO));
			int statusCode = response.getStatusLine().getStatusCode();
			if(statusCode == 200){
				String refundId = (String)jsonObject.get("refund_id");
				if(StringUtils.isBlank(refundId)){
					result.setApiResultCode(String.valueOf(statusLine.getStatusCode()));
					result.setApiResultMessage(statusLine.getReasonPhrase());
					result.setResultMessage(statusLine.getReasonPhrase());
					result.setSuccess(false);
					result.setRealAmount(req.getAmount());
					result.setProcessTime(new Date());
					result.setFundChannelCode(req.getFundChannelCode());
					result.setApiType(FundChannelApiType.DEBIT);
					result.setExtension(body);
					result.setInstOrderNo(req.getInstOrderNo());
					logger.info("返回支付平台结果:"+JSON.toJSONString(result));
					return ResultWrapper.ok().putData(result);
				}else{
					result.setApiResultCode((String)jsonObject.get("status"));
					result.setApiResultMessage((String)jsonObject.get("user_received_account"));
					result.setResultMessage((String)jsonObject.get("user_received_account"));
					result.setSuccess(true);
					result.setRealAmount(new BigDecimal(refounAmount));
					result.setProcessTime(new Date());
					result.setFundChannelCode(req.getFundChannelCode());
					result.setApiType(FundChannelApiType.DEBIT);
					result.setExtension(body);
					result.setInstOrderNo(req.getInstOrderNo());
					logger.info("返回支付平台结果:"+JSON.toJSONString(result));
					return ResultWrapper.ok().putData(result);
				}
			}else{
				logger.info("查询订单失败,响应码 ==>{},响应信息是===>{}",statusCode,body);
				result.setFundChannelCode(req.getFundChannelCode());
				result.setInstOrderNo(req.getInstOrderNo());
				result.setApiResultCode(String.valueOf(statusCode));
				result.setRealAmount(req.getAmount());
				result.setApiResultMessage(body);
				result.setResultMessage(body);
				result.setSuccess(false);
				result.setExtension(body);
				logger.info("查询响应结果:"+JSON.toJSONString(result));
				return ResultWrapper.ok().putData(result);
			}

		}catch (Exception e) {
			logger.error("资金源[" + req.getFundChannelCode() + "]支付异常", e);
			Map<String, String> map = new HashMap<String,String>();
			map.put("fundsChannel", req.getFundChannelCode());
			result.setExtension(JSON.toJSONString(map));
			result = builFalidFundinResponse(req, "支付异常", ReturnCode.FAILED, ReturnCode.FAILED,
					StringUtils.EMPTY_STRING);
			ResultWrapper.error().putData(result);
		}
		return null;
	}

退款查询:

@PostMapping("/refundQuery")
	public ResultWrapper<Map<String,Object>>  refundQuery(@RequestBody String request) {

		logger.info("PayChannelOrder->Channel微信支付退款结果查询请求参数:"+request);
		ChannelFundResult result = new ChannelFundResult();
		QueryRequest req = JSON.parseObject(request, QueryRequest.class);
		result.setApiType(req.getApiType());
		logger.info("PayChannelOrder->Channel微信支付退款结果查询请求参数转换对象:"+req);
		Properties properties = propertyHelper.getProperties(req.getFundChannelCode());
		try {
			String mock_switch = properties.getProperty(WXPAYFundChannelKey.MOCK_SWITCH);
			if("true".equals(mock_switch)){//开关开启返回mock数据
				result.setFundChannelCode(req.getFundChannelCode());
				result.setInstOrderNo(req.getInstOrderNo());
				result.setSuccess(true);
				result.setApiType(req.getApiType());
				result.setRealAmount(req.getAmount());
				result.setInstOrderNo(req.getInstOrderNo());
				result.setApiResultCode("0000");
				result.setApiResultSubCode("SUCCESS");
				result.setApiResultMessage("注意:当前为mock数据!:查询成功");
				result.setResultMessage("注意:当前为mock数据!:交易成功");
				result.setApiResultSubMessage("注意:当前为mock数据!:交易成功");
				logger.info("注意这是mock数据!");
				return ResultWrapper.ok().putData(result);
			}
			String mchId = properties.getProperty(WXPAYFundChannelKey.KEY_WEIXIN_MCHID);
			String url =properties.getProperty(WXPAYFundChannelKey.REFUNDS_QUERY_URL);
			url = url.replace("{out_refund_no}",req.getOriginalInstOrderNo());
			logger.info("【微信V3支付】->退款查询请求URL:{}",url);
			String mchSerialNo = properties.getProperty(WXPAYFundChannelKey.MCH_SERIAL_NO);
			String privateKeyFilePath = properties.getProperty(WXPAYFundChannelKey.PRIVATE_KEY_FILE_PATH);
			String token = WeiXinPaySignUtils.getToken("GET", HttpUrl.parse(url_prex + url),
					mchId, mchSerialNo, privateKeyFilePath, "");
			//创建httpclient对象
			CloseableHttpClient client = HttpClients.createDefault();
			HttpGet httpGet = new HttpGet(url_prex + url);
			//设置header信息
			//指定报文头【Content-type】、【User-Agent】
			httpGet.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
			httpGet.setHeader("Accept", "application/json");
			httpGet.setHeader("Authorization",
					"WECHATPAY2-SHA256-RSA2048 " + token);
			CloseableHttpResponse response = client.execute(httpGet);
			String bodyString = EntityUtils.toString(response.getEntity());//得到我的这个请求的body请求信息
			logger.info("【微信V3支付】->返回结果->Entity:{}",bodyString);
			Map<String, String> resultMap = MapUtil.jsonToMap(bodyString);
			int statusCode = response.getStatusLine().getStatusCode();
			String refund_id = resultMap.get("refund_id");
			if (statusCode == 200 && StringUtils.isNotBlank(refund_id)) {
				result.setFundChannelCode(req.getFundChannelCode());
				result.setInstOrderNo(req.getInstOrderNo());
				result.setApiResultCode(resultMap.get("status"));
				Map<String,String> amountMap = MapUtil.jsonToMap(resultMap.get("amount"));
				result.setRealAmount(new BigDecimal(AmountUtils.Fen2Yuan(Long.parseLong(amountMap.get("refund")))));
				result.setResultMessage(resultMap.get("user_received_account"));
				result.setApiResultMessage(resultMap.get("user_received_account"));
				result.setSuccess(true);
				result.setInstReturnOrderNo(refund_id);
				result.setExtension(bodyString);
				logger.info("退款查询响应结果:"+JSON.toJSONString(result));
				return ResultWrapper.ok().putData(result);

			}else {
				logger.info("查询订单失败,响应码 ==>{},响应信息是===>{}",statusCode,bodyString);
				result.setFundChannelCode(req.getFundChannelCode());
				result.setInstOrderNo(req.getInstOrderNo());
				result.setApiResultCode(String.valueOf(statusCode));
				result.setRealAmount(req.getAmount());
				result.setApiResultMessage(bodyString);
				result.setResultMessage(bodyString);
				result.setSuccess(false);
				result.setExtension(bodyString);
				logger.info("查询响应结果:"+JSON.toJSONString(result));
				return ResultWrapper.ok().putData(result);
			}
		}catch (Exception ex) {
			logger.error("查询异常", ex);
			result = buildFaildChannelFundResult("签约支付异常", ReturnCode.FAILED, FundChannelApiType.SINGLE_QUERY);
			return ResultWrapper.error().putData(result);
		}
	}

下载对账文件

@PostMapping("/downloadBill")
	public ResultWrapper<Map<String,Object>>  downloadBill(@RequestBody String request) {
		logger.info("PayChannelOrder->Channel微信V3支付账单请求参数:"+request);
		ChannelFundResult result = new ChannelFundResult();
		ChannelFundRequest req = JSON.parseObject(request, ChannelFundRequest.class);
		logger.info("PayChannelOrder->Channel微信V3支付账单渠道请求参数转换对象:"+req);
		Properties properties = propertyHelper.getProperties(req.getFundChannelCode());
      //判断mock开关是否打开,是否要返回mock数据
        String mock_switch = properties.getProperty(WXPAYFundChannelKey.MOCK_SWITCH);
        if("true".equals(mock_switch)){//开关开启返回mock数据
        	result.setApiType(req.getApiType());
        	result.setRealAmount(req.getAmount());
 			result.setInstOrderNo(req.getInstOrderNo());
 			result.setProcessTime(new Date());
        	result = MockResultData.mockResule(result);
        	logger.info("注意这是mock数据!");
        	return ResultWrapper.ok().putData(result);
        }
        try {
        	Map<String, String> extension = req.getExtension();
        	String bill_dowload_url = properties.getProperty(WXPAYFundChannelKey.KEY_TRADE_BILL_URL);
        	logger.info("【微信对账下载】->【对账单下载】:"+bill_dowload_url);
     		String billType = properties.getProperty(WXPAYFundChannelKey.KEY_WEIXIN_BILL_TYPE);
     		// 对账类型: ALL,返回当日所有订单信息,默认值 SUCCESS,返回当日成功支付的订单  REFUND,返回当日退款订单
     		logger.info("【微信对账下载】->【微信对账类型】:"+billType);
     		String billDirPath = properties.getProperty(WXPAYFundChannelKey.KEY_BILL_DIR_PATH);
     		logger.info("【微信对账下载】->【对账文件路径】:"+billDirPath);
     		Map<String,String> map = new HashMap<String,String>();
			String mchSerialNo = properties.getProperty(WXPAYFundChannelKey.MCH_SERIAL_NO);
			logger.info("【微信对账下载】->【微信证书编号】:"+mchSerialNo);
			String privateKeyFilePath = properties.getProperty(WXPAYFundChannelKey.PRIVATE_KEY_FILE_PATH);
			logger.info("【微信对账下载】->【微信秘钥路径】:"+privateKeyFilePath);
			String mchId = properties.getProperty(WXPAYFundChannelKey.KEY_WEIXIN_MCHID);
			logger.info("【微信对账下载】->【微信商户号】:"+mchId);
			map.put("bill_dowload_url", url_prex+bill_dowload_url);
     		map.put("bill_date", extension.get("billDate"));
     		map.put("billDirPath", billDirPath);
     		map.put("bill_type", billType);
     		map.put("tar_type", "GZIP");
			map.put("mchSerialNo", mchSerialNo);
			map.put("privateKeyFilePath", privateKeyFilePath);
			map.put("mchId", mchId);
     		File file = winXinFileDown.v3fileDown(map);
     		result.setSuccess(true);
			String bill_file = file.getCanonicalPath();
     		Map<String, String> extensionMap = new HashMap<String, String>();
     		extensionMap.put("bill_file", bill_file);
        	result.setInstOrderNo(req.getInstOrderNo());
			result.setExtension(JSON.toJSONString(extensionMap));
			result.setFundChannelCode(req.getFundChannelCode());
        	result.setApiResultCode("0000");
        	result.setRealAmount(req.getAmount());
        	result.setResultMessage("对账文件下载成功");
        	result.setApiResultMessage("对账文件下载成功");
        	result.setSuccess(true);
        	return ResultWrapper.ok().putData(result);
        }catch (Exception e) {
        	logger.error("资金源[" + req.getFundChannelCode() + "]账单下载异常", e);
        	Map<String, String> map = new HashMap<String,String>();
            map.put("fundsChannel", req.getFundChannelCode());
            result.setExtension(JSON.toJSONString(map));
            result = builFalidFundinResponse(req, "账单下载异常", ReturnCode.FAILED, ReturnCode.FAILED,
                StringUtils.EMPTY_STRING);
            ResultWrapper.error().putData(result);
        }
		return null;
	}

有疑问欢迎联系我,GitHub - panda726548/yiranpay: 聚合支付是一种第四方支付服务。简而言之,第三方支付提供的是资金清算通道,而聚合支付提供的是支付基础之上的多种衍生服务。聚合支付服务”不具备支付牌照,而是通过聚合多种第三方支付平台、合作银行及其他服务商接口等支付工具的综合支付服务。聚合支付不进行资金清算,但能够根据商户的需求进行个性化定制,形成支付通道资源优势互补,具有中立性、灵活性、便捷性等特点。目前已经对接微信,支付宝,银联支付等渠道。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 接入微信支付API V3 接口开发案例 的相关文章

  • 集成底座POC方案说明

    企业的信息化建设是伴随企业发展不断延伸 不断升级的过程 而随着信息化体量的不断增大 复杂繁多的业务系统往往又成为信息化建设的瓶颈 而为了消除瓶颈 更便捷的打通系统的关联 针对企业实际业务建立集成底座平台则是非常有效的一种方式 通过集成底座打
  • idea 配置log4j

    1 导入log4j jar包 放在lib目录下 右键jar包 createlibrary 好像是 还有一种方法 2 配置log4j properties文件 设置输出信息 DEBUG级别和ERROR级别 log4j rootLogger d
  • 利用Java EE相关技术实现一个简单的购物车系统

    利用JSP编程技术实现一个简单的购物车程序 具体要求如下 1 用JSP编程实现一个登录页面 登录信息中有用户名和密码 分别用两个按钮来提交和重置登录信息 另外 登录页面请实现记住密码功能 2 编写一个JSP程序来获取用户提交的登录信息并查询
  • Mybatis对数据的增删改查

    文章目录 创建sql的映射文件 增加 插入数据 修改 删除 查找 向数据库参数传递 简单参数 多个参数 传入对象 使用map传递 Mybatis的基本增删改查总的代码演示 创建sql的映射文件
  • 学完Web的你,赶快来看看SpringBoot吧!

    学完web的你 赶快看看SpringBoot吧 一 SpringBoot简介 1 1 原有Spring优缺点分析 1 1 1 Spring的优点分析 1 1 2 Spring的缺点分析 1 2 SpringBoot的概述 1 2 1 Spr
  • java基础知识

    java基础知识 java是一门编程语言 面向对象 是一门开源的计算机语言 曾用名Oak java市场需求 市面比较广 面向对象 可以开发的方面比较多 主要是是应用方面 android app 软件工具 微信小程序 大数据分析 当今社会上手
  • Cookie 和 Session 详解 及实现用户登陆功能

    Cookie是啥 浏览器提供的在客户端存储数据的一种机制 由于浏览器禁止了网页中的代码直接访问磁盘的文件因此要想再网页中实现数据的持久化存储 就可以使用Cookie这样的机制 Cookie 里面存什么 键值对结构 键和值都是程序猿自定义的
  • HTTPS 的加密流程

    目录 一 HTTPS是什么 二 为什么要加密 三 加密 是什么 四 HTTPS 的工作过程 1 对称加密 2 非对称加密 3 中间人攻击 4 证书 总结 一 HTTPS是什么 HTTPS Hyper Text Transfer Protoc
  • java基础语法

    java基础语法 1 Java概述 1 1 Java语言发展史 了解 1 2 Java语言跨平台原理 理解 1 3 JRE和JDK 记忆 1 4 JDK的下载和安装 应用 1 4 1 下载 1 4 2 安装 1 4 3 JDK的安装目录介绍
  • 解析Java-throw抛出异常详细过程

    首先 我们知道Java有3种抛出异常的形式 throw 执行的时候一定抛出某种异常对象 throws 出现异常的可能性 不一定会发生 系统自动抛出异常 throw用在一个语句抛出异常的时候 throw an instance of exce
  • 多线程(九):JUC组件

    在来时juc组件前 我们先把上一章遗漏的部分给补上 synchronized 实现策略 锁升级 无锁 gt 偏向锁 gt 轻量级锁 gt 重量级锁 还有一个 锁消除 锁消除即删除不必要的加锁操作 JVM在运行时 对一些 在代码上要求同步 但
  • XXX--1.0-SNAPSHOT.jar中没有主清单属性

    一 情况 将项目打包后 启动项目时报 yiqi 1 0 SNAPSHOT jar中没有主清单属性 二 原因 maven项目打包时没有配置主类 缺少plugin配置 三 解决 加上plugin配置
  • 分配操作菜单

    目录 概述 介绍 数据库 后端 前端 效果展示 概述 在写后台管理系统时 我们可以根据不同的登录人 给予不同的功能菜单 如 给楼栋管理员登录时分配 楼栋管理 宿舍管理 所以在数据库就要创建 1 登录人与角色表 2再给角色表分配操作菜单 登录
  • @Resource注解是什么作用,和@bean区别是什么?

    Resource 注解就像是 Java 开发的快递小哥 专门用来送依赖关系到你的代码门口 它的主要工作就是帮你实现依赖注入 把其他组件 比如类 对象 bean 啥的 送到你需要的地方 具体来说 依赖注入 Resource 负责把其他组件注入
  • 【计算机毕业设计】出租车管理系统

    现代经济快节奏发展以及不断完善升级的信息化技术 让传统数据信息的管理升级为软件存储 归纳 集中处理数据信息的管理方式 本出租车管理系统就是在这样的大环境下诞生 其可以帮助管理者在短时间内处理完毕庞大的数据信息 使用这种软件工具可以帮助管理人
  • 【计算机毕业设计】精品课程在线学习系统

    如今社会上各行各业 都喜欢用自己行业的专属软件工作 互联网发展到这个时候 人们已经发现离不开了互联网 新技术的产生 往往能解决一些老技术的弊端问题 因为传统精品课程学习信息管理难度大 容错率低 管理人员处理数据费工费时 所以专门为解决这个难
  • 线程安全的集合类

    Java中提供了许多集合类 其中有的是线程安全的 有的是线程不安全的 线程安全的集合类有 1 Vector Vector类实现了一个 动态数组 与ArrayList相似 但Vector是同步访问的 2 Stack Stack是Vector的
  • 【计算机毕业设计】电商个性化推荐系统

    伴随着我国社会的发展 人民生活质量日益提高 于是对电商个性化推荐进行规范而严格是十分有必要的 所以许许多多的信息管理系统应运而生 此时单靠人力应对这些事务就显得有些力不从心了 所以本论文将设计一套电商个性化推荐系统 帮助商家进行商品信息 在
  • 【计算机毕业设计】二手图书交易系统

    随着世界经济信息化 全球化的到来和互联网的飞速发展 推动了各行业的改革 若想达到安全 快捷的目的 就需要拥有信息化的组织和管理模式 建立一套合理 动态的 交互友好的 高效的二手图书交易系统 当前的信息管理存在工作效率低 工作繁杂等问题 基于
  • 【计算机毕业设计】springbootstone音乐播放器的设计与实现

    随着我国经济的高速发展与人们生活水平的日益提高 人们对生活质量的追求也多种多样 尤其在人们生活节奏不断加快的当下 人们更趋向于足不出户解决生活上的问题 stone音乐播放器展现了其蓬勃生命力和广阔的前景 与此同时 为解决用户需求 stone

随机推荐

  • 异常相关面试题

    1 java中的异常继承体系及常见运行时异常 Throwable类是所有异常或错误的超类 它有两个子类 Error和Exception 分别表示错误和异常 其中异常Exception 分为运行时异常 RuntimeException 和编译
  • js合并数组对象(将数组中具有相同属性对象合并到一起,组成一个新的数组)

    一 根据数组对象中某一key值 合并相同key值的字段 到同一个数组对象中 组成新的数组 1 原数组 var array id 1 name Alice id 2 name Bob id 1 age 25 id 3 name Charlie
  • 机器学习 | Sklearn中的朴素贝叶斯全解

    前期文章介绍了朴素贝叶斯理论 掌握理论后如何去使用它 是数据挖掘工作者需要掌握的实操技能 下面来看看Sklearn中都有哪些朴素贝叶斯 朴素贝叶斯是运用训练数据学习联合概率分布 及
  • uniapp微信小程序实现对H5的全屏适配(@莫成尘)

    复制代码您将看到和一下截图一样的效果 我们将适配全屏至正常h5下的所以页面大小 您再此处将依然使用rpx作为开发单位
  • Linux网络编程 - 基于TCP的服务器端/客户端(1)

    一 理解 TCP 和 UDP 根据数据传输方式的不同 基于网络传输协议的套接字一般分为TCP套接字和UDP套接字 因为TCP是面向连接的 因此又称为基于流 stream 的套接字 TCP Transmission Control Proto
  • Android recyclerView只显示一条数据

    recyclerView的数据集合里明明很多条数据 为什么只显示了一条数据 代码里一顿debug过后 还去翻onBindViewHolder的注释文档 你是不是还是死活找不到原因 骚年 该扇自己耳光了 把item的高度设置成了match p
  • linux环境变量和软件安装路径 小结

    linux环境变量和软件安装路径 小结 目录 linux环境变量和软件安装路径 小结 1 背景 2 Linux环境变量设置 1 对所有用户永久生效 2 对单一用户永久生效 3 当前shell BASH 临时有效 4 查看环境变量 expor
  • sort函数自定义排序

    sort函数自定义排序 咳咳 自定义排序说实话用的地方还是很多的 像ACM里面 天梯赛里面 特别是天梯 必出这玩意 咳咳 水点字数 咱继续 首先哈 我们来看一下c 自带的排序 可以看出哈 默认是按升序排序的 sort不但可以对整型进行排序还
  • VTK交互器

    VTK交互器定义了用户了与VtkWidget界面的交互方式 结构图如下 交互器类名 功能 vtkInteractorStyle 一个实现大部分动作的基类 对交互只有接口 没有实际功能 vtkInteractorStyle3D 允许用户交互
  • 阿里云ECS服务器Linux第一次登录 提示Login Incorrect的解决方法

    问题情景 1 第一次购买ECS服务器 2 没有设置过系统root账户的Login密码 不是连接远程时提示需要输入的那个6位纯数字密码哈 3 在阿里云控制台中启动 远程连接 4 输入6位纯数字密码 首次连接会提示密码 后面不再提示 需要记下来
  • 小白就懂的IDEA中将本地的Jar包导入到Springboot(含若依)中

    在项目开发的过程中 难免会遇到在Springboot中无法通过pom中添加依赖使用maven下载需要的jar包 而本地PC端中我们拥有jar包 这时候该如何办呢 下面所采用的方法既不添加lib文件 就能搞定 1 打开IDEA开发软件 2 然
  • 『贪吃蛇』AI 算法简易实现(中秋特别版)

    前言 一年一度的中秋节就快到了 平台也有各种各样的中秋发文活动 正在翻阅时偶然间我看到了这篇文章 兔饼大作战 吃月饼 见月亮 还能咬自己 欢庆中秋特制版 掘金 juejin cn 大家肯定比较熟悉了 这个游戏的内核就是贪吃蛇 作者也是对玩法
  • G1理论基础与最佳实践

    文章目录 1 G1理论基础 1 1 G1介绍 1 2 YongGC 1 3 mixed gc 1 4 扩展 1 4 1 记忆集和卡表 1 4 2 STAB和TAMS 1 5 G1相比CMS的优势 2 G1日志解读与经验分享 2 1 日志解读
  • Win10专业版系统Docker安装、配置和使用详细教程

    一 win10专业版系统首先需要开启硬件虚拟化及Hyper V功能 才能进行Docker for Windows软件安装 如何开启硬件虚拟化 自行百度 可在任务栏中查看虚拟化是否开启 win10系统 打开控制面板 应用 程序和功能 开启Hy
  • postman——集合——执行集合——迭代运行集合

    网址 https learning getpostman com docs postman collection runs starting a collection run 开始收集运行 集合是一组请求 可以在对应的环境下作为一系列请求一
  • 前后端接口规范

    原文地址 https github com f2e journey treasure blob master api md 前后端接口规范 随着前后端分离越来越普遍 后端接口规范也就越来越重要了 一套良好的接口规范可以提升工作效率 减少沟通
  • Java-线程同步

    Java 线程同步 在Java中 我们通过同步机制 来解决线程的安全问题 实现线程安全的三种方法 1 同步代码块 synchronized 同步监视器 需要被同步的代码 说明 操作共享数据的代码 即为需要被同步的代码 gt 不能包含代码多了
  • 基于Python+Django的项目实战-信息安全领域中语义搜索引擎的设计与实现(附源码+论文)

    大家好 我是职场程序猿 感谢您阅读本文 欢迎一键三连哦 当前专栏 基于Python的毕业设计 精彩专栏推荐 微信小程序毕业设计 安卓app毕业设计 Java毕业设计 信息安全领域中语义搜索引擎的设计 django 演示 源码及论文下载地址
  • 前端复习HTML+CSS+JavaScript(必问面试题)

    前端复习 HTML 常见的几种图片格式以及他们之间的区别是什么 JPG 支持有损压缩 不支持透明 不支持动画 色彩还原度较好 PNG 不支持压缩 支持透明 半透明 不透明 不支持动画 GIF 支持有损压缩 不支持全透明 支持半透明 支持动画
  • Java 接入微信支付API V3 接口开发案例

    关于API v3 为了在保证支付安全的前提下 带给商户简单 一致且易用的开发体验 我们推出了全新的微信支付API v3 相较于之前的微信支付API 主要区别是 遵循统一的REST的设计风格 使用JSON作为数据交互的格式 不再使用XML 使