需求分析

C扫B的概念

C扫B,即顾客(Customer)扫描商户(Business)提供的二维码来完成支付。下图是支付宝提供的C扫B业务流程:

  1. 商家出示付款二维码
  2. 客户打开支付宝或微信的扫一扫,扫描二维码
  3. 确认支付,完成支付。

在这里插入图片描述
C扫B支付分为两种方式:一是固定金额支付,顾客扫描后无需输入金额直接确认支付即可;另外一种是输入金额,顾客扫描后需自己输入待支付的金额,然后完成支付。

什么是固定金额支付?

C扫B固定金额比较常见的就是在自动售货机购买饮料时,当你选择一个饮料后屏幕会显示一个二维码,咱们扫描后只需确认支付即可,无需自己输入饮料的价格,这种情况大家可以根据下面的交互流程图来自行实现。

什么是输入金额支付?

C扫B输入金额支付方式可以让买方自由输入金额,商户提前生成一个二维码,将二维码张贴在结账处,买方扫描二维码,输入当前消费的金额,完成支付。

业务流程

本章节实现C扫B输入金额支付,业务流程如下:
在这里插入图片描述
1、商户点击组织管理-》门店管理-》打开门店列表页面
在这里插入图片描述
2、选择应用
在这里插入图片描述
3、点击指定门店的生成二维码按钮
在这里插入图片描述
4、顾客扫描生成的二维码,进入支付页面,输入金额来完成支付
在这里插入图片描述
在这里插入图片描述

需求列表

根据业务流程的分析,闪聚支付平台实现C扫B输入金额支付功能,需要完成以下需求:

  1. 为门店生成统一的支付二维码,用户扫一下二维码即可使用微信支付也可使用支付宝完成支付。
  2. 闪聚支付平台与微信、支付宝对接,闪聚支付作为中介,最终的支付动作(银行交易)仍通过微信、支付宝进行。
  3. 闪聚平台作为中介调用微信、支付宝的下单接口,完成支付。

支付接口技术预研

根据前边的需求分析,最重要的是闪聚支付平台作为中介,将用户的支付请求通过接口与微信、支付宝等第三方支付渠道进行对接,完成支付通道的聚合,所以首先需要调研微信、支付宝等第三方支付渠道的对接方式。

本项目首期上线要求集成微信和支付宝,下边对微信和支付宝的支付接口进行技术预研,包括:对接的流程,接口协议、接口测试等。

参考:闪聚支付-第3章-支付宝支付接入指南 、 闪聚支付-第3章-微信支付接入指南 。

生成门店二维码

业务流程

1、商户点击组织管理-》门店管理-》打开门店列表页面
在这里插入图片描述
2、选择应用
在这里插入图片描述
3、点击指定门店的生成二维码按钮
在这里插入图片描述

生成二维码技术预研

ZXing是一个开源的,用Java编写的多格式的1D / 2D条码图像处理库,使用ZXing可以生成、识别QR Code(二维码)。常用的二维码处理库还有zbar,近几年已经不再更新代码,下边介绍ZXing生成二维码的方法。

(1)引入依赖

<!-- 二维码生成&识别组件 -->
<dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.3.3</version>
</dependency><dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.3.3</version>
</dependency>

(2)生成二维码方法

复制二维码工具类QRCodeUtil.java到项目中

测试根据内容生成二维码方法,在QRCodeUtil中添加main方法如下:

public static void main(String[] args) throws IOException {QRCodeUtil qrCodeUtil = new QRCodeUtil();System.out.println(qrCodeUtil.createQRCode("http://www.itcast.cn/", 200, 200));
}

运行main方法,将输出的内容复制到浏览器地址后回车

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIAQAAAACFI5MzAAABQElEQVR42u2YPZKDMAyF5aFIuUfIUThafDSOwhEoUzC8fZKMySSbrVI8ZuICBX8uIvtZPxjeDfuSf8liPi7LFSgrzRTvV3XCKawXYLptFobviz6ZzB2xEfTjhyS9OwXB3A7jbMSngLOQ0I4v2AZf96wqTWJ9+9/dYEHSx2RYqfg/oqUgiX3nFBVfcCepcSbiJP67iwZ1G+5+Am7kyTzW9OcW/kRAX+QJ953+uCl8zO5PV5UsaffUp8rqP5+jiySJU8jtNxcNrysetCNK6A/V4lEQeU+xa0eZREE1tOTpFYod0VKXsKCqvRqMkW5pkza8Ggy3WgEuTvZcz0dcUBc+9MneL1DqkXjQz0eaZA1LqVtmzcMffTKPiPwz1mh2zkGyNwtT9kguTVI7LWv6ul7DCpOjX9iaGV66HDny/ZL1WfILfc/hMHLUpekAAAAASUVORK5CYII=

在这里插入图片描述
使用手机扫描二维码,即可自动打开传智播客官网

门店列表

商户服务查询门店列表
接口定义

1、接口描述

1)根据商户id和分页信息查询门店列表

2、接口定义如下:MerchantService

/*** 分页条件查询商户下门店* @param storeDTO 查询条件,必要参数:商户id* @param pageNo  页码* @param pageSize 分页记录数* @return*/
PageVO<StoreDTO> queryStoreByPage(StoreDTO storeDTO, Integer pageNo, Integer pageSize);
接口实现

3、在MerchantServiceImpl中实现queryStoreByPage方法:

/*** 门店列表的查询* @param storeDTO 查询条件,必要参数:商户id* @param pageNo   页码* @param pageSize 分页记录数* @return*/
@Override
public PageVO<StoreDTO> queryStoreByPage(StoreDTO storeDTO, Integer pageNo, Integer pageSize) {//分页条件Page<Store> page = new Page<>(pageNo, pageSize);//查询条件拼装LambdaQueryWrapper<Store> lambdaQueryWrapper = new LambdaQueryWrapper<Store>();//如果 传入商户id,此时要拼装 查询条件if (storeDTO != null && storeDTO.getMerchantId() != null) {lambdaQueryWrapper.eq(Store::getMerchantId, storeDTO.getMerchantId());}//再拼装其它查询条件 ,比如:门店名称if (storeDTO != null && StringUtils.isNotEmpty(storeDTO.getStoreName())) {lambdaQueryWrapper.eq(Store::getStoreName, storeDTO.getStoreName());}//分页查询数据库IPage<Store> storeIPage = storeMapper.selectPage(page, lambdaQueryWrapper);//查询列表List<Store> records = storeIPage.getRecords();//将包含entity的list转成包含dto的listList<StoreDTO> storeDTOS = StoreConvert.INSTANCE.listentity2dto(records);return new PageVO(storeDTOS, storeIPage.getTotal(), pageNo, pageSize);
}
商户平台应用查询门店列表
接口定义

1、接口描述

1)请求商户服务查询门店列表

2、接口定义如下:StoreController

package com.shanjupay.merchant.controller;
/*** 门店管理相关接口定义**/
@Api(value = "商户平台-门店管理", tags = "商户平台-门店管理", description = "商户平台-门店的增删改查")
@RestController
@Slf4j
public class StoreController {@ApiOperation("分页条件查询商户下门店")@ApiImplicitParams({@ApiImplicitParam(name = "pageNo", value = "页码", required = true, dataType = "int", paramType = "query"),@ApiImplicitParam(name = "pageSize", value = "每页记录数", required = true, dataType = "int", paramType = "query")})@PostMapping("/my/stores/merchants/page")public PageVO<StoreDTO> queryStoreByPage(Integer pageNo, Integer pageSize) {}
}
接口实现

前端JS在Long长度大于17位时会出现精度丢失的问题,由于项目中门店ID的长度会超过17位,所以在此处添加注解将返回给前端的门店ID自动转为string类型

1)使用jackson来完成自动转换,在shanjupay-merchant-api工程中添加依赖:

<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.9</version><scope>compile</scope>
</dependency>

2)在StoreDTO中添加注解:

@ApiModelProperty("门店Id")
@JsonSerialize(using= ToStringSerializer.class)
private Long id;

如下:
在这里插入图片描述

2、在StoreController中实现queryStoreByPage方法:

package com.shanjupay.merchant.controller;import com.shanjupay.common.domain.PageVO;
import com.shanjupay.merchant.api.MerchantService;
import com.shanjupay.merchant.api.dto.StoreDTO;
import com.shanjupay.merchant.common.util.SecurityUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;/*** 门店管理相关接口定义**/
@Api(value = "商户平台-门店管理", tags = "商户平台-门店管理", description = "商户平台-门店的增删改查")
@RestController
@Slf4j
public class StoreController {@ReferenceMerchantService merchantService;@ApiOperation("分页条件查询商户下门店")@ApiImplicitParams({@ApiImplicitParam(name = "pageNo", value = "页码", required = true, dataType = "int", paramType = "query"),@ApiImplicitParam(name = "pageSize", value = "每页记录数", required = true, dataType = "int", paramType = "query")})@PostMapping("/my/stores/merchants/page")public PageVO<StoreDTO> queryStoreByPage(Integer pageNo, Integer pageSize) {//获取商户idLong merchantId = SecurityUtil.getMerchantId();//查询条件StoreDTO storeDTO = new StoreDTO();storeDTO.setMerchantId(merchantId);//商户id//调用service分页查询列表PageVO<StoreDTO> stores = merchantService.queryStoreByPage(storeDTO, pageNo, pageSize);return stores;}
}
接口测试

由于已经接入SaaS,请求统一走网关的端口56010。

1、首先请求认证获取token,及租户的id

(1) 启动三个SaaS服务

(2)使用账号申请token

2、使用Postman:POST http://localhost:56010/merchant/my/stores/merchants/page?pageNo=1&pageSize=20 查询门店列表

注意在header中添加 Authorization及tenantId。

访问:http://localhost:56020/uaa/oauth/token 拿到token。需要的参数如下图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

生成二维码

系统交互流程

生成二维码的系统交互 流程如下:
在这里插入图片描述
1、商户登录商户应用平台 ,查询门店列表

2、商户平台 请求交易 服务获取门店二维码URL

3、商户平台 根据 URL生成二维码

交易服务生成二维码URL
接口定义

接口描述:

生成门店的c扫b二维码

接口参数:

输入:商户id、应用id、门店id、标题,内容

输出:支付入口

1、在交易服务的api工程 创建dto

package com.shanjupay.transaction.api.dto;import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;@Data
@NoArgsConstructor
public class QRCodeDto implements Serializable {private Long merchantId;//商户idprivate String appId;//应用idprivate Long storeId;//门店idprivate String subject;//商品标题private String body;//订单描述
}

2、在交易服务shanjupay-transaction-api工程中创建接口TransactionService,定义如下方法

package com.shanjupay.transaction.api;import com.shanjupay.common.domain.BusinessException;
import com.shanjupay.transaction.api.dto.QRCodeDto;/*** 交易相关的服务接口*/
public interface TransactionService {/*** 生成门店二维码的url* @param qrCodeDto 传入merchantId,appId、storeid、channel、subject、body* @return 支付入口(url),要携带参数(将传入的参数转成json,用base64编码)* @throws BusinessException*/String createStoreQRCode(QRCodeDto qrCodeDto) throws BusinessException;
}
商户服务应用合法校验接口

商户生成二维码需要根据门店、应用来生成,设计接口需要对应用和门店的合法性来校验。

1、校验该应用是否属于该商户。

2、校验该门店是否属于该商户

1、接口描述

1)根据商户id和应用id查询应用信息,查询到则说明合法。

2、在shanjupay-merchant-api中的AppService下定义接口如下:

/*** 查询应用是否属于某个商户* @param appId* @param merchantId* @return*/
Boolean queryAppInMerchant(String appId, Long merchantId);

3、接口实现如下

在AppServiceImpl中实现queryAppInMerchant接口

/*** 查询应用是否属于某个商户* @param appId* @param merchantId* @return true表示存在,false不表示存在*/
@Override
public Boolean queryAppInMerchant(String appId, Long merchantId) {Integer count = appMapper.selectCount(new LambdaQueryWrapper<App>().eq(App::getAppId, appId).eq(App::getMerchantId, merchantId));return count > 0;
}
商户服务门店合法校验接口

1、接口描述

1)根据商户id和门店id查询门店,查询到则说明合法。

2、在shanjupay-merchant-api工程的MerchantService中定义接口:

/*** 查询门店是否属于某商户* @param StoreId* @param merchantId* @return*/
Boolean queryStoreInMerchant(Long StoreId, Long merchantId);

3、接口实现如下

在MerchantServiceImpl中实现queryStoreInMerchant接口

/*** 查询门店是否属于某商户** @param storeId* @param merchantId* @return true存在,false不存在*/
@Override
public Boolean queryStoreInMerchant(Long storeId, Long merchantId) {Integer count = storeMapper.selectCount(new LambdaQueryWrapper<Store>().eq(Store::getId, storeId).eq(Store::getMerchantId, merchantId));return count > 0;
}
接口实现

在Nacos中添加支付入口配置:transaction-service.yaml

支付入口是扫码支付的统一入口。

#支付入口url
shanjupay:payurl: "http://127.0.0.1:56010/transaction/pay-entry/"

在这里插入图片描述
在shanjupay-transaction-service工程中新建TransactionServiceImpl类实现TransactionService接口中的createStoreQRCode方法:

package com.shanjupay.transaction.service;import com.alibaba.fastjson.JSON;
import com.shanjupay.common.domain.BusinessException;
import com.shanjupay.common.domain.CommonErrorCode;
import com.shanjupay.common.util.EncryptUtil;
import com.shanjupay.merchant.api.AppService;
import com.shanjupay.merchant.api.MerchantService;
import com.shanjupay.transaction.api.TransactionService;
import com.shanjupay.transaction.api.dto.PayOrderDTO;
import com.shanjupay.transaction.api.dto.QRCodeDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Value;@Service
@Slf4j
public class TransactionServiceImpl implements TransactionService {//从配置文件读取支付入口地址@Value("${shanjupay.payurl}")private String payurl;@ReferenceAppService appService;@ReferenceMerchantService merchantService;/*** 生成门店二维码的url** @param qrCodeDto 支付入口(url),要携带参数,商户id、应用id、门店id、标题、内容(将传入的参数转成json,用base64编码)* @return* @throws BusinessException*/@Overridepublic String createStoreQRCode(QRCodeDto qrCodeDto) throws BusinessException {//校验商户id和应用id和门店id的合法性verifyAppAndStore(qrCodeDto.getMerchantId(), qrCodeDto.getAppId(), qrCodeDto.getStoreId());//组装url所需要的数据,生成支付信息PayOrderDTO payOrderDTO = new PayOrderDTO();payOrderDTO.setMerchantId(qrCodeDto.getMerchantId());payOrderDTO.setAppId(qrCodeDto.getAppId());payOrderDTO.setStoreId(qrCodeDto.getStoreId());payOrderDTO.setSubject(qrCodeDto.getSubject());//显示订单标题payOrderDTO.setChannel("shanju_c2b");//服务类型,要写为c扫b的服务类型payOrderDTO.setBody(qrCodeDto.getBody());//订单内容//转成jsonString jsonString = JSON.toJSONString(payOrderDTO);//base64编码String ticket = EncryptUtil.encodeUTF8StringBase64(jsonString);//目标是生成一个支付入口 的url,需要携带参数将传入的参数转成json,用base64编码String url = payurl + ticket;log.info("transaction service createStoreQRCode,pay‐entry is {}", url);return url;}//私有方法,校验商户id和应用id和门店id的合法性private void verifyAppAndStore(Long merchantId, String appId, Long storeId) {//根据应用id和商户id查询,判断应用是否属于当前商户Boolean aBoolean = appService.queryAppInMerchant(appId, merchantId);if (!aBoolean) {throw new BusinessException(CommonErrorCode.E_200005);}//根据门店id和商户id查询,判断门店是否属于当前商户Boolean aBoolean1 = merchantService.queryStoreInMerchant(storeId, merchantId);if (!aBoolean1) {throw new BusinessException(CommonErrorCode.E_200006);}}
}
商户平台应用生成二维码
接口实现

1、配置参数

定义c扫b二维码的标题和内容
在这里插入图片描述
内容 如下:

shanjupay:c2b:subject: "%s商品"body: "向%s付款"

2、定义接口

//"%s商品"  门店二维码订单标题
@Value("${shanjupay.c2b.subject}")
String subject;
//"向%s付款"  门店二维码订单内容
@Value("${shanjupay.c2b.body}")
String body;@Reference
TransactionService transactionService;@ApiOperation("生成商户应用门店的二维码")
@ApiImplicitParams({@ApiImplicitParam(name = "appId", value = "商户应用id", required = true, dataType = "String", paramType = "path"),@ApiImplicitParam(name = "storeId", value = "商户门店id", required = true, dataType = "String", paramType = "path"),
})
@GetMapping(value = "/my/apps/{appId}/stores/{storeId}/app-store-qrcode")
public String createCScanBStoreQRCode(@PathVariable("storeId") Long storeId, @PathVariable("appId") String appId) throws IOException {//获取商户idLong merchantId = SecurityUtil.getMerchantId();//商户信息MerchantDTO merchantDTO = merchantService.queryMerchantById(merchantId);QRCodeDto qrCodeDto = new QRCodeDto();qrCodeDto.setMerchantId(merchantId);qrCodeDto.setStoreId(storeId);qrCodeDto.setAppId(appId);//标题.用商户名称替换 %sString subjectFormat = String.format(subject, merchantDTO.getMerchantName());qrCodeDto.setSubject(subjectFormat);//内容String bodyFormat = String.format(body, merchantDTO.getMerchantName());qrCodeDto.setBody(bodyFormat);//获取二维码的URLString storeQRCodeURL = transactionService.createStoreQRCode(qrCodeDto);//调用工具类生成二维码图片QRCodeUtil qrCodeUtil = new QRCodeUtil();//二维码图片base64编码String qrCode = qrCodeUtil.createQRCode(storeQRCodeURL, 200, 200);return qrCode;
}
测试

1、请求认证,获取token及租户id

2、请求获取二维码

http://localhost:56010/merchant/my/apps/{appId}/stores/{storeId}/app-store-qrcode

注意:应用及门店的合法性

同样,先生成token
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
生成如下二维码:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIAQAAAACFI5MzAAAGBklEQVR42u2YvZXsqhKFkQMpgAOpgSNSQA5IDqSAHJQaOJACcuBW97nPeqs5CVyNNf31aEH97No1aP560H8ESEbq7rpyY6jy7omkXR4jsiRtWM63MB94ovD6SeRNri1JxVZXeMXMxoVzJ5F5NqT+C9FJtfcZHNEju0Q6p/vfyUwkSkziVi2i3LKW/kLaUFGVSCrl2hurWt/K/+7zi2S0///zb9x+kTlbSyqRlizrxupnljv8m59fpFVy9+M+MaZyt2g/omrn1pZk3lfLCOu7U+bFfUMSKj/WpGRpqTzKO3YdrUrygA+4XpI3lMtD5p7r5JRkxFq46xGWpH/+9o7YhQfwk11U855tSQaLu3jmvAY9Wmtvu69KrViS15Os/QZxfO4Zd4mOTK1uawK16rdhrHXlyTuZGY5+PEsCvwzM4WenULrYZVIyu9qSlJJ1VO8pPFVDP92Ka7DnWZLZqeFbZ54f1VJVkZVkfLPwm7xPQiRkhNQpErUu7Ziyty1J69ggcqqQqb4ieQeB71KxJJ2V6JKF66LNu0Th4NBkekkycidFCm54d8hHMuqUfzr4N2ltQlXovI0jY7OFlvSd//TpT1KN9trvlm4RYb7LHVnddViS9obMVXZ34uIkibWqWiRzSeZw3ULuOntCZu/jjwHaqZfkBbnbPJZu4OPKzlPtqVFhSUrenmdQCCcbJCEN2mQkaUvSSbmL33UbiEENI5KO0FpYE6zSpxul8tvnMF15ue96Sd5WQedODRJDPLVIn5jqTJakZdKiuEE0RQkRqcyNlUgsSXkG3k7iFdQGqNhVuQrJuiXpklzhPvnh1X2603ySfZSwJFW0cJotQw+Te75XUkMk9qzJbjA66nZdCcOMOabfd3HNJenQ7gZhGBMQU8g0eaK7uluSkik53eV3qEWQI0516WKQJel6Vp0oRciFru5sJbVctSUpgwVoc0Oup3jjugpQzCEsyXATwrGzikHFqFVjVwGKaknm6VrfVWR55xiSbRhITNJLAiMJGwPDDLt5nQ5Ez1OuniXpGFkCF4TA7oja471KeU6xJG85t4gxJoMaEHMpOcyYpy1Jt+7bjUiE6EpGUI9md2tSZukWG8mgla9WYDxvd2RhSeYpnoq3G+SueHoMCycvJ1mTEI3Lm/+I/gDlE+HkzLslgXNufTt3foSryw2KkUvK2pJUl44qoX+fakXUJ1WnS24uSecCBiCUeUgSjNp8wW+cZE3eN3IoJ9EGZXUn0LvgUP64gJ9kSOINZxDOu8IA2M0xqPy6p99kthKuvIFAto/TRTu0SChzSZrfP7bMSwwuoEOew4CbkyWpGs56ed1OzBIHtfRGhOyWZJ5IDAxVpUDGOgKlGSRjvSTtHdIldEQpjwTBuQtMg+tZEpjfiXksCkhgYie0fZfsbksy1F0yOVnnSA3XrmqPWapektkRTAgBCoug6DGnmzfH1w3+Ju/9XPN+oXqPt0VWxV3Z+1Xy3wQmOPjMqvsWNbh8KI63b191+U0q63jfWtw82k4RypMMF98u+U3KichVjSizxePjPDPsH12vSYPqBbuOKAa7gZAUV7m+arkg59a5ldug4Bk41y0aCQZ/SQaBkSQ3kBjDhgqgG5e3WCxJ3jE4JtFKu7JVGbQTGyvaksyreOj51kUidVcXvNHj79T8TSrCmEEZQpdsF8yzBgvM8XWqv0k+PpYJhAXssNdZQmENd7olGdaqSMHhUrgphX1SdPCgeknajOCGXbhvD0pWLliQqfrT9T8JCHiioFqXF9BQEQyerqrqJck6Y9gc3jKOt7poReicXWFJWtqRe58QBeQ9f/0Q0qUtSSkTtnDswKLe3t0hVHDR363tN4EVmlUJx8AWbR1Juc3PF5cE5PKE+gNPCFmgFvZdsHqZLElGkj39Y4QkjLP9SFvFu2pLAkpsjRTwNlh1vQW5gL2lPUtSsTQO1lYEn4WEPqv4e2LyFwKhh1XlHvKAAGWuq37f+TdymqPuR7iHfmE+wZg6nrAkbRjuImV35XRnY7uTe8u7JhlBIZICDopCo4gqGRhR9yzJf/8X+03+AdB2dSBjS36OAAAAAElFTkSuQmCC

在浏览器打开:
在这里插入图片描述

支付入口

需求分析

买方扫描门店二维码,进入支付入口 即进入订单确认页面,流程如下:

1)顾客扫描二维码

2)进入订单确认页面
在这里插入图片描述

Freemarker技术预研

支付确认页面由服务端渲染生成,常用的技术有jsp、freemarker velocity Thymeleaf等。

项目采用freemarker模板引擎,参考 freemarker基础。

交易服务支付入口

交互流程如下:
在这里插入图片描述

  1. 顾客扫描二维码,请求交易服务支付入口
  2. 交易服务解析请求,生成支付确认页面
  3. 交易服务向服务响应支付确认页面
支付确认页面

1、从资料->代码拷贝pay.html、pay_error.html到交易服务工程下。
在这里插入图片描述

2、在交易服务接口实现工程的pom.xml中引入依赖

<!--freemarker依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

3、在Nacos中配置spring-boot-freemarker.yaml,Group: COMMON_GROUP

#freemarker基本配置
spring:freemarker:charset: UTF-8request-context-attribute: rccontent-type: text/htmlsuffix: .htmlenabled: trueresources:add-mappings: false #关闭工程中默认的资源处理mvc:throw-exception-if-no-handler-found: true #出现错误时直接抛出异常

在这里插入图片描述
在shanjupay-transaction-service工程的bootstrap.yml引入spring-boot-freemarker.yaml
在这里插入图片描述

		- refresh: truedata-id: spring-boot-freemarker.yaml # spring boot freemarker配置group: COMMON_GROUP # 通用配置组

4、在shanjupay-transaction-service工程的config包下新建WebMvcConfig配置确认支付页面视图名:

package com.shanjupay.transaction.config;import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Component
public class WebMvcConfig implements WebMvcConfigurer {@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/pay‐page").setViewName("pay");}
}

5、定义支付入口接口

注意:PayController要向前端响应页面,使用@Controller注解。

package com.shanjupay.transaction.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpServletRequest;/*** 支付相关接口*/
@Slf4j
@Controller
public class PayController {/*** 支付入口** @param ticket  传入数据,对json数据进行的base64编码* @param request* @return*/@RequestMapping(value = "/pay‐entry/{ticket}")public String payEntry(@PathVariable("ticket") String ticket, HttpServletRequest request) throws Exception {return "forward:/pay‐page";}
}

6、测试页面渲染

1)在nacos配置网关转发到交易服务的路由
在这里插入图片描述

    transaction-service:path: /transaction/**stripPrefix: false

2)用chrome浏览器访问(用手机视图模式)二维码的统一入口地址。

http://127.0.0.1:56010/transaction/pay-entry/…

注意:由于没有数据,订单信息均为空。
在这里插入图片描述

页面完善

前边测试的支付确认页面由于没有数据所以显示为空,下边对页面进行完善。

1、页面完善需求如下:

1)进入支付入口,传入ticket,需要解析ticket得到订单信息,在支付确认页面显示。

2)解析出客户端类型,目前支付微信、支付宝两种。

解析ticket

拷贝资料–>代码下的PayOrderDTO.java、PayOrderConvert.java到交易服务工程。

解析ticket代码如下:

/*** 支付入口** @param ticket  传入数据,对json数据进行的base64编码* @param request* @return*/
@RequestMapping(value = "/pay-entry/{ticket}")
public String payEntry(@PathVariable("ticket") String ticket, HttpServletRequest request) throws Exception {//准备确认页面所需要的数据,将ticket的base64还原String jsonString = EncryptUtil.decodeUTF8StringBase64(ticket);//将json串转成对象PayOrderDTO order = JSON.parseObject(jsonString, PayOrderDTO.class);//将对象转成url格式,将对象的属性和值组成一个url的key/value串String params = ParseURLPairUtil.parseURLPair(order);return "forward:/pay‐page?" + params;
}
解析客户端类型

1、拷贝到资料–>代码下的BrowserType.java到交易服务controller包下,此类是系统定义的客户端配置类型。

2、修改支付入口代码:

/*** 支付入口** @param ticket  传入数据,对json数据进行的base64编码* @param request* @return*/
@RequestMapping(value = "/pay-entry/{ticket}")
public String payEntry(@PathVariable("ticket") String ticket, HttpServletRequest request) throws Exception {//准备确认页面所需要的数据,将ticket的base64还原String jsonString = EncryptUtil.decodeUTF8StringBase64(ticket);//将json串转成对象PayOrderDTO order = JSON.parseObject(jsonString, PayOrderDTO.class);//将对象转成url格式,将对象的属性和值组成一个url的key/value串String params = ParseURLPairUtil.parseURLPair(order);//2、解析客户端的类型(微信、支付宝)//得到客户端类型BrowserType browserType = BrowserType.valueOfUserAgent(request.getHeader("user-agent"));switch (browserType) {case ALIPAY://转发到确认页面,直接跳转收银台pay.htmlreturn "forward:/pay-page?" + params;case WECHAT://转发到确认页面,获取授权码(待实现)return "forward:/pay-page?" + params;default:}//不支持客户端类型,转发到错误页面return "forward:/pay-page-error";
}

在WebMvcConfig类中添加如下:
在这里插入图片描述

接口测试

由于添加上了客户端类型的解析,使用微信或支付宝扫码方可进入支付入口,需要使用模拟器进行测试。

1、修改支付入口地址

修改为模拟器可以访问到的地址,模拟器安装在开发电脑上,支付入口地址修改为开发电脑局域网的地址。
在这里插入图片描述
在这里插入图片描述
2、生成门店二维码

3、使用模拟器运行支付宝沙箱APP,扫描二维码查看支付确认页面
在这里插入图片描述

立即支付

需求分析

顾客扫码进入支付确认页面,输入金额,点击立即支付,打开支付客户端(微信或支付宝),输入支付密码完成支付。

立即支付需要调用第三方支付渠道的统一下单接口,本章节完成支付宝统一下单接口对接。

交互流程如下:
在这里插入图片描述
支付渠道代理服务介绍:

有支付需求的微服务统一通过支付渠道代理服务调用“第三方支付服务”提供的接口,这样做的好处由支付渠道代理服务将第三方支付系统和闪聚支付内部服务进行解耦合。

整体执行流程如下:

  1. 顾客输入金额,点击立即支付
  2. 请求交易服务,交易服务保存订单
  3. 交易服务调用支付渠道代理服务的支付宝下单接口
  4. 支付渠道代理服务调用支付宝的统一下单接口。
  5. 支付凭证返回

搭建支付渠道代理工程

支付渠道代理服务包括如下工程:

服务名 职责
支付渠道代理服务API(shanjupay-payment-agent-api) 定义支付渠道代理服务提供的接口
支付渠道代理服务(shanjupay-payment-agent-service) 实现支付渠道代理服务的所有接口

1、复制提供的shanjupay-payment-agent目录到shanjupay根目录

2、添加Module到IDEA中
在这里插入图片描述
3、在Nacos中添加payment-agent-service.yaml配置,Group: SHANJUPAY_GROUP

server:servlet:context-path: /payment-receiver

在这里插入图片描述

4、打开shanjupay-payment-agent-service工程的bootstrap.yml,将其中的namespace替换为自己创建的dev命名空间ID
在这里插入图片描述

5、启动PaymentAgentBootstrap测试:
在这里插入图片描述

支付渠道代理服务支付宝下单

接口定义
支付宝接口参数

支付渠道代理服务调用支付宝手机网站下单接口,下边梳理接口的参数:

公共参数如下:

标记蓝色的由sdk设置、标记红色的已在支付渠道参数配置中(需要支付渠道代理服务接口处理),标记绿色的需支付渠道代理服务接口处理。
在这里插入图片描述
在这里插入图片描述
业务参数如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

接口定义

1、接口描述:

调用支付宝手机wap下单接口

2、接口定义

在shanjupay-payment-agent-api工程中新建PayChannelAgentService接口类定义接口:

package com.shanjupay.paymentagent.api;import com.shanjupay.common.domain.BusinessException;
import com.shanjupay.paymentagent.api.conf.AliConfigParam;
import com.shanjupay.paymentagent.api.dto.AlipayBean;
import com.shanjupay.paymentagent.api.dto.PaymentResponseDTO;/*** 与第三支付渠道进行交互,调用支付宝手机WAP下单接口*/
public interface PayChannelAgentService {/*** 调用支付宝的下单接口* @param aliConfigParam 支付渠道配置的参数(配置的支付宝的必要参数)* @param alipayBean     业务参数,请求支付参数(商户订单号,订单标题,订单描述,,)* @return 统一返回PaymentResponseDTO*/public PaymentResponseDTO createPayOrderByAliWAP(AliConfigParam aliConfigParam, AlipayBean alipayBean) throws BusinessException;
}
接口实现

在shanjupay-payment-agent-service工程中新建service包,创建PayChannelAgentServiceImpl实现类

package com.shanjupay.paymentagent.service;import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.response.AlipayTradeWapPayResponse;
import com.shanjupay.common.domain.BusinessException;
import com.shanjupay.common.domain.CommonErrorCode;
import com.shanjupay.paymentagent.api.PayChannelAgentService;
import com.shanjupay.paymentagent.api.conf.AliConfigParam;
import com.shanjupay.paymentagent.api.dto.AlipayBean;
import com.shanjupay.paymentagent.api.dto.PaymentResponseDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Service;@Slf4j
@Service
public class PayChannelAgentServiceImpl implements PayChannelAgentService {/*** 调用支付宝的下单接口* @param aliConfigParam 支付渠道配置的参数(配置的支付宝的必要参数)* @param alipayBean     业务参数,请求支付参数(商户订单号,订单标题,订单描述,,)* @return 统一返回PaymentResponseDTO*/@Overridepublic PaymentResponseDTO createPayOrderByAliWAP(AliConfigParam aliConfigParam, AlipayBean alipayBean) throws BusinessException {log.info("支付宝请求参数", alipayBean.toString());//支付宝渠道参数String url = aliConfigParam.getUrl();//支付宝接口网关地址,下单接口地址String appId = aliConfigParam.getAppId();//支付宝应用idString rsaPrivateKey = aliConfigParam.getRsaPrivateKey();//应用私钥String format = aliConfigParam.getFormat();//数据格式jsonString charest = aliConfigParam.getCharest();//字符编码String alipayPublicKey = aliConfigParam.getAlipayPublicKey();//支付宝公钥String signtype = aliConfigParam.getSigntype();//签名算法类型String returnUrl = aliConfigParam.getReturnUrl();//支付成功跳转的urlString notifyUrl = aliConfigParam.getNotifyUrl();//支付结果异步通知的url//构造sdk的客户端对象,支付宝sdk客户端AlipayClient alipayClient = new DefaultAlipayClient(url, appId, rsaPrivateKey, format, charest, alipayPublicKey, signtype); //获得初始化的AlipayClient//封装请求支付信息AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();//创建API对应的requestAlipayTradeWapPayModel model = new AlipayTradeWapPayModel();model.setOutTradeNo(alipayBean.getOutTradeNo());//商户的订单,就是闪聚平台的订单model.setTotalAmount(alipayBean.getTotalAmount());//订单金额(元)model.setSubject(alipayBean.getSubject());//订单标题model.setBody(alipayBean.getBody());//订单内容model.setProductCode("QUICK_WAP_PAY");//商户与支付宝签定的产品码,固定为QUICK_WAP_WAYmodel.setTimeoutExpress(alipayBean.getExpireTime());//订单过期时间alipayRequest.setBizModel(model);//请求参数集合log.info("createPayOrderByAliWAP..alipayRequest:{}", JSON.toJSONString(alipayBean));alipayRequest.setReturnUrl(returnUrl);//设置同步地址alipayRequest.setNotifyUrl(notifyUrl);//设置异步通知地址try {//请求支付宝下单接口,发起http请求,调用SDK提交表单AlipayTradeWapPayResponse response = alipayClient.pageExecute(alipayRequest);PaymentResponseDTO paymentResponseDTO = new PaymentResponseDTO();log.info("调用支付宝下单接口,响应内容:{}", response.getBody());paymentResponseDTO.setContent(response.getBody());//支付宝的响应结果return paymentResponseDTO;} catch (AlipayApiException e) {e.printStackTrace();throw new BusinessException(CommonErrorCode.E_400002);//支付宝确认支付失败}}
}

交易服务支付宝下单

接口定义

交易服务支付宝下单是提供给支付入口请求的支付宝付款接口,当用户用支付宝客户端扫描二维码进入确认支付页面,点击确认支付即将请求此接口。

1、接口描述

1)接收前端支付请求

2)保存订单信息到闪聚支付平台

3)调用支付渠道代理服务请求支付宝下单接口

4)将支付宝下单接口响应结果返回到前端,前端开始进行支付

2、接口定义

在shanjupay-transaction-service工程的PayController中定义接口如下:

定义OrderConfirmVO接收前端请求的支付参数:

package com.shanjupay.transaction.vo;import io.swagger.annotations.ApiModel;
import lombok.Data;@ApiModel(value = "OrderConfirmVO", description = "订单确认信息")
@Data
public class OrderConfirmVO {private String appId; //应用idprivate String tradeNo;//交易单号private String openId;//微信openidprivate String storeId;//门店idprivate String channel; //服务类型private String body;//订单描述private String subject;//订单标题private String totalAmount;//金额
}

接口定义如下:

/*** 支付宝的下单接口,前端订单确认页面,点击确认支付,请求进来* @param orderConfirmVO 订单信息* @param request* @param response*/
@ApiOperation("支付宝门店下单付款")
@PostMapping("/createAliPayOrder")
public void createAlipayOrderForStore(OrderConfirmVO orderConfirmVO, HttpServletRequest request, HttpServletResponse response) throws BusinessException, IOException {}
接口实现
保存订单

1、拷贝资料–》代码中的IdWorkerUtils.java、PaymentUtil.java工具类到common 工程。

2、在TransactionService中定义接口submitOrderByAli方法:

/*** 保存支付宝订单,1、保存订单到闪聚平台,2、调用支付渠道代理服务调用支付宝的接口* @param payOrderDTO* @return* @throws BusinessException*/
public PaymentResponseDTO submitOrderByAli(PayOrderDTO payOrderDTO) throws BusinessException;

2、在TransactionServiceImpl中编写submitOrderByAli方法。

/*** 保存支付宝订单,1、保存订单到闪聚平台,2、调用支付渠道代理服务调用支付宝的接口* @param payOrderDTO* @return* @throws BusinessException*/
@Override
public PaymentResponseDTO submitOrderByAli(PayOrderDTO payOrderDTO) throws BusinessException {payOrderDTO.setChannel("ALIPAY_WAP");//支付渠道//保存订单到闪聚平台数据库PayOrderDTO save = save(payOrderDTO);//调用支付渠道代理服务支付宝下单接口//...return null;
}

3、编写保存订单的方法

//保存订单到闪聚平台(公用)
private PayOrderDTO save(PayOrderDTO payOrderDTO) throws BusinessException {PayOrder payOrder = PayOrderConvert.INSTANCE.dto2entity(payOrderDTO);//订单号payOrder.setTradeNo(PaymentUtil.genUniquePayOrderNo());//采用雪花片算法payOrder.setCreateTime(LocalDateTime.now());//订单创建时间payOrder.setExpireTime(LocalDateTime.now().plus(30, ChronoUnit.MINUTES));//设置过期时间是30分钟后payOrder.setCurrency("CNY");//人民币payOrder.setTradeState("0");//订单状态,0:订单生成payOrderMapper.insert(payOrder);//插入订单return PayOrderConvert.INSTANCE.entity2dto(payOrder);
}
请求代理服务调用支付宝下单

在TransactionServiceImpl中编写私有方法,实现请求代理服务调用支付宝下单接口。

此私有方法被submitOrderByAli方法调用。

//调用支付渠道代理服务的支付宝下单接口
private PaymentResponseDTO alipayH5(String tradeNo) {//订单信息,从数据库查询订单PayOrderDTO payOrderDTO = queryPayOrder(tradeNo);//组装alipayBean,构建支付实体AlipayBean alipayBean = new AlipayBean();alipayBean.setOutTradeNo(payOrderDTO.getTradeNo());//订单号try {//支付宝那边入参是元,将分转成元alipayBean.setTotalAmount(AmountUtil.changeF2Y(payOrderDTO.getTotalAmount().toString()));} catch (Exception e) {e.printStackTrace();throw new BusinessException(CommonErrorCode.E_300006);}alipayBean.setSubject(payOrderDTO.getSubject());alipayBean.setBody(payOrderDTO.getBody());alipayBean.setStoreId(payOrderDTO.getStoreId());alipayBean.setExpireTime("30m");//支付渠道配置参数,从数据库查询,根据应用、服务类型、支付渠道查询支付渠道参数//String appId,String platformChannel,String payChannelPayChannelParamDTO payChannelParamDTO = payChannelService.queryParamByAppPlatformAndPayChannel(payOrderDTO.getAppId(), "shanju_c2b", "ALIPAY_WAP");if (payChannelParamDTO == null) {throw new BusinessException(CommonErrorCode.E_300007);}String paramJson = payChannelParamDTO.getParam();//支付渠道参数AliConfigParam aliConfigParam = JSON.parseObject(paramJson, AliConfigParam.class);//字符编码aliConfigParam.setCharest("utf-8");//AliConfigParam aliConfigParam, AlipayBean alipayBeanPaymentResponseDTO payOrderByAliWAP = payChannelAgentService.createPayOrderByAliWAP(aliConfigParam, alipayBean);log.info("支付宝H5支付响应Content:" + payOrderByAliWAP.getContent());return payOrderByAliWAP;
}

定义根据订单号查询订单信息。

在shanjupay-transaction-api工程的TransactionService中定义如下接口:

/*** 根据订单号查询订单号* @param tradeNo* @return*/
public PayOrderDTO queryPayOrder(String tradeNo);

在shanjupay-transaction-service工程的TransactionServiceImpl中实现接口,如下:

/*** 根据订单号查询订单信息* @param tradeNo* @return*/
public PayOrderDTO queryPayOrder(String tradeNo) {PayOrder payOrder = payOrderMapper.selectOne(new LambdaQueryWrapper<PayOrder>().eq(PayOrder::getTradeNo, tradeNo));return PayOrderConvert.INSTANCE.entity2dto(payOrder);
}

完善submitOrderByAli方法,调用alipayH5方法:

/*** 保存支付宝订单,1、保存订单到闪聚平台,2、调用支付渠道代理服务调用支付宝的接口** @param payOrderDTO* @return* @throws BusinessException*/
@Override
public PaymentResponseDTO submitOrderByAli(PayOrderDTO payOrderDTO) throws BusinessException {payOrderDTO.setChannel("ALIPAY_WAP");//支付渠道//保存订单到闪聚平台数据库PayOrderDTO save = save(payOrderDTO);//调用支付渠道代理服务支付宝下单接口,请求第三方支付系统PaymentResponseDTO paymentResponseDTO = alipayH5(save.getTradeNo());return paymentResponseDTO;
}
完善交易服务下单接口

在shanjupay-transaction-service工程的PayController类中完善createAliPayOrder接口,调用submitOrderByAli提交支付宝订单。

/*** 支付宝的下单接口,前端订单确认页面,点击确认支付,请求进来* @param orderConfirmVO 订单信息* @param request* @param response*/
@ApiOperation("支付宝门店下单付款")
@PostMapping("/createAliPayOrder")
public void createAlipayOrderForStore(OrderConfirmVO orderConfirmVO, HttpServletRequest request, HttpServletResponse response) throws BusinessException, IOException {if (StringUtils.isBlank(orderConfirmVO.getAppId())) {throw new BusinessException(CommonErrorCode.E_300003);}PayOrderDTO payOrderDTO = PayOrderConvert.INSTANCE.vo2dto(orderConfirmVO);//应用idString appId = payOrderDTO.getAppId();//获取下单应用信息AppDTO app = appService.getAppById(appId);//设置所属商户payOrderDTO.setMerchantId(app.getMerchantId());//商户id//将前端输入的元转成分payOrderDTO.setTotalAmount(Integer.parseInt(AmountUtil.changeY2F(orderConfirmVO.getTotalAmount().toString())));//客户端ippayOrderDTO.setClientIp(IPUtil.getIpAddr(request));//保存订单,调用支付渠道代理服务的支付宝下单PaymentResponseDTO<String> paymentResponseDTO = transactionService.submitOrderByAli(payOrderDTO);//支付宝下单接口响应String content = paymentResponseDTO.getContent();log.info("支付宝H5支付响应的结果:" + content);response.setContentType("text/html;charset=UTF-8");response.getWriter().write(content);//直接将完整的表单html输出到页面response.getWriter().flush();response.getWriter().close();
}
测试

1、生成二维码

注意:二维码的URL可以被模拟器访问。
在这里插入图片描述
2、扫码进入支付入口,进入支付确认页面

3、输入金额,点击确认支付。

订单写入闪聚平台数据库。

调用支付宝下单接口是否成功。

获取支付结果

需求分析

获取支付结果的需求包括如下几个方面:

1、服务间异步通信

顾客支付完成后,平台需要及时得到支付结果并更新数据库中的订单状态。根据微服务职责划分,支付渠道代理服务负责与支付宝、微信接口对接,交易服务负责维护订单的数据,支付渠道代理服务如何把查询到的订单结果通知给交易服务呢?项目中会采用消息队列来完成。

2、实现第三方支付系统支付结果查询接口

完成支付后第三方支付系统提供两种方式获取支付结果,如下图:

1)第三方支付系统主动通知闪聚支付平台支付结果。

2)闪聚支付平台主动从第三方支付系统查询支付结果。
在这里插入图片描述
以上两种方法在实际项目中可以都用,其中第二种是必须用的,因为第一种是由第三方支付系统主动通知闪聚支付平台,当调用闪聚平台接口无法通信达到一定的次数后第三方支付系统将不再通知。

本项目支付渠道代理服务集成第二种方法完成支付结果的查询。

3、下单成功延迟向第三方支付系统查询支付结果

在调用第三方支付下单接口之后此时用户正在支付中,所以需要延迟一定的时间再去查询支付结果。

如果查询支付结果还没有支付再继续等待一定的时间再去查询,当达到订单的有效期还没有支付则不再查询。

RocketMQ技术预研

参考: RocketMQ研究 。

技术方案

项目使用消息队列RocketMQ完成支付渠道代理服务与交易服务之间的通信,如下图:
在这里插入图片描述

  1. 支付渠道代理服务调用第三方支付下单接口。(此时顾客开始输入密码进行支付)
  2. 支付渠道代理向消息队列发送一条延迟消息(查询支付结果),消费方仍是支付渠道代理服务。
  3. 支付渠道代理接收消息,调用支付宝接口查询支付结果
  4. 支付渠道代理查询到支付结果,将支付结果发送至MQ,消费方是交易服务。
  5. 交易服务接收到支付结果消息,更新订单状态。

支付渠道代理查询支付宝交易状态

支付宝交易状态查询接口

参考手机网支付产品介绍文档(https://docs.open.alipay.com/203),查看alipay.trade.query交易状态查询接口文档。
在这里插入图片描述

请求参数

公共请求参数基本都是支付渠道设置的参数,实现方法参考支付宝下单接口即可。
在这里插入图片描述
业务请求参数中out_trade_no即闪聚支付平台订单号,根据此订单号查询支付状态。
在这里插入图片描述

响应参数

支付宝交易状态查询接口的响应参数如下:
在这里插入图片描述
在这里插入图片描述
以上参数主要解析code和trade_status:

1、根据code判断接口请求是否成功

参考:https://docs.open.alipay.com/common/105806

2、根据trade_status判断具体的支付状态

交易状态如下:

  • WAIT_BUYER_PAY(交易创建,等待买家付款)
  • TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)
  • TRADE_SUCCESS(交易支付成功)
  • TRADE_FINISHED(交易结束,不可退款)
支付渠道代理接口定义
接口定义

接口描述:

1)使用支付宝SDK发起支付结果查询请求 2)返回查询结果

在PayChannelAgentService定义如下接口:

/*** 查询支付宝订单状态* @param aliConfigParam 支付渠道参数* @param outTradeNo     闪聚平台的订单号* @return*/
public PaymentResponseDTO queryPayOrderByAli(AliConfigParam aliConfigParam, String outTradeNo) throws BusinessException;
接口实现

参考支付宝官方提供的样例代码与支付宝通信。https://docs.open.alipay.com/api_1/alipay.trade.query

1)定义支付宝查询返回状态码常量类:AliCodeConstants

package com.shanjupay.paymentagent.common.constant;/*** 支付宝查询返回状态码常量类*/
public class AliCodeConstants {public static final String SUCCESSCODE = "10000"; // 支付成功或接口调用成功public static final String PAYINGCODE = "10003"; // 用户支付中public static final String FAILEDCODE = "40004"; // 失败public static final String ERRORCODE = "20000"; // 系统异常/*** 支付宝交易状态*/public static final String WAIT_BUYER_PAY = "WAIT_BUYER_PAY";//(交易创建,等待买家付款)public static final String TRADE_CLOSED = "TRADE_CLOSED";//(未付款交易超时关闭,或支付完成后全额退款)public static final String TRADE_SUCCESS = "TRADE_SUCCESS";//(交易支付成功)public static final String TRADE_FINISHED = "TRADE_FINISHED";//(交易结束,不可退款)
}

2)sdk示例如下

参考sdk示例:https://docs.open.alipay.com/api_1/alipay.trade.query/

AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", "app_id", "your private_key", "json", "GBK", "alipay_public_key", "RSA2");
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
request.setBizContent("{" +"\"out_trade_no\":\"20150320010101001\"," +"\"trade_no\":\"2014112611001004680 073956707\"," +"\"org_pid\":\"2088101117952222\"," +"      \"query_options\":[" +"        \"TRADE_SETTLE_INFO\"" +"      ]" +"  }");
AlipayTradeQueryResponse response = alipayClient.execute(request);if (response.isSuccess()) {System.out.println("调用成功");
} else {System.out.println("调用失败");
}

3)接口实现如下

在shanjupay-payment-agent-service工程的shanjupay-payment-agent-service实现类中,支付宝交易状态查询实现方法如下:

/*** 查询支付宝订单状态* @param aliConfigParam 支付渠道参数* @param outTradeNo     闪聚平台的订单号* @return*/
@Override
public PaymentResponseDTO queryPayOrderByAli(AliConfigParam aliConfigParam, String outTradeNo) throws BusinessException {String url = aliConfigParam.getUrl();//支付宝接口网关地址String appId = aliConfigParam.getAppId();//支付宝应用idString rsaPrivateKey = aliConfigParam.getRsaPrivateKey();//应用私钥String format = aliConfigParam.getFormat();//json格式String charest = aliConfigParam.getCharest();//编码String alipayPublicKey = aliConfigParam.getAlipayPublicKey();//支付宝公钥String signtype = aliConfigParam.getSigntype();//签名算法String returnUrl = aliConfigParam.getReturnUrl();//支付成功跳转的urlString notifyUrl = aliConfigParam.getNotifyUrl();//支付结果异步通知的urllog.info("C扫B请求支付宝查询订单,参数:{}", JSON.toJSONString(aliConfigParam));//构造sdk的客户端对象AlipayClient alipayClient = new DefaultAlipayClient(url, appId, rsaPrivateKey, format, charest, alipayPublicKey, signtype); //获得初始化的AlipayClientAlipayTradeQueryRequest request = new AlipayTradeQueryRequest();AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();model.setOutTradeNo(outTradeNo);//商户的订单,就是闪聚平台的订单号request.setBizModel(model);//封装请求参数AlipayTradeQueryResponse response = null;try {//请求支付宝订单状态查询接口response = alipayClient.execute(request);//支付宝响应的code,10000表示接口调用成功String code = response.getCode();if (AliCodeConstants.SUCCESSCODE.equals(code)) {String tradeStatusString = response.getTradeStatus();//解析支付宝返回的状态,解析成闪聚平台的TradeStatusTradeStatus tradeStatus = covertAliTradeStatusToShanjuCode(tradeStatusString);//String tradeNo(支付宝订单号), String outTradeNo(闪聚平台的订单号), TradeStatus tradeState(订单状态), String msg(返回信息)PaymentResponseDTO<Object> dto = PaymentResponseDTO.success(response.getTradeNo(), response.getOutTradeNo(), tradeStatus, response.getMsg());log.info("‐‐‐‐查询支付宝H5支付结果" + JSON.toJSONString(dto));return dto;}} catch (AlipayApiException e) {e.printStackTrace();}//String msg, String outTradeNo, TradeStatus tradeStatereturn PaymentResponseDTO.fail("支付宝订单状态查询失败", outTradeNo, TradeStatus.UNKNOWN);
}

定义支付宝响应状态与闪聚平台的转换方法:

 /*** 解析支付宝的订单状态为闪聚平台的状态* 将支付宝查询时订单状态trade_status 转换为闪聚订单状态* @param aliTradeStatus 支付宝交易状态* @return*/
private TradeStatus covertAliTradeStatusToShanjuCode(String aliTradeStatus) {switch (aliTradeStatus) {case AliCodeConstants.WAIT_BUYER_PAY://(交易创建,等待买家付款)return TradeStatus.USERPAYING;//交易新建,等待支付case AliCodeConstants.TRADE_FINISHED://(交易结束,不可退款)case AliCodeConstants.TRADE_SUCCESS://(交易支付成功)return TradeStatus.SUCCESS;//业务交易支付 明确成功case AliCodeConstants.TRADE_CLOSED://(未付款交易超时关闭,或支付完成后全额退款)return TradeStatus.REVOKED;//交易已撤销default:return TradeStatus.FAILED;//交易失败}
}
接口测试

对queryPayOrderByAli方法进行单元测试:

1、通过C扫B进行支付定下单,从数据库找到订单号

2、编写测试类

在shanjupay-payment-agent-service的test包下创建测试类

APP_ID、APP_PRIVATE_KEY、ALIPAY_PUBLIC_KEY使用自己申请的支付宝沙箱参数。

package com.shanjupay.paymentagent.service;import com.shanjupay.paymentagent.api.PayChannelAgentService;
import com.shanjupay.paymentagent.api.conf.AliConfigParam;
import com.shanjupay.paymentagent.api.dto.PaymentResponseDTO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@SpringBootTest
@RunWith(SpringRunner.class)
public class TestPayChannelAgentService {@AutowiredPayChannelAgentService payChannelAgentService;@Testpublic void testqueryPayOrderByAli() {//沙箱应用APPIDString APP_ID = "2021000118620802";//沙箱应用私钥String APP_PRIVATE_KEY = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCWIa5n7rnlTYpgC7AUXC5n1I9CtFfK40wBkQoPea8dpsuMWcOFOM7TDzD1ImQXO01oak5VGdpRKj4WIOH3o/n2VYwaFipBldGLfFOQhPZbqbFEI2I7vWf0r716SuN5cG4mny8ZbhP56S4ccNpCsLsd7ZExjDPMR30loc0sBYkwomBk3To9HzD+xvYB0Ld2orhjFTB7/VCPYblDYZkwfRiZ/BoJY5cpyt3hgF9uVa0KQq6whhrvnX0HzzTNtYClCrOemLu0BqTzX6g5mFxGJwfF7hhwvbgHqTS8Gonn9+ha3VTEMikPHjVCg03PmtBoJyaMhQwhATELNe2UWOMmlRJLAgMBAAECggEADw/b/oNd1Rp9anths/k3kqUppkiPkkRRiMqzVrAfmHr2auNKkWAMp/IbOEy1+/qwHmyj5TfNxlzVk8TCxuSFnGgiwS8+GAxe1H6pp5MfYDzbEvn1zgaHmm3TNaSzw6g69Nb9k7COgoEZZjMQQqaWbz85VN47CCCX9qGQAv2fMOjBnYXz9/5cexEYFM5n881ocWh4CbmRwn3S5M2EqVXMvxkYP27STtv808GvrozrODzR+D4k3yubkLC7/U9HDnazY5sAN0YNCf3sBudAgeU42HyBHq2sxIUWyZy9UaxTnqNoYo+8lldGS3QbdrXIHZM40V6tWKYbGMA2BrYAFNCRCQKBgQDnvFjmxEZRQOt9f9u5fF4wL2TRYPQajsajBm5bXq2MF+YQnNnUzh1n+Lh/GLSCND+97mNftaM0zYgGxj241KaLF83w9KqFTl1RCT1CSMN4+PRhsMfzU/Hpx/13ZYtvOb+qLnczjkTYa4wx79n5I54ib9noQcKGsEtRyHygU+tqLwKBgQCl2fFTb2l2IdwgeopRkE+Ak+bTIBzks/VS8+4pbFPFKb94VI0eJCTtLaMun9ElB01WfnYqVfQaCeieRzHWnpo00XR6r4qtmeoBV91JDpHmpnRqHjEMr5gr5RBhzgLUgxOA2O2RtX68Pe8Dd/siSEKHGz9gyw8Eus1gdj2RjrH+pQKBgQC9MO8f0ARcl/Tqa/V2VMwM6NSVgGMqP4B6XmjAneZwJp7E11mcPH6TgOMXmJLebkvQA40L+Z36IQa6CSUg/jPOASw4WXfSB6112GYz9HXqEM5r50kHJnStWYJc9QFGWE5bYT4eUDtyuTMnHdvGZEbZdJnh3bY0AkArz9O3jWv4LwKBgC793oO+eIoxM9a8Ab70faI3xdoiKi2e067KULvJ5r5hgs/MXSOiKBhPqwHF5JNySzZrpH2AVyadkhxuna9qxtSaWD9+x3NCvevdgmR1zV8l4KxEm681fY9KWubrYR/nd7o1PLLhUuRxQ+yerThccwUm8kExp7K2XwSq2+0HGmXFAoGBAIxgJ57Pcmix0HO/xnEcHYhADrSDGDyKDCmNdoW/rxI5JKq6/SucPvDGwWv9/1bwp46y7CSWUM5UqCOqcnsbTDmoOz1oO3Lm7ESKydv7/IXQdrmDXUPzrXVGwne4JpCKkuwhYaCeT6uICJWOs4ZFv0kw9l9n9nYQBrQB06Hc4mzd";//支付宝公钥String ALIPAY_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApZsrdXBmfuatiXIQWT84KNAirI9XrGnFNFmXpoZUaU9kC40fFZiamPoCaHASs3gsuJka3gezE+rMoZHPNzHIoWsHnE6kPgv2n2AdBFW7Bo4BL8fkiNj+2iNPfF2Cn9CgpsplTWCBygJCdf6QoRblQRyQzpnMT8aqcid/5cXxUNTsXqtcunCmQyZMqN+KJcgskk46RFIvdgkQMNuiTKJI3Pg+pPRDFXoxpY4YcWiJNDRbzc7UC4jr+sR6qAYkd7mDiMSSK8t8ybtltbfyvIIYnTG87HCl/atMCGYWUcyohjWxduX1PDQP3IxxgKpRSDDujVb8s/Le4LZ4LEZocasUgQIDAQAB";//签名算法类型String CHARSET = "UTF-8";//支付宝接口的网关地址,正式"https://openapi.alipay.com/gateway.do"String serverUrl = "https://openapi.alipaydev.com/gateway.do";//签名算法类型String sign_type = "RSA2";//支付渠道参数AliConfigParam aliConfigParam = new AliConfigParam();aliConfigParam.setUrl(serverUrl);aliConfigParam.setCharest(CHARSET);aliConfigParam.setAlipayPublicKey(ALIPAY_PUBLIC_KEY);aliConfigParam.setRsaPrivateKey(APP_PRIVATE_KEY);aliConfigParam.setAppId(APP_ID);aliConfigParam.setFormat("json");aliConfigParam.setSigntype(sign_type);//AliConfigParam aliConfigParam,String outTradeNoPaymentResponseDTO paymentResponseDTO = payChannelAgentService.queryPayOrderByAli(aliConfigParam, "SJ1217987323129917440");System.out.println(paymentResponseDTO);}
}

支付结果查询

交互流程

根据技术方案的分析,交互流程如下:

  1. 支付渠道代理服务调用支付宝下单接口完成后向MQ发送“支付结果查询”消息(延迟消息),消费方为支付渠道代理服务。
  2. 支付渠道代理服务监听消息队列,接收“支付结果查询”消息。
  3. 支付渠道代理服务调用第三方支付系统的支付结果查询接口。
发送消息
配置RocketMQ

1)在支付渠道代理工程中添加RocketMQ依赖:

<dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.0.2</version>
</dependency>

2)在Nacos中添加spring-boot-starter-rocketmq.yaml配置,Data ID: spring-boot-starter-rocketmq.yaml,Group: COMMON_GROUP

rocketmq:nameServer: 127.0.0.1:9876producer:group: PID_PAY_PRODUCER

在这里插入图片描述
3)在shanjupay-payment-agent-service工程bootstrap.yml中引入此配置:

	   -refresh: truedata-id: spring-boot-starter-rocketmq.yaml # rocketmq配置group: COMMON_GROUP # 通用配置组

在这里插入图片描述

生产消息类

1、修改支付宝下单调用方法createPayOrderByAliWAP,调用PayProducer发送消息。

发送支付结果查询延迟消息代码如下:

try {//请求支付宝下单接口,发起http请求,调用SDK提交表单AlipayTradeWapPayResponse response = alipayClient.pageExecute(alipayRequest);PaymentResponseDTO paymentResponseDTO = new PaymentResponseDTO();log.info("调用支付宝下单接口,响应内容:{}", response.getBody());paymentResponseDTO.setContent(response.getBody());//支付宝的响应结果//向MQ发一条延迟消息,发送支付结果查询延迟消息PaymentResponseDTO<AliConfigParam> notice = new PaymentResponseDTO<AliConfigParam>();notice.setOutTradeNo(alipayBean.getOutTradeNo());//闪聚平台的订单号notice.setContent(aliConfigParam);notice.setMsg("ALIPAY_WAP");//标识是查询支付宝的接口//发送消息payProducer.payOrderNotice(notice);return paymentResponseDTO;
} catch (AlipayApiException e) {e.printStackTrace();throw new BusinessException(CommonErrorCode.E_400002);//支付宝确认支付失败
}

在这里插入图片描述
2、在支付渠道代理服务中编写生产消息类PayProducer

package com.shanjupay.paymentagent.message;import com.alibaba.fastjson.JSON;
import com.shanjupay.paymentagent.api.dto.PaymentResponseDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;@Component
@Slf4j
public class PayProducer {//订单结果查询主题private static final String TOPIC_ORDER = "TP_PAYMENT_ORDER";@AutowiredRocketMQTemplate rocketMQTemplate;//发送消息(查询支付宝订单状态)public void payOrderNotice(PaymentResponseDTO paymentResponseDTO) {log.info("支付通知发送延迟消息:{}", paymentResponseDTO);try {//发送延迟消息,处理消息存储格式Message<PaymentResponseDTO> message = MessageBuilder.withPayload(paymentResponseDTO).build();//延迟第3级发送(延迟10秒)rocketMQTemplate.syncSend(TOPIC_ORDER, message, 1000, 3);log.info("支付渠道代理服务向mq发送订单查询的消息:{}", JSON.toJSONString(paymentResponseDTO));} catch (Exception e) {log.warn(e.getMessage(), e);}}
}
接收消息

定义PayConsumer类,监听“支付结果查询”消息队列。

package com.shanjupay.paymentagent.message;import com.alibaba.fastjson.JSON;
import com.shanjupay.paymentagent.api.PayChannelAgentService;
import com.shanjupay.paymentagent.api.conf.AliConfigParam;
import com.shanjupay.paymentagent.api.dto.PaymentResponseDTO;
import com.shanjupay.paymentagent.api.dto.TradeStatus;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
@RocketMQMessageListener(topic = "TP_PAYMENT_ORDER", consumerGroup = "CID_PAYMENT_CONSUMER")
@Slf4j
public class PayConsumer implements RocketMQListener<MessageExt> {@AutowiredPayChannelAgentService payChannelAgentService;@Overridepublic void onMessage(MessageExt messageExt) {byte[] body = messageExt.getBody();String jsonString = new String(body);log.info("支付渠道代理服务接收到查询订单的消息:{}", JSON.toJSONString(jsonString));//将消息转成对象PaymentResponseDTO paymentResponseDTO = JSON.parseObject(jsonString, PaymentResponseDTO.class);String outTradeNo = paymentResponseDTO.getOutTradeNo();//订单号String params = String.valueOf(paymentResponseDTO.getContent());//支付渠道参数//params转成对象AliConfigParam aliConfigParam = JSON.parseObject(params, AliConfigParam.class);PaymentResponseDTO responseDTO = null;//判断是支付宝还是微信if ("ALIPAY_WAP".equals(paymentResponseDTO.getMsg())) {//调用支付宝订单状态查询接口,查询支付宝支付结果//AliConfigParam aliConfigParam,String outTradeNoresponseDTO = payChannelAgentService.queryPayOrderByAli(aliConfigParam, outTradeNo);} else if ("WX_JSAPI".equals(paymentResponseDTO.getMsg())) {//调用微信的接口去查询订单状态,查询微信支付结果//...}//当没有获取到订单结果,抛出异常,再次重试消费,返回查询获得的支付状态if (responseDTO == null || TradeStatus.UNKNOWN.equals(responseDTO.getTradeState()) || TradeStatus.USERPAYING.equals(responseDTO.getTradeState())) {//在支付状态未知或支付中,抛出异常会重新消息此消息//如果重试的次数达到一次数量,不要再重试消费,将消息记录到数据库,由单独的程序或人工进行处理log.info("支付代理‐‐‐支付状态未知,等待重试");throw new RuntimeException("支付状态未知,等待重试");}}
}

支付结果更新

交互流程

支付渠道代理服务查询到支付结果,将支付结果更新消息发送给交易服务,实现订单状态更新,流程如下:

  1. 支付渠道代理服务查询到支付结果
  2. 向MQ发送“支付结果更新”消息
  3. 交易服务监听“支付结果更新”消息队列
  4. 交易服务接收到“支付结果更新”消息,更新订单状态
发送消息

在支付渠道代理服务的PayProducer中定义发送“支付结果更新”消息方法

//订单结果 主题
private static final String TOPIC_RESULT = "TP_PAYMENT_RESULT";//发送支付结果消息
public void payResultNotice(PaymentResponseDTO paymentResponseDTO) {rocketMQTemplate.convertAndSend(TOPIC_RESULT, paymentResponseDTO);log.info("支付渠道代理服务向mq支付结果消息:{}", JSON.toJSONString(paymentResponseDTO));
}

在这里插入图片描述
修改支付渠道代理服务的PayConsumer,在查询到支付结果后调用payResultNotice

//... ...//将订单状态,再次发到mq...
//不管支付成功还是失败都需要发送支付结果消息
log.info("交易中心处理支付结果通知,支付代理发送消息:{}", responseDTO);
payProducer.payResultNotice(responseDTO);//... ...

在这里插入图片描述

接收消息
交易服务更新订单接口

在shanjupay-transaction-api工程中的TransactionService定义接口:

/*** 更新订单支付状态* @param tradeNo           闪聚平台订单号* @param payChannelTradeNo 支付宝或微信的交易流水号(第三方支付系统的订单)* @param state             订单状态  交易状态支付状态,0-订单生成,1-支付中(目前未使用),2-支付成功,4-关闭 5--失败*/
public void updateOrderTradeNoAndTradeState(String tradeNo, String payChannelTradeNo, String state) throws BusinessException;

在TransactionServiceImpl中实现updateOrderTradeNoAndTradeState方法,根据闪聚支付订单号和支付宝订单号更新订单状态:

/*** 更新订单支付状态* @param tradeNo           闪聚平台订单号* @param payChannelTradeNo 支付宝或微信的交易流水号(第三方支付系统的订单)* @param state             订单状态  交易状态支付状态,0-订单生成,1-支付中(目前未使用),2-支付成功,4-关闭 5--失败*/
@Override
public void updateOrderTradeNoAndTradeState(String tradeNo, String payChannelTradeNo, String state) throws BusinessException {LambdaUpdateWrapper<PayOrder> payOrderLambdaUpdateWrapper = new LambdaUpdateWrapper<>();payOrderLambdaUpdateWrapper.eq(PayOrder::getTradeNo, tradeNo).set(PayOrder::getTradeState, state).set(PayOrder::getPayChannelTradeNo, payChannelTradeNo);if (state != null && state.equals("2")) {payOrderLambdaUpdateWrapper.set(PayOrder::getPaySuccessTime, LocalDateTime.now());}payOrderMapper.update(null, payOrderLambdaUpdateWrapper);
}
交易服务接收消息

1)在交易服务工程中添加RocketMQ依赖:

<dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.0.2</version>
</dependency>

2)在shanjupay-transaction-service的bootstrap.yml中引入此配置:

	- refresh: truedata-id: spring-boot-starter-rocketmq.yaml # rocketmq配置group: COMMON_GROUP # 通用配置组

在这里插入图片描述
3)在交易服务定义“支付结果消息”消费类。

package com.shanjupay.transaction.message;import com.alibaba.fastjson.JSON;
import com.shanjupay.paymentagent.api.dto.PaymentResponseDTO;
import com.shanjupay.paymentagent.api.dto.TradeStatus;
import com.shanjupay.transaction.api.TransactionService;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
@RocketMQMessageListener(topic = "TP_PAYMENT_RESULT", consumerGroup = "CID_ORDER_CONSUMER")
@Slf4j
public class TransactionPayConsumer implements RocketMQListener<MessageExt> {@AutowiredTransactionService transactionService;@Overridepublic void onMessage(MessageExt messageExt) {byte[] body = messageExt.getBody();String jsonString = new String(body);log.info("交易服务向接收到支付结果消息:{}", JSON.toJSONString(jsonString));//接收到消息,内容包括订单状态PaymentResponseDTO paymentResponseDTO = JSON.parseObject(jsonString, PaymentResponseDTO.class);String tradeNo = paymentResponseDTO.getTradeNo();//支付宝微信的订单号订单号String outTradeNo = paymentResponseDTO.getOutTradeNo();//闪聚平台的订单号//订单状态TradeStatus tradeState = paymentResponseDTO.getTradeState();//更新数据库switch (tradeState) {case SUCCESS://String tradeNo, String payChannelTradeNo, String state//支付成功时,修改订单状态为支付成功transactionService.updateOrderTradeNoAndTradeState(outTradeNo, tradeNo, "2");return;case REVOKED://支付关闭时,修改订单状态为关闭transactionService.updateOrderTradeNoAndTradeState(outTradeNo, tradeNo, "4");return;case FAILED://支付失败时,修改订单状态为失败transactionService.updateOrderTradeNoAndTradeState(outTradeNo, tradeNo, "5");return;default:throw new RuntimeException(String.format("无法解析支付结果:%s", body));}}
}

接入微信

接入分析

闪聚支付平台是将各各常用的第三方支付渠道统一为一个支付通道,前边实现了C扫B支付宝支付的流程,下边接入微信支付,根据接入支付宝的流程分析接入微信需要实现的如下:

1、支付入口

顾客扫码进入支付入口,根据客户端类型判断是微信还是支付宝,是支付宝则直接进入收银台,如果是微信则需要首先获取openid,再进入收银台。

2、立即支付

点击立即支付调用微信的统一下单接口,若下单成功则唤起微信客户端开始支付。

3、获取支付结果

调用微信的“支付结果查询”接口获取支付结果。

支付入口

获取openid接口

参考:闪聚支付-第3章-微信支付接入指南

获取微信授权码

用户进入支付入口,判断客户端类型如果是微信则获取微信授权码。

根据获取openid的流程得知,第一步获取微信授权码,这里需要生成获取微信授权码的URL,由页面重定向即可。

1、在nacos中交易服务的主配置文件中添加如下参数:

weixin:oauth2RequestUrl: "https://open.weixin.qq.com/connect/oauth2/authorize"oauth2CodeReturnUrl: "http://xfc.nat300.top/transaction/wx‐oauth‐code‐return"oauth2Token: "https://api.weixin.qq.com/sns/oauth2/access_token"

2、在交易服务TransactionService中定义接口,如下:

/*** 申请微信授权码* @param payOrderDTO* @return 申请授权码的地址*/
public String getWXOAuth2Code(PayOrderDTO payOrderDTO);

3、在交易服务TransactionServiceImpl实现类中添加获取微信授权码方法。

@Value("${weixin.oauth2RequestUrl}")
String oauth2RequestUrl;@Value("${weixin.oauth2CodeReturnUrl}")
String oauth2CodeReturnUrl;@Value("${weixin.oauth2Token}")
String oauth2Token;/*** 申请微信授权码** @param payOrderDTO* @return 申请授权码的地址*/
@Override
public String getWXOAuth2Code(PayOrderDTO payOrderDTO) {//闪聚平台的应用idString appId = payOrderDTO.getAppId();//获取微信支付渠道参数//String appId,String platformChannel,String payChannel,获取微信支付渠道参数,根据应用、服务类型、支付渠道查询支付渠道参数PayChannelParamDTO payChannelParamDTO = payChannelService.queryParamByAppPlatformAndPayChannel(appId, "shanju_c2b", "WX_JSAPI");if (payChannelParamDTO == null) {throw new BusinessException(CommonErrorCode.E_300007);}//支付渠道参数String param = payChannelParamDTO.getParam();//微信支付渠道参数WXConfigParam wxConfigParam = JSON.parseObject(param, WXConfigParam.class);//state是一个原样返回的参数String jsonString = JSON.toJSONString(payOrderDTO);//将订单信息封装到state参数中String state = EncryptUtil.encodeUTF8StringBase64(jsonString);//https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirecttry {String url = String.format("%s?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=%s#wechat_redirect",oauth2RequestUrl, wxConfigParam.getAppId(), oauth2CodeReturnUrl, state);log.info("微信生成授权码url:{}", url);return "redirect:" + url;} catch (Exception e) {e.printStackTrace();}return "forward:/pay-page-error";//生成获取授权码链接失败
}

3、在支付入口PayController中调用微信授权码获取方法

/*** 支付入口* @param ticket  传入数据,对json数据进行的base64编码* @param request* @return*/
@RequestMapping(value = "/pay-entry/{ticket}")
public String payEntry(@PathVariable("ticket") String ticket, HttpServletRequest request) throws Exception {//... ...BrowserType browserType = BrowserType.valueOfUserAgent(request.getHeader("user-agent"));switch (browserType) {case ALIPAY://转发到确认页面,直接跳转收银台pay.htmlreturn "forward:/pay-page?" + params;case WECHAT://转发到确认页面,获取授权码(待实现)//return "forward:/pay-page?" + params;//先获取授权码,申请openid,再到支付确认页面return transactionService.getWXOAuth2Code(payOrderDTO);default:}// ......
}

在这里插入图片描述

微信授权码回调接口

授权码获取成功后微信会将授权码传入授权码回调URL,在授权码回调接口中实现获取openid。

接口定义

1、在PayController中定义微信授权码回调接口

/*** 授权码回调,申请获取授权码,微信将授权码请求到此地址* @param code 授权码* @param state 订单信息* @return*/
@ApiOperation("微信授权码回调")
@GetMapping("/wx-oauth-code-return")
public String wxOAuth2CodeReturn(@RequestParam String code, @RequestParam String state) {//获取openid//重定向到支付确认页面
}

2、在TransactionService中定义获取openid方法

/*** 申请openid* @param code 授权码* @param appId 闪聚平台的应用id,为了获取该应用的微信支付渠道参数* @return*/
public String getWXOAuthOpenId(String code, String appId);
接口实现

1、添加RestTemplate配置

使用RestTemplate发起http请求,在shanjupay-transaction-service工程的pom.xml中添加依赖:

<!--okhttp3-->
<dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId>
</dependency>

2、添加获取openid地址配置

在nacos中向交易服务添加获取openid地址配置,如下:

weixin:oauth2Token: "https://api.weixin.qq.com/sns/oauth2/access_token"

3、在交易服务的TransactionServiceImpl中实现getWXOAuthOpenId接口实现

/*** 获取微信openid* @param code  授权码* @param appId 闪聚平台的应用id,为了获取该应用的微信支付渠道参数* @return*/
@Override
public String getWXOAuthOpenId(String code, String appId) {//获取微信支付渠道参数,根据应用、服务类型、支付渠道查询支付渠道参数//String appId,String platformChannel,String payChannelPayChannelParamDTO payChannelParamDTO = payChannelService.queryParamByAppPlatformAndPayChannel(appId, "shanju_c2b", "WX_JSAPI");String param = payChannelParamDTO.getParam();//微信支付渠道参数WXConfigParam wxConfigParam = JSON.parseObject(param, WXConfigParam.class);//https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_codeString url = String.format("%s?appid=%s&secret=%s&code=%s&grant_type=authorization_code",oauth2Token, wxConfigParam.getAppId(), wxConfigParam.getAppSecret(), code);//申请openid,请求urlRestTemplate restTemplate = new RestTemplate();ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.GET, null, String.class);//申请openid接口响应的内容,其中包括了openidString body = exchange.getBody();log.info("申请openid响应的内容:{}", body);//获取openidString openid = JSON.parseObject(body).getString("openid");return openid;
}

4、在PayController中实现微信授权码回调接口实现

/*** 授权码回调,申请获取授权码,微信将授权码请求到此地址* @param code 授权码* @param state 订单信息* @return*/
@ApiOperation("微信授权码回调")
@GetMapping("/wx-oauth-code-return")
public String wxOAuth2CodeReturn(@RequestParam String code, @RequestParam String state) {//获取openid//重定向到支付确认页面//将之前state中保存的订单信息读取出来String jsonString = EncryptUtil.decodeUTF8StringBase64(state);PayOrderDTO payOrderDTO = JSON.parseObject(jsonString, PayOrderDTO.class);//闪聚平台的应用idString appId = payOrderDTO.getAppId();//接收到code授权码,申请openidString openId = transactionService.getWXOAuthOpenId(code, appId);//将对象的属性和值组成一个url的key/value串String params = null;try {params = ParseURLPairUtil.parseURLPair(payOrderDTO);//转发到支付确认页面String url = String.format("forward:/pay-page?openId=%s&%s", openId, params);return url;} catch (Exception e) {e.printStackTrace();return "forward:/pay-page-error";}
}
测试

1、生成门店c扫b的二维码

2、打开内网穿透工具

3、打开模拟器,使用微信扫码

4、观察程序输出日志,确认openid是否生成成功,支付确认页面是否正常打开

立即支付

交互流程

点击立即支付调用第三方支付系统的下单接口,微信客户端扫码进入确认页面,点击立即支付则由渠道代理服务调用微信支付的下单接口,具体的流程如下:

1、微信客户端扫码进入确认页面,点击立即支付请求交易服务微信下单接口

2、交易服务通过支付渠道代理服务调用微信下单接口

3、调用微信下单接口成功,返回H5网页

4、在H5网页调起微信客户端支付。
在这里插入图片描述

支付渠道代理服务微信下单
接口定义

1、 微信支付下单接口参数

请求参数如下,主要关注必填项目:

红色:支付渠道参数配置的内容

蓝色:微信sdk自动配置

绿色:程序设置
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
响应参数:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2、 支付渠道代理服务下单接口定义

1)接口描述:

调用微信jsapi下单接口
2)接口定义

在PayChannelAgentService中定义接口:

/*** 微信下单接口* @param wxConfigParam 微信支付渠道参数* @param weChatBean 订单业务数据* @return h5网页的数据*/
public Map<String, String> createPayOrderByWeChatJSAPI(WXConfigParam wxConfigParam, WeChatBean weChatBean);
接口实现

将资料–>代码下的WXSDKConfig.java工具类拷贝至支付渠道代理服务。

1、在shanjupay-payment-agent-service工程的pom.xml引入依赖:

<!--微信支付SDK-->
<dependency><groupId>com.github.tedzhdz</groupId><artifactId>wxpay-sdk</artifactId><version>3.0.10</version>
</dependency>
<dependency><groupId>com.github.binarywang</groupId><artifactId>weixin-java-pay</artifactId><version>3.4.0</version>
</dependency>

2、在PayChannelAgentService中实现createPayOrderByWeChatJSAPI方法

/*** 微信下单接口* @param wxConfigParam 微信支付渠道参数* @param weChatBean    订单业务数据* @return h5网页的数据*/
@Override
public Map<String, String> createPayOrderByWeChatJSAPI(WXConfigParam wxConfigParam, WeChatBean weChatBean) {WXSDKConfig config = new WXSDKConfig(wxConfigParam);//通过实际支付参数匹配Map<String, String> jsapiPayParam = null;try {//创建sdk客户端WXPay wxPay = new WXPay(config);//按照微信统一下单接口要求构造请求参数Map<String, String> requestParam = new HashMap<>();requestParam.put("out_trade_no", weChatBean.getOutTradeNo());//订单号requestParam.put("body", weChatBean.getBody());//订单描述requestParam.put("fee_type", "CNY");//人民币requestParam.put("total_fee", String.valueOf(weChatBean.getTotalFee())); //金额requestParam.put("spbill_create_ip", weChatBean.getSpbillCreateIp());//客户端iprequestParam.put("notify_url", weChatBean.getNotifyUrl());//微信异步通知支付结果接口,暂时不用requestParam.put("trade_type", "JSAPI");//从请求中获取openidString openid = weChatBean.getOpenId();requestParam.put("openid", openid);//调用统一下单接口Map<String, String> resp = wxPay.unifiedOrder(requestParam);//=====向mq写入订单查询的消息=====PaymentResponseDTO paymentResponseDTO = new PaymentResponseDTO();//订单号paymentResponseDTO.setOutTradeNo(weChatBean.getOutTradeNo());//支付渠道参数paymentResponseDTO.setContent(wxConfigParam);//msgpaymentResponseDTO.setMsg("WX_JSAPI");payProducer.payOrderNotice(paymentResponseDTO);//准备h5网页需要的数据jsapiPayParam = new HashMap<>();jsapiPayParam.put("appId", wxConfigParam.getAppId());jsapiPayParam.put("timeStamp", System.currentTimeMillis() / 1000 + "");jsapiPayParam.put("nonceStr", UUID.randomUUID().toString());//随机字符串jsapiPayParam.put("package", "prepay_id=" + resp.get("prepay_id"));jsapiPayParam.put("signType", "HMAC-SHA256");//将h5网页响应给前端jsapiPayParam.put("paySign", WXPayUtil.generateSignature(jsapiPayParam, wxConfigParam.getKey(), WXPayConstants.SignType.HMACSHA256));log.info("微信JSAPI支付响应内容:" + jsapiPayParam);return jsapiPayParam;} catch (Exception e) {e.printStackTrace();throw new BusinessException(CommonErrorCode.E_400001);}
}
交易服务微信下单

交易服务微信下单是提供给支付入口请求的微信付款的接口,当用户用微信客户端扫描二维码进入确认支付页面,点击确认支付即将请求此接口。

H5页面

按照微信官方例子编写调起微信客户端支付的H5页面,从资料文件夹拷贝“wxpay.html”到交易服务下
在这里插入图片描述

接口定义

1、接口描述

1)接收前端支付请求

2)保存订单信息到闪聚支付平台

3)调用支付渠道代理服务请求微信下单接口

2、接口定义

1、在TransactionService中编写submitOrderByWechat接口。

/*** 1、保存订单到闪聚平台,2、调用支付渠道代理服务调用微信的接口* @param payOrderDTO* @return h5页面所需要的数据*/
Map<String, String> submitOrderByWechat(PayOrderDTO payOrderDTO) throws BusinessException;

2、在PayController中定义接口如下:

//微信下单 /wxjspay
@ApiOperation("微信门店下单付款")
@PostMapping("/wxjspay")
public ModelAndView createWXOrderForStore(OrderConfirmVO orderConfirmVO, HttpServletRequest request) {if (StringUtils.isBlank(orderConfirmVO.getOpenId())) {throw new BusinessException(CommonErrorCode.E_300002);}PayOrderDTO payOrderDTO = PayOrderConvert.INSTANCE.vo2dto(orderConfirmVO);//应用idString appId = payOrderDTO.getAppId();AppDTO app = appService.getAppById(appId);//商户idpayOrderDTO.setMerchantId(app.getMerchantId());//客户端ippayOrderDTO.setClientIp(IPUtil.getIpAddr(request));//将前端输入的元转成分payOrderDTO.setTotalAmount(Integer.parseInt(AmountUtil.changeY2F(orderConfirmVO.getTotalAmount().toString())));//调用微信下单接口 submitOrderByWechatMap<String, String> model = transactionService.submitOrderByWechat(payOrderDTO);log.info("/wxjspay 微信门店下单接口响应内容:{}",model);return new ModelAndView("wxpay", model);
}
接口实现

本接口实现两部分内容:

1)保存订单到闪聚支付数据库

2)调用支付渠道代理服务请求微信下单接口

1、保存订单

实现方法同支付宝下单,需要注意订单信息的支付渠道标识为WX_JSAPI:

/*** 微信确认支付* 1、保存订单到闪聚平台,* 2、调用支付渠道代理服务调用微信的接口* @param payOrderDTO* @return h5页面所需要的数据*/
@Override
public Map<String, String> submitOrderByWechat(PayOrderDTO payOrderDTO) throws BusinessException {//微信openidString openId = payOrderDTO.getOpenId();//支付渠道payOrderDTO.setChannel("WX_JSAPI");//保存订单到闪聚平台数据库PayOrderDTO save = save(payOrderDTO);//调用支付渠道代理服务,调用微信下单接口return weChatJsapi(openId,save.getTradeNo());
}

2、请求支付渠道代理服务进行微信下单

//微信jsapi 调用支付渠道代理
private Map<String, String> weChatJsapi(String openId, String tradeNo) {//根据订单号查询订单详情PayOrderDTO payOrderDTO = queryPayOrder(tradeNo);if (payOrderDTO == null) {throw new BusinessException(CommonErrorCode.E_400002);}//构造微信订单参数实体WeChatBean weChatBean = new WeChatBean();weChatBean.setOpenId(openId);//微信openidweChatBean.setOutTradeNo(payOrderDTO.getTradeNo());//闪聚平台的订单号weChatBean.setTotalFee(payOrderDTO.getTotalAmount());//金额(分)weChatBean.setSpbillCreateIp(payOrderDTO.getClientIp());//客户ipweChatBean.setBody(payOrderDTO.getBody());//订单描述weChatBean.setNotifyUrl("none");//异步接收微信通知支付结果的地址(暂时不用)String appId = payOrderDTO.getAppId();//根据应用、服务类型、支付渠道查询支付渠道参数,从数据库查询//String appId,String platformChannel,String payChannelPayChannelParamDTO payChannelParamDTO = payChannelService.queryParamByAppPlatformAndPayChannel(appId, "shanju_c2b", "WX_JSAPI");String paramJson = payChannelParamDTO.getParam();WXConfigParam wxConfigParam = JSON.parseObject(paramJson, WXConfigParam.class);//WXConfigParam wxConfigParam, WeChatBean weChatBeanMap<String, String> payOrderByWeChatJSAPI = payChannelAgentService.createPayOrderByWeChatJSAPI(wxConfigParam, weChatBean);return payOrderByWeChatJSAPI;
}
接口测试

1、生成门店c扫b的二维码

2、打开模拟器,使用微信扫码,进入支付确认页面

3、输入金额,点击立即支付

4、观察控制台日志,最终订单写入闪聚平台数据库

5、调起微信支付客户端,输入密码支付成功

获取支付结果

微信支付结果查询接口

根据获取支付结果的技术方案,接入微信需要请求微信查询支付结果,接口参数如下:

请求参数:
在这里插入图片描述
响应参数:
在这里插入图片描述
在这里插入图片描述
以下字段在return_code 、result_code、trade_state都为SUCCESS时有返回 ,如trade_state不为 SUCCESS,则只返回out_trade_no(必传)和attach(选传)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
支付结果:

  • SUCCESS—支付成功
  • REFUND—转入退款
  • NOTPAY—未支付
  • CLOSED—已关闭
  • REVOKED—已撤销(付款码支付)
  • USERPAYING–用户支付中(付款码支付)
  • PAYERROR–支付失败(其他原因,如银行返回失败)
支付渠道代理接口定义
接口定义

接口描述:

1)使用微信SDK发起支付结果查询请求 2)返回查询结果

在PayChannelAgentService定义如下接口:

/*** 查询微信订单状态* @param wxConfigParam 支付渠道参数* @param outTradeNo 闪聚平台的订单号* @return* @throws BusinessException*/
public PaymentResponseDTO queryPayOrderByWeChat(WXConfigParam wxConfigParam, String outTradeNo) throws BusinessException;
接口实现
/*** 查询微信订单状态,查询微信支付结果* @param wxConfigParam 支付渠道参数* @param outTradeNo    闪聚平台的订单号* @return* @throws BusinessException*/
@Override
public PaymentResponseDTO queryPayOrderByWeChat(WXConfigParam wxConfigParam, String outTradeNo) throws BusinessException {WXSDKConfig config = new WXSDKConfig(wxConfigParam);//通过实际支付参数匹配Map<String, String> result = null;try {//创建sdk客户端WXPay wxPay = new WXPay(config);Map<String, String> map = new HashMap<>();map.put("out_trade_no", outTradeNo);//闪聚平台的订单号//调用微信的订单查询接口result = wxPay.orderQuery(map);} catch (Exception e) {log.warn(e.getMessage(), e);return PaymentResponseDTO.fail("调用微信订单查询接口失败", outTradeNo, TradeStatus.UNKNOWN);}String return_code = result.get("return_code");String return_msg = result.get("return_msg");String result_code = result.get("result_code");String trade_state = result.get("trade_state");//订单状态String transaction_id = result.get("transaction_id");//微信订单号if ("SUCCESS".equals(return_code) && "SUCCESS".equals(result_code)) {if ("SUCCESS".equals(trade_state)) {  //支付成功return PaymentResponseDTO.success(transaction_id, outTradeNo, TradeStatus.SUCCESS, return_msg);} else if ("CLOSED".equals(trade_state)) {//交易关闭return PaymentResponseDTO.success(transaction_id, outTradeNo, TradeStatus.REVOKED, return_msg);} else if ("USERPAYING".equals(trade_state)) {//支付中return PaymentResponseDTO.success(transaction_id, outTradeNo, TradeStatus.USERPAYING, return_msg);} else if ("PAYERROR".equals(trade_state)) {//支付失败return PaymentResponseDTO.success(transaction_id, outTradeNo, TradeStatus.FAILED, return_msg);}}return PaymentResponseDTO.success("不可识别的微信订单状态", transaction_id, outTradeNo, TradeStatus.UNKNOWN);
}
单元测试

在shanjupay-payment-agent-service工程的测试类中进行测试

@Test
public void testQueryPayOrderByWeChat() {String appID = "wxd2bf2dba2e86a8c7";String mchID = "1502570431";String appSecret = "cec1a9185ad435abe1bced4b93f7ef2e";String key = "95fe355daca50f1ae82f0865c2ce87c8";WXConfigParam wxConfigParam = new WXConfigParam();wxConfigParam.setKey(key);wxConfigParam.setAppSecret(appSecret);wxConfigParam.setAppId(appID);wxConfigParam.setMchId(mchID);//WXConfigParam wxConfigParam,String outTradeNoPaymentResponseDTO paymentResponseDTO = payChannelAgentService.queryPayOrderByWeChat(wxConfigParam, "SJ1218090459880816640");System.out.println(paymentResponseDTO);
}
支付查询
发送支付结果查询消息

支付渠道代理服务完成微信下单接口的调用即向MQ发送支付结果查询消息

修改支付渠道代理服务的createPayOrderByWeChatJSAPI方法:
在这里插入图片描述

消费支付结果查询消息

修改支付渠道代理服务的PayConsumer

if ("ALIPAY_WAP".equals(paymentResponseDTO.getMsg())) {//调用支付宝订单状态查询接口,查询支付宝支付结果//AliConfigParam aliConfigParam,String outTradeNoresponseDTO = payChannelAgentService.queryPayOrderByAli(aliConfigParam, outTradeNo);
} else if ("WX_JSAPI".equals(paymentResponseDTO.getMsg())) {//调用微信的接口去查询订单状态,查询微信支付结果WXConfigParam wxConfigParam = JSON.parseObject(params, WXConfigParam.class);responseDTO = payChannelAgentService.queryPayOrderByWeChat(wxConfigParam, outTradeNo);
}
//当没有获取到订单结果,抛出异常,再次重试消费,返回查询获得的支付状态
if (responseDTO == null || TradeStatus.UNKNOWN.equals(responseDTO.getTradeState()) || TradeStatus.USERPAYING.equals(responseDTO.getTradeState())) {//在支付状态未知或支付中,抛出异常会重新消息此消息//如果重试的次数达到一次数量,不要再重试消费,将消息记录到数据库,由单独的程序或人工进行处理log.info("支付代理‐‐‐支付状态未知,等待重试");throw new RuntimeException("支付状态未知,等待重试");
}

在这里插入图片描述

测试

1、生成门店c扫b的二维码

2、打开模拟器,使用微信扫码,进入支付确认页面

3、输入金额,点击立即支付

4、观察控制台日志,最终订单写入闪聚平台数据库

5、调起微信支付客户端,输入密码支付成功

观察控制台日志,支付结果是否发送至交易服务。

数据库订单状态是否正常更新。

代码仓库

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 232. 微服务架构之zk集群部署

    文章目录1. 微服务1.1 介绍1.2 Dubbo微服务框架2. Devops流水线2.1 Jenkins老头2.2 Maven工具3. 本次实战架构4. 部署zookeeper4.1 安装JDK 1.84.2 安装ZK[11,12,21]4.3 配置DNS解析4.4 启动zk 1. 微服务 1.1 介绍 微服务 (Microservices) 是一种软件架构风格,它是以专注于单一…...

    2024/4/18 23:34:19
  2. 专题分纲目录 高效记忆/形象记忆

    专题分纲目录 均为索引,本纲为:高效记忆/形象记忆高效记忆/形象记忆(01) 记忆原理高效记忆/形象记忆(02)连锁奇象法高效记忆/形象记忆(03)故事奇象法高效记忆/形象记忆(04)数字编码记忆高效记忆/形象记忆(05)110数字编码表 0-9高效记忆/形象记忆(06)110数字编码表…...

    2024/5/1 12:20:14
  3. QT学习笔记----day04

    若看本节有困难,请先看前面的内容,如下: day01 day02 day03 七、实例七:布局管理器 最终要实现如下界面效果: 姓名:可填写 性别:可选择,互斥(单选按钮) 身高:可填写,可以手动上加或者下减 (浮点型的分量框) 生日:可选择(有日期选择框) 学历:可选择,下拉列表…...

    2024/4/19 4:21:04
  4. Python项目实战-Tensorflow2.0实现泰坦尼克生存预测

    目录一、数据集下载地址二、探索性因子分析(EDA)三、特征工程四、构建Dataset与Modelfit和自定义estimator使用预定义estimator的使用一、数据集下载地址# https:storage.googleapis.com/tf-datasets/titanic/train.csv # https:storage.googleapis.com/tf-datasets/titanic/…...

    2024/4/29 6:19:27
  5. 查看连接过并已经记住密码的wifi密码

    cmd打开,输入: Netsh wlan show profile name=”热点名字” key=clear 其中,“热点名字”就填写你想要查看密码的WiFi热点的名称,例如笔者想要查看“staff”这个热点的密码,则输入: Netsh wlan show profile name=”staff” key=clear 回车键后出现“安全设置”:...

    2024/4/22 0:09:43
  6. 2020-08-07 apache安装之源码包安装

    #今天本想安装一个用源码包安装一个apache,但是总是下不全包 #不过还是被我给找到了,在这里分享给大家 链接:https://pan.baidu.com/s/1OgkLq3au57ysJTP6hLhn7Q 提取码:qwer...

    2024/4/28 11:18:38
  7. docker安装常用的容器

    安装sentinel 拉取镜像: docker pull bladex/sentinel-dashboard 启动容器 docker run --name sentinel -d -p 8858:8858 bladex/sentinel-dashboard 启动参数解释 --name 指定容器启动的名称 -d 设置容器后台运行 -p 开放容器里面的端口到外部端口安装postgres 拉取镜像 …...

    2024/4/15 20:47:03
  8. 弹出软键盘带有输入框(像聊天时的手机软键盘上面有附带的框)

    弹出软键盘并弹出输入框,并获取焦点在此输入框里: 国际惯例,先上图:下面为步骤: Step 1:自定义带有输入框等等需要的样式的dialog public class SoftKeyboardInputDialog extends Dialog {private Activity activity;public SoftKeyboardInputDialog(@NonNull Context co…...

    2024/4/15 20:47:01
  9. 基于SpringBoot的车牌识别系统(附项目地址)

    介绍spring boot + maven 实现的车牌识别及训练系统 基于java语言的深度学习项目,在整个开源社区来说都相对较少;而基于java语言实现车牌识别EasyPR-Java项目,最后的更新已经是五年以前。 本人参考了EasyPR原版C++项目、以及fan-wenjie的EasyPR-Java项目;同时查阅了部分ope…...

    2024/4/15 20:47:00
  10. make idegen 报错

    make idegen Saved manifest to manifest.xml build/core/config.mk:601: *** Error: could not find jdk tools.jar at /usr/lib/jvm/java-11-openjdk-amd64/bin/../lib/tools.jar, please check if your JDK was installed correctly。 停止。其实是java的环境变量没搞对sour…...

    2024/4/15 20:47:00
  11. 搭建IQ Option二元期权、ExperOption二元期权MT4博易大师信管家

    搭建IQ Option二元期权、ExperOption二元期权MT4博易大师信管家 ★稳定高并发的系统C#语言搭建; ★行情数据实时流畅不卡不顿; ★365天贴心服务保驾护航; 搭建:IQ Option二元期权、ExperOption二元期权、MT4、博易大师、信管家。 2017年8月推出IQ Option利用250人的开发团队…...

    2024/4/15 20:46:57
  12. JavaSE练习实操——库存管理系统(一)

    JavaSE练习实操——库存管理系统(一) 需求分析: 用例名称 :用户管理 描述 :用户信息的增删改查 执行者 : admin(系统管理员) 使用数据库k_user界面如图1、创建view包,编写菜单 主要是登陆初始界面以及对增删改查方法的调用 2、service包,用户服务 3、model…...

    2024/4/15 20:46:56
  13. iOS 13 statusBar错误

    iphone xr切换场景提示报错statusBar错误,Use the statusBarManager object on the window scene instead解决方案if (@available(iOS 13.0, *)) {// iOS 13 弃用keyWindow属性 从所有windowl数组中取UIView *statusBar = [[UIView alloc]initWithFrame:[UIApplication shar…...

    2024/4/21 18:30:13
  14. 强化学习 蒙特卡洛模拟 一种model-free的强化学习方法

    一、蒙特卡洛方法动态规划方法是建立在模型已知的情况下,但是往往大多数情况下模型是未知的,实际应用中我们不可能完全了解一个环境的所有知识,比如说得出它的状态转移矩阵。这个时候蒙特卡洛算法就派上用场了,它只需要从经验(experience)中去学习,这个经验包括样本序列…...

    2024/4/15 20:46:54
  15. 嵌入式-ARM-学习总结(1):初识ARM

    嵌入式-ARM-学习总结(1):初识ARMARM的特点冯诺依曼结构与哈佛结构内存与外存S5PV210的启动过程ARM的7种工作模式ARM汇编指令集8种寻址方式 ARM的特点 ARM采用RISC架构,CPU本身不能直接读取内存(需要借助内部寄存器对外部内容进行读取。当要改变外部内存中的数据时,首先需…...

    2024/4/18 17:30:25
  16. 洛谷 P2292 AC自动机 + 状态压缩

    题目问,给定 nnn 个模式串,问你主串的 “可理解的” 最长前缀长度。“可理解的” 意思指可以被分成若干个模式串。首先我们知道AC自动机,failfailfail 指针跳转的是相同后缀,在跳转的过程中会依据长度从大到小经过相同的后缀。比如 abcd−−>bcd−−>dabcd -- >bc…...

    2024/4/15 20:46:52
  17. ECMAScript 6 基础

    JavaScript 三大组成部分ECMAScript DOM BOMECMAScript 发展历史 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Language_Resources ECMAScript 包含内容:JS 中的数据类型及相关操作,流程控制,运算符及相关运算……ECMAScript 6let和constlet和var的区别let 允…...

    2024/4/15 13:25:06
  18. popwindow遮挡虚拟按键

    popwindow遮挡虚拟按键 //防止被底部虚拟键挡住 pop.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);...

    2024/4/15 13:25:04
  19. 只要上下滚动条 而不要左右滚动条

    在样式里面添加style="overflow:auto;overflow-x: hidden"解决问题!这样左右滚动条就没有了,效果:...

    2024/4/30 11:09:19
  20. 【C语言&数据结构】动态数组

    一、动态数组In addition to dynamically allocating single values, we can also dynamically allocate arrays of variables. Unlike a fixed array, where the array size must be fixed at compile time, dynamically allocating an array allows us to choose an array le…...

    2024/4/15 13:25:02

最新文章

  1. 今日arXiv最热联邦学习论文:通信成本降低94%,中科院计算所发布个性化联邦学习方法

    引言&#xff1a;你的隐私&#xff0c;联邦来守护&#xff01; 想象一下&#xff0c;未来你的手机就像一位贴心的私人助理&#xff0c;能够洞察你的喜好、日程&#xff0c;甚至预测你的情绪。听起来很棒&#xff0c;但你可能会担心隐私泄露的问题。别担心&#xff0c;最近一种…...

    2024/5/1 23:43:19
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. Spring Boot 经典面试题(二)

    1.Spring Boot如何加载外部配置文件&#xff1f; Spring Boot 设计了一种非常灵活的方式来加载外部配置文件&#xff0c;允许你轻松地管理和调整应用程序的配置而无需改动代码。这些配置文件可以是 properties 文件、YAML 文件、环境变量或命令行参数。Spring Boot 在启动时会…...

    2024/4/30 7:44:49
  4. Jmeter02-1:参数化组件CVS

    目录 1、Jmeter组件&#xff1a;参数化概述 1.1 是什么&#xff1f; 1.2 为什么&#xff1f; 1.3 怎么用&#xff1f; 2、Jmeter组件&#xff1a;参数化实现之CSV Data Set Config(重点中重点) 2.1 是什么&#xff1f; 2.2 为什么&#xff1f; 2.3 怎么用&#xff1f; …...

    2024/5/1 13:53:24
  5. SQL 第一章 (准备工作)

    目录 1. 准备工作1.1 导入练习数据1.2 前置知识 1. 准备工作 为了更好的学习&#xff0c;对于本地没用安装MySQL和Navicat的同学&#xff0c;请百度查询相关安装教程。当然&#xff0c;数据库软件也可以用其他的替代Navicat。 我将持续更新SQL的知识&#xff0c;你将会学到各种…...

    2024/4/30 3:40:37
  6. 416. 分割等和子集问题(动态规划)

    题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义&#xff1a;dp[i][j]表示当背包容量为j&#xff0c;用前i个物品是否正好可以将背包填满&#xff…...

    2024/5/1 10:25:26
  7. 【Java】ExcelWriter自适应宽度工具类(支持中文)

    工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...

    2024/5/1 13:20:04
  8. Spring cloud负载均衡@LoadBalanced LoadBalancerClient

    LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon&#xff0c;直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件&#xff0c;我们讨论Spring负载均衡以Spring Cloud2020之后版本为主&#xff0c;学习Spring Cloud LoadBalance&#xff0c;暂不讨论Ribbon…...

    2024/5/1 21:18:12
  9. TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案

    一、背景需求分析 在工业产业园、化工园或生产制造园区中&#xff0c;周界防范意义重大&#xff0c;对园区的安全起到重要的作用。常规的安防方式是采用人员巡查&#xff0c;人力投入成本大而且效率低。周界一旦被破坏或入侵&#xff0c;会影响园区人员和资产安全&#xff0c;…...

    2024/5/1 4:07:45
  10. VB.net WebBrowser网页元素抓取分析方法

    在用WebBrowser编程实现网页操作自动化时&#xff0c;常要分析网页Html&#xff0c;例如网页在加载数据时&#xff0c;常会显示“系统处理中&#xff0c;请稍候..”&#xff0c;我们需要在数据加载完成后才能继续下一步操作&#xff0c;如何抓取这个信息的网页html元素变化&…...

    2024/4/30 23:32:22
  11. 【Objective-C】Objective-C汇总

    方法定义 参考&#xff1a;https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...

    2024/4/30 23:16:16
  12. 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】

    &#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】&#x1f30f;题目描述&#x1f30f;输入格…...

    2024/5/1 6:35:25
  13. 【ES6.0】- 扩展运算符(...)

    【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数&#xff0…...

    2024/5/1 11:24:00
  14. 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?

    文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕&#xff0c;各大品牌纷纷晒出优异的成绩单&#xff0c;摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称&#xff0c;在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁&#xff0c;多个平台数据都表现出极度异常…...

    2024/5/1 4:35:02
  15. Go语言常用命令详解(二)

    文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令&#xff0c;这些命令可以帮助您在Go开发中进行编译、测试、运行和…...

    2024/5/1 20:22:59
  16. 用欧拉路径判断图同构推出reverse合法性:1116T4

    http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b&#xff0c;我们在 a i a_i ai​ 和 a i 1 a_{i1} ai1​ 之间连边&#xff0c; b b b 同理&#xff0c;则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然&#xff0…...

    2024/4/30 22:14:26
  17. 【NGINX--1】基础知识

    1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息&#xff0c;并安装一些有助于配置官方 NGINX 软件包仓库的软件包&#xff1a; apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...

    2024/5/1 6:34:45
  18. Hive默认分割符、存储格式与数据压缩

    目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限&#xff08;ROW FORMAT&#xff09;配置标准HQL为&#xff1a; ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...

    2024/4/30 22:57:18
  19. 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法

    文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中&#xff0c;传感器和控制器产生大量周…...

    2024/4/30 20:39:53
  20. --max-old-space-size=8192报错

    vue项目运行时&#xff0c;如果经常运行慢&#xff0c;崩溃停止服务&#xff0c;报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中&#xff0c;通过JavaScript使用内存时只能使用部分内存&#xff08;64位系统&…...

    2024/5/1 4:45:02
  21. 基于深度学习的恶意软件检测

    恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞&#xff0c;例如可以被劫持的合法软件&#xff08;例如浏览器或 Web 应用程序插件&#xff09;中的错误。 恶意软件渗透可能会造成灾难性的后果&#xff0c;包括数据被盗、勒索或网…...

    2024/5/1 8:32:56
  22. JS原型对象prototype

    让我简单的为大家介绍一下原型对象prototype吧&#xff01; 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定&#xff0c;每一个构造函数都有一个 prototype 属性&#xff0c;指向另一个对象&#xff0c;所以我们也称为原型对象…...

    2024/5/1 14:33:22
  23. C++中只能有一个实例的单例类

    C中只能有一个实例的单例类 前面讨论的 President 类很不错&#xff0c;但存在一个缺陷&#xff1a;无法禁止通过实例化多个对象来创建多名总统&#xff1a; President One, Two, Three; 由于复制构造函数是私有的&#xff0c;其中每个对象都是不可复制的&#xff0c;但您的目…...

    2024/5/1 11:51:23
  24. python django 小程序图书借阅源码

    开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索图书&#xff0c;轮播图&#xff0…...

    2024/5/1 5:23:20
  25. 电子学会C/C++编程等级考试2022年03月(一级)真题解析

    C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...

    2024/5/1 20:56:20
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57