目录
1.9.22 提交订单的问题
1)、订单号显示、应付金额回显
ps:之前代码忘记创建订单之后相应数据,这个方法应该补充以下两项。
- 出现问题:orderSn长度过长
- 解决方案:数据库的表中的对应字段长度增大
2)、提交订单消息回显
- confirm.html
3)、为了确保锁库存失败后,订单和订单项也能回滚,需要抛出异常
- 修改 NoStockException,将库存下的这个异常类直接放到公共服务下。并添加一个构造器。
- orderServiceImpl
1.9.23 创建库存上锁、解锁 业务交换机&队列
1)、流程梳理
2)、解锁库存实现
①库存服务导入RabbitMQ的依赖
<!-- RabbitMQ的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependen
②库存服务 RabbitMQ的配置
spring.rabbitmq.host=192.168.56.10
spring.rabbitmq.virtual-host=/
③ 库存服务 配置RabbitMQ的序列化机制
@Configuration
public class MyRabbitConfig {
/**
* 使用JSON序列化机制,进行消息转换
* @return
*/
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
④ 库存服务 开启RabbitMQ
⑤ 按照下图创建交换机、队列、绑定关系
统一使用 topic交换机:因为交换机需要绑定多个队列,不同的路由键,且具有模糊匹配功能。
库存服务
package com.atguigu.gulimall.ware.config;
@Configuration
public class MyRabbitConfig {
/**
* 使用JSON序列化机制,进行消息转换
* @return
*/
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
/**
出现问题: 并没创建交换机、队列、绑定关系
出现问题的原因:只有当第一次连接上RabbitMQ时,发现没有这些东西才会创建
解决方案:监听队列
* @param message
*/
@RabbitListener(queues = "stock.release.stock.queue")
public void handle(Message message){
}
@Bean
public Exchange stockEventExchange(){
//String name, boolean durable, boolean autoDelete, Map<String, Object> arguments
return new TopicExchange("stock-event-exchange",true,false);
}
@Bean
public Queue stockReleaseStockQueue(){
//String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
return new Queue("stock.release.stock.queue",true,false,false,null);
}
/**
* 延时队列
* @return
*/
@Bean
public Queue stockDelayQueue() {
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange","stock-event-exchange");
arguments.put("x-dead-letter-routing-key","stock.release");
arguments.put("x-message-ttl",120000);
return new Queue("stock.delay.queue",true,false,false,arguments);
}
@Bean
public Binding stockReleaseBinding(){
//String destination, DestinationType destinationType, String exchange, String routingKey,
// Map<String, Object> arguments
return new Binding("stock.release.stock.queue",
Binding.DestinationType.QUEUE,
"stock-event-exchange",
"stock.release.#",
null);
}
@Bean
public Binding stockLockedBinding(){
//String destination, DestinationType destinationType, String exchange, String routingKey,
// Map<String, Object> arguments
return new Binding("stock.delay.queue",
Binding.DestinationType.QUEUE,
"stock-event-exchange",
"stock.locked",
null);
}
}
3)、测试及问题解决
进行启动测试-------出现问题: 并没创建交换机、队列、绑定关系
出现问题的原因:只有当第一次连接上RabbitMQ时,发现没有这些东西才会创建
解决方案:监听队列
注意:交换机、队列、绑定关系创建成功后,将上述代码注释
1.9.24 监听库存解锁
库存解锁的两种场景:
①下单成功,订单过期没有支付被系统自动取消、被用户手动取消。都要解锁
②下单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚。之前锁定的库存就要自动解锁
1)、数据库新增字段
①更改数据库表 ---- 库存服务下的wms_ware_order_task_detail
- 添加两个字段(仓库id和商品锁定状态),方便库存回滚
- 实体类中需要修改的:
WareOrderTaskDetailEntity
加上 全参和无参构造器方便消息传播该实体类数据。
WareOrderTaskDetailDao.xml
中结果集映射修改
② 保存工作单详情方便回溯
- WareSkuServiceImpl中的
orderLockStock
方法中增加下面的代码
③ Common服务中创建To,方便MQ发送消息
package com.atguigu.common.to.mq;
@Data
public class StockLockedTo {
private Long id;//库存工作单的id
private Long detailId;//库存工作单详情id //接下来需要修改,这里只是暂时的
}
如果To仅仅保存这个两个数据的话,会存在一些问题,
当1号订单在1号仓库扣减1件商品成功,2号订单在2号仓库扣减2件商品成功,3号订单在3号仓库扣减3件商品失败时,库存工作单的数据将会回滚,
此时,数据库中将查不到1号和2号订单的库存工作单的数据,但是库存扣减是成功的,导致无法解锁库存
解决方案: 保存库存工作详情To
package com.atguigu.common.to.mq;
@Data
public class StockDetailTo {
private Long id;
/**
* sku_id
*/
private Long skuId;
/**
* sku_name
*/
private String skuName;
/**
* 购买个数
*/
private Integer skuNum;
/**
* 工作单id
*/
private Long taskId;
/**
* 仓库id
*/
private Long wareId;
/**
* 库存锁定状态
*/
private Integer lockStatus;
}
- 进行修改
④ WareSkuServiceImpl 中的orderLockStock ----- 向MQ发送库存锁定成功的消息
库存回滚解锁
1)库存锁定
在库存锁定时添加以下逻辑
- 由于可能订单回滚的情况,所以为了能够得到库存锁定的信息,在锁定时需要记录库存工作单,其中包括订单信息和锁定库存时的信息(仓库id,商品id,锁了几件…)
- 在锁定成功后,向延迟队列发消息,带上库存锁定的相关信息
逻辑:
遍历订单项,遍历每个订单项的每个库存,直到锁到库存
发消息后库存回滚也没关系,用id是查不到数据库的
锁库存的sql
这里编写了发送消息队列的逻辑,下面写接收消息队列后还原库存的逻辑。
1.9.25 库存解锁逻辑&库存自动解锁完成&测试库存自动解锁
- 解锁场景:
1.下单成功,库存锁定成功,接下来的业务如果调用失败导致订单回滚。之前锁定的库存就要自动解锁。
2.锁库存失败无需解锁
解决方案:通过查询订单的锁库存信息,如果有则仅仅说明库存锁定成功,还需判断是否有订单信息,如果有订单信息则判断订单状态,若订单状态已取消则解锁库存,反之:不能解锁库存,如果没有订单信息则需要解锁库存,如果没有锁库存信息则无需任何操作。
1.编写Vo,通过拷贝订单实体----> OrderEntity,用于接收订单信息
- 库存服务中
package com.atguigu.gulimall.ware.vo;
2. 远程服务编写,获取订单状态功能
订单服务下编写:OrderController
@GetMapping("/status/{orderSn}")
public R getOrderStatus(@PathVariable("orderSn") String orderSn){
OrderEntity orderEntity = orderService.getOrderByOrderSn(orderSn);
return R.ok().setData(orderEntity);
}
实现:
- OrderServiceImpl
@Override
public OrderEntity getOrderByOrderSn(String orderSn) {
OrderEntity order_sn = this.getOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderSn));
return order_sn;
}
库存服务下编写:调用远程服务 OrderFeignService
@FeignClient("gulimall-order")
public interface OrderFeignService {
@GetMapping("/order/order/status/{orderSn}")
R getOrderStatus(@PathVariable("orderSn") String orderSn);
}
3.监听事件
接收消息
- 延迟队列会将过期的消息路由至
"stock.release.stock.queue"
,通过监听该队列实现库存的解锁 - 为保证消息的可靠到达,我们使用手动确认消息的模式,在解锁成功后确认消息,若出现异常则重新归队
库存服务 编写 StockReleaseListener
当@RabbitListener标注在类上,需要搭配@RabbitHandler一起使用
@Service
@RabbitListener(queues = "stock.release.stock.queue")
public class StockReleaseListener {
@Autowired
WareSkuService wareSkuService;
@RabbitHandler
public void handleStockLockedRelease(StockLockedTo to, Message message, Channel channel) throws IOException {
System.out.println("收到解锁库存的消息...");
try {
wareSkuService.unlockStock(to);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}catch (Exception e){
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
}
}
}
WareSkuServiceImpl
库存解锁
-
如果工作单详情不为空,说明该库存锁定成功
- 查询最新的订单状态,
- 如果订单不存在,说明订单提交出现异常回滚,
- 如果订单存在(但订单处于已取消的状态),我们要对已锁定的库存进行解锁
-
如果工作单详情为空,说明库存未锁定,自然无需解锁
-
为保证幂等性,我们分别对订单的状态和工作单的状态都进行了判断,只有当订单过期且工作单显示当前库存处于锁定的状态时,才进行库存的解锁
@Autowired
WareSkuDao wareSkuDao;
@Autowired
WareOrderTaskService orderTaskService;
@Autowired
WareOrderTaskDetailService orderTaskDetailService;
@Autowired
RabbitTemplate rabbitTemplate;
@Autowired
OrderFeignService orderFeignService;
/**
* 1、库存自动解锁。
* 下订单成功,库存锁定成功,接下来的业务如果调用失败,导致订单回滚。之前锁定的库存就要自动解锁。
* 2、订单失败。
* 锁库存失败。
* <p>
* 只要解锁库存的消息失败。一定要告诉服务器解锁失败。
*
* @param to
*/
@Override
public void unlockStock(StockLockedTo to) {
StockDetailTo detail = to.getDetail();
Long detailId = detail.getId();
//解锁
//1、查询数据库关于这个订单的锁定库存信息。
//有:证明库存锁定成功了。
// 解锁:查询订单情况。
// 1、没有这个订单。必须解锁
// 2、有这个订单。不是解锁库存。
// 订单状态:已取消:解锁库存
// 没取消:不能解锁
//没有:库存锁定失败了,库存回滚了。这种情况无需解锁
WareOrderTaskDetailEntity byId = orderTaskDetailService.getById(detailId);
if (byId != null) {
//解锁
Long id = to.getId();
WareOrderTaskEntity taskEntity = orderTaskService.getById(id);
String orderSn = taskEntity.getOrderSn();//根据订单号查询订单的状态
R r = orderFeignService.getOrderStatus(orderSn);
if (r.getCode() == 0) {
//订单数据返回成功
OrderVo data = r.getData(new TypeReference<OrderVo>() {
});
if (data == null || data.getStatus() == 4) {
//订单不存在
//订单已经被取消了。才能解锁库存
// detailId
if (byId.getLockStatus() == 1){
//当前库存工作单详情,状态 是1 :已锁定但是未解锁才可以解锁
unLockStock(detail.getSkuId(), detail.getWareId(), detail.getSkuNum(), detailId);
}
}
} else {
//消息拒绝以后重新放到队里里面,让别人继续消费解锁。
throw new RuntimeException("远程服务失败");
}
} else {
//无需解锁
}
}
/**
* 解锁方法
*/
private void unLockStock(Long skuId, Long wareId, Integer num, Long taskDetailId) {
//库存解锁
wareSkuDao.unlockStock(skuId, wareId, num);
//更新库存工作单的状态
WareOrderTaskDetailEntity entity = new WareOrderTaskDetailEntity();
entity.setId(taskDetailId);
entity.setLockStatus(2);//变为已解锁
orderTaskDetailService.updateById(entity);
}
WareSkuDao.xml
SQL编写:
UPDATE `wms_ware_sku` SET stock_locked = stock_locked -1
WHERE sku_id = 1 AND ware_id = 2
<update id="unlockStock">
UPDATE `wms_ware_sku` SET stock_locked = stock_locked -#{num}
WHERE sku_id = #{skuId} AND ware_id = #{wareId}
</update>
4. 远程服务调用可能会出现失败,需要设置手动ACK,确保其它服务能消费此消息
库存服务下
#手动ACK设置
spring.rabbitmq.listener.simple.acknowledge-mode=manual
5.出现问题: 远程调用订单服务时被拦截器拦截
解决方案:请求路径适配放行
订单服务下的 拦截器。
1.9.26 定时关单完成
1.定时关单代码编写
①订单创建成功,给MQ发送关单消息
订单服务下的 OrderServiceImpl 的 submitOrder提交订单方法
② 监听事件,进行关单
订单服务下
package com.atguigu.gulimall.order.listener;
@Service
@RabbitListener(queues = "order.release.order.queue")
public class OrderCloseListener {
@Autowired
OrderService orderService;
/**
* 定时关单
* @param entity
* @param channel
* @param message
* @throws IOException
*/
@RabbitHandler
public void listener(OrderEntity entity, Channel channel, Message message) throws IOException {
System.out.println("收到过期的订单消息:准备关闭订单" + entity.getOrderSn());
try {
orderService.closeOrder(entity);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
//失败了重新放到队列中,让其他消费者能够消费
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
}
}
}
OrderServiceImpl
/**
* 关单操作
* @param entity
*/
@Override
public void closeOrder(OrderEntity entity) {
//查询当前这个订单的最新状态
OrderEntity orderEntity = this.getById(entity.getId());
if (orderEntity.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()){
//关单
OrderEntity update = new OrderEntity();
update.setId(entity.getId());
update.setStatus(OrderStatusEnum.CANCLED.getCode());
this.updateById(update);
}
}
订单释放和库存解锁逻辑: 当订单创建成功,1分钟之后,向MQ发送关单消息,2分钟后,向MQ发送解锁库存消息,关单操作完成之后,过了1分钟解锁库存操作。
存在问题:由于机器卡顿、消息延迟等导致关单消息延迟发送,解锁库存消息正常发送和监听,导致解锁库存消息被消费,当执行完关单操作后便无法再执行解锁库存操作,导致卡顿的订单永远无法解锁库存。
解决方案:采取主动补偿的策略。当关单操作正常完成之后,主动去发送解锁库存消息给MQ,监听解锁库存消息进行解锁。
③ 按上图创建绑定关系
订单服务MyMQConfig
④ common服务中,创建OrderTo(拷贝order实体)
⑤ 向MQ发送解锁库存消息
- OrderServiceImpl
package com.atguigu.gulimall.order.service.impl;
/**
* 关单操作
* @param entity
*/
@Override
public void closeOrder(OrderEntity entity) {
//查询当前这个订单的最新状态
OrderEntity orderEntity = this.getById(entity.getId());
if (orderEntity.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()){
//未付款状态,进行关单
OrderEntity update = new OrderEntity();
update.setId(entity.getId());
update.setStatus(OrderStatusEnum.CANCLED.getCode());
this.updateById(update);
OrderTo orderTo = new OrderTo();
BeanUtils.copyProperties(orderEntity,orderTo);
//发给MQ一个消息:解锁库存
rabbitTemplate.convertAndSend("order-event-exchange","order.release.other",orderTo);
}
}
⑥ 解锁库存操作
库存服务下
@RabbitHandler
public void handleOrderCloseRelease(OrderTo orderTo, Message message, Channel channel) throws IOException {
System.out.println("订单关闭准备解锁库存...");
try {
wareSkuService.unlockStock(orderTo);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}catch (Exception e){
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
}
}
//防止订单服务卡顿,导致订单状态消息一直改不了,库存消息优先到期。查订单状态新建状态,什么都不做就走了。
//导致卡顿的订单,永远不能解锁库存。
@Transactional
@Override
public void unlockStock(OrderTo orderTo) {
String orderSn = orderTo.getOrderSn();
//查一下最新库存的状态,防止重复解锁库存
WareOrderTaskEntity task = orderTaskService.getOrderTaskByOrderSn(orderSn);
Long id = task.getOrderId();
//按照工作单找到所有 没有解锁的库存,进行解锁
List<WareOrderTaskDetailEntity> entities = orderTaskDetailService.list(
new QueryWrapper<WareOrderTaskDetailEntity>()
.eq("task_id", id)
.eq("lock_status", 1));
// Long skuId, Long wareId, Integer num, Long taskDetailId
for (WareOrderTaskDetailEntity entity : entities) {
unLockStock(entity.getSkuId(),entity.getWareId(),entity.getSkuNum(),entity.getId());
}
}
WareOrderTaskServiceImpl
@Override
public WareOrderTaskEntity getOrderTaskByOrderSn(String orderSn) {
WareOrderTaskEntity one = this.getOne(new QueryWrapper<WareOrderTaskEntity>().eq("order_sn", orderSn));
return one;
}
1.9.27 消息丢失、积压、重复等解决方案
- 如何保证消息可靠性-消息丢失
1、消息丢失
消息发送出去,由于网络问题没有抵达服务器
做好容错方法(try-catch),发送消息可能会网络失败,失败后要有重试机制,可记录到数据库,采用定期扫描重发的方式
做好日志记录,每个消息状态是否都被服务器收到都应该记录
做好定期重发,如果消息没有发送成功,定期去数据库扫描未成功的消息进行重发
消息抵达Broker,Broker要将消息写入磁盘(持久化)才算成功。此时Broker尚未持久化完成,宕机。
publisher也必须加入确认回调机制,确认成功的消息,修改数据库消息状态。
自动ACK的状态下。消费者收到消息,但没来得及消息然后宕机
一定开启手动ACK,消费成功才移除,失败或者没来得及处理就noAck并重新入队
情况一: 消息发送出去但是由于网络原因未到达服务器,解决方案:采用try-catch将发送失败的消息持久化到数据库中,采用定期扫描重发的方式。
drop table if exists mq_message;
CREATE TABLE `mq_message` (
`message_id` CHAR(32) NOT NULL,
`content` TEXT,#json
`to_exchange` VARCHAR(255) DEFAULT NULL,
`routing_key` VARCHAR(255) DEFAULT NULL,
`class_type` VARCHAR(255) DEFAULT NULL,
`message_status` INT(1) DEFAULT '0' COMMENT '0-新建 1-已发送 2-错误抵达 3-已抵达',
`create_time` DATETIME DEFAULT NULL,
`update_time` DATETIME DEFAULT NULL,
PRIMARY KEY (`message_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4
情况二:消息抵达服务器的队列中才算完成消息的持久化,解决方案----->publish + consumer的两端的ack机制
情况三: 防止自动ack带来的缺陷,采用手动ack,解决方案上面都有这里不再细说
- 如何保证消息可靠性-消息重复
2、消息重复
- 消息消费成功,事务已经提交,ack时,机器宕机。导致没有ack成功,Broker的消息重新由unack变为ready,并发送给其他消费者
- 消息消费失败,由于重试机制,自动又将消息发送出去
- 成功消费,ack时宕机,消息由unack变为ready,Broker又重新发送
- 消费者的业务消费接口应该设计为幂等性的。比如扣库存有工作单的状态标志
- 使用防重表(redis/mysql),发送消息每一个都有业务的唯一标识,处理过就不用处理
- rabbitMQ的每一个消息都有redelivered字段,可以获取是否是被重新投递过来的,而不是第一次投递过来的
消息被成功消费,ack时宕机,消息由unack变成ready,Broker又重新发送。解决方案:将消费者的业务消费接口应该设计为幂等性的,比如扣库存有工作单的状态标志。
- 如何保证消息可靠性-消息积压
3、消息积压
- 消费者宕机积压
- 消费者消费能力不足积压
- 发送者发送流量太大
- 上线更多的消费者,进行正常消费
- 上线专门的队列消费服务,将消息先批量取出来,记录数据库,离线慢慢处理
消息积压即消费者的消费能力不够, 上线更多的消费者进行正常的消费。
1.9.28 支付服务
1)、支付宝沙箱代码
① 文档和资料
支付宝开放平台传送门:支付宝开放平台
网站支付DEMO传送门:手机网站支付 DEMO | 网页&移动应用
沙箱应用传送门:沙箱应用
密钥工具下载传送门:密钥工具
沙箱环境文档:沙箱环境文档
- 下载手机网站支付DEMO,使用eclipse导入
2)、RSA、加密加签、密钥等相关概念
对称加密:发送方和接收方用的是同一把密钥,存在问题:当某一方将密钥泄漏之后,发送的消息可以被截取获悉并且随意进行通信。
非对称加密:发送方和接收方使用的不是同一把密钥,发送方使用密钥A对明文进行加密,接收方使用密钥B对密文进行解密,然后接收方将回复的明文用密钥C进行加密,发送方使用密钥D进行解密。采用非对称加密的好处是:即使有密钥被泄漏也不能自由的通信。
密钥的公私性是相对于生成者而言的。发送方通过密钥A对明文进行加密,密钥A是只有发送方自己知道的,接收方想要解密密文,就需要拿到发送方公布出来的密钥B。
公钥:生成者发布的密钥可供大家使用的
私钥:生成者自己持有的密钥
签名:为了防止中途传输的数据被篡改和使用的方便,发送方采用私钥生成明文对应的签名,此过程被成为加签。接收方使用公钥去核验明文和签名是否对应,此过程被成为验签。
配置支付宝的沙箱环境:
沙箱环境配置查看传送门:登录 - 支付宝
接口加签方式共有两种:
①采用系统默认生成的支付宝的公钥、应用的私钥和公钥:
② 采用自定义密钥
传送门:密钥工具下载
将支付宝密钥工具生成的应用公钥复制进加签内容配置中,会自动生成支付宝的公钥
- 沙箱账号:用于测试环境中的商品支付
- 使用eclipse测试:
注意如果项目报红:因为老师给的 沙箱测试Demo 默认使用的 是 tomact7.0 ,所以这里需要将 tomact7.0移除掉,使用我们自己本机安装的 tomcat。
选择 tomcat 7.0移除 ,然后导入自己的 就行。这里我已经移除好了。
- 老师课件:
一、支付宝支付
1、进入“蚂蚁金服开放平台”
https://open.alipay.com/platform/home.htm
2、下载支付宝官方demo,进行配置和测试
文档地址
https://open.alipay.com/platform/home.htm 支付宝&蚂蚁金服开发者平台
https://docs.open.alipay.com/catalog 开发者文档
https://docs.open.alipay.com/270/106291/ 全部文档=>电脑网站支付文档;下载demo
3、配置使用沙箱进行测试
1、使用RSA 工具生成签名
2、下载沙箱版钱包
3、运行官方demo 进行测试
4、什么是公钥、私钥、加密、签名和验签?
1、公钥私钥
公钥和私钥是一个相对概念
它们的公私性是相对于生成者来说的。
一对密钥生成后,保存在生成者手里的就是私钥,
生成者发布出去大家用的就是公钥
2、加密和数字签名
-
加密是指:
- 我们使用一对公私钥中的一个密钥来对数据进行加密,而使用另一个密钥来进行解密的技术。
- 公钥和私钥都可以用来加密,也都可以用来解密。
- 但这个加解密必须是一对密钥之间的互相加解密,否则不能成功。
- 加密的目的是:
- 为了确保数据传输过程中的不可读性,就是不想让别人看到。
-
签名:
- 给我们将要发送的数据,做上一个唯一签名(类似于指纹)
- 用来互相验证接收方和发送方的身份;
- 在验证身份的基础上再验证一下传递的数据是否被篡改过。因此使用数字签名可以
用来达到数据的明文传输。
-
验签
- 支付宝为了验证请求的数据是否商户本人发的,
- 商户为了验证响应的数据是否支付宝发的
3)、 内网穿透
如果别人直接访问我们自己电脑的本地项目,是不能访问的。
内网穿透的原理: 内网穿透服务商是正常外网可以访问的ip地址,我们的电脑通过下载服务商软件客户端并与服务器建立起长连接,然后服务商就会给我们的电脑分配一个域名,别人的电脑访问hello.hello.com会先找到hello.com即一级域名,然后由服务商将请求转发给我们电脑的二级域名;从而实现了别人可以通过IP地址访问我们本地的项目。
下面是老师课件:
二、内网穿透
1、简介
内网穿透功能可以允许我们使用外网的网址来访问主机;
正常的外网需要访问我们项目的流程是:
- 1、买服务器并且有公网固定IP
- 2、买域名映射到服务器的IP
- 3、域名需要进行备案和审核
2、使用场景
- 1、开发测试(微信、支付宝)
- 2、智慧互联
- 3、远程控制
- 4、私有云
3、内网穿透的几个常用软件
1、natapp:https://natapp.cn/ 优惠码:022B93FD(9 折)[仅限第一次使用]
2、续断:www.zhexi.tech 优惠码:SBQMEA(95 折)[仅限第一次使用]
3、花生壳:https://www.oray.com/
老师课件中使用的 是 续断进行测试的,这里我们使用免费的内网穿透工具进行测试。
内网穿免费工具下载地址:cpolar - 安全的内网穿透工具
使用教程: Win系统如何下载安装使用cpolar内网穿透工具?_Cpolar Lisa的博客-CSDN博客
上面两个 教程都可以参考,个人推荐 第二个教程。
已经成功建立连接。
【需要注意的是,对于免费版本的cpolar随机URL地址是会在24小时之后变化的,如果需要进一步使用,可以将站点配置成二级子域名,或自定义域名(使用自己的域名)长期使用。】
- 配置修改:
修改url前缀:
- 测试:访问成功。
4)、 整合支付
注意,需要保证所有项目的编码格式都是 utf-8
1.导入支付宝支付SDK的依赖
订单服务
<!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java -->
<!--导入支付宝的SDK-->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.35.7.ALL</version>
</dependency>
2. 编写AlipayTemplate工具类和PayVo
直接复制老师给的课件。
更改一些,这里我使用的是 绑定配置文件的方式来声明一些变量的:因为老师使用了一个@ConfigurationProperties(prefix = “alipay”)。
- AlipayTemplate
@ConfigurationProperties(prefix = "alipay")
@Component
@Data
public class AlipayTemplate {
//在支付宝创建的应用的id
@Value("${alipay.app_id}")//这里使用的是绑定配置文件的方式
private String app_id;
// 商户私钥,您的PKCS8格式RSA2私钥
@Value("${alipay.merchant_private_key}")
private String merchant_private_key;
// 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
@Value("${alipay.alipay_public_key}")
private String alipay_public_key;
// 服务器[异步通知]页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
// 支付宝会悄悄的给我们发送一个请求,告诉我们支付成功的信息
@Value("${alipay.notify_url}")
private String notify_url;
// 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
//同步通知,支付成功,一般跳转到成功页
@Value("${alipay.return_url}")
private String return_url;
// 签名方式
private String sign_type = "RSA2";
// 字符编码格式
private String charset = "utf-8";
// 支付宝网关; https://openapi.alipaydev.com/gateway.do
@Value("${alipay.gatewayUrl}")
private String gatewayUrl;
public String pay(PayVo vo) throws AlipayApiException {
//AlipayClient alipayClient = new DefaultAlipayClient(AlipayTemplate.gatewayUrl, AlipayTemplate.app_id, AlipayTemplate.merchant_private_key, "json", AlipayTemplate.charset, AlipayTemplate.alipay_public_key, AlipayTemplate.sign_type);
//1、根据支付宝的配置生成一个支付客户端
AlipayClient alipayClient = new DefaultAlipayClient(gatewayUrl,
app_id, merchant_private_key, "json",
charset, alipay_public_key, sign_type);
//2、创建一个支付请求 //设置请求参数
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(return_url);
alipayRequest.setNotifyUrl(notify_url);
//商户订单号,商户网站订单系统中唯一订单号,必填
String out_trade_no = vo.getOut_trade_no();
//付款金额,必填
String total_amount = vo.getTotal_amount();
//订单名称,必填
String subject = vo.getSubject();
//商品描述,可空
String body = vo.getBody();
alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
+ "\"total_amount\":\""+ total_amount +"\","
+ "\"subject\":\""+ subject +"\","
+ "\"body\":\""+ body +"\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
String result = alipayClient.pageExecute(alipayRequest).getBody();
//会收到支付宝的响应,响应的是一个页面,只要浏览器显示这个页面,就会自动来到支付宝的收银台页面
System.out.println("支付宝的响应:"+result);
return result;
}
}
application.properties
PayVo
//封装数据
@Data
public class PayVo {
private String out_trade_no; // 商户订单号 必填
private String subject; // 订单名称 必填
private String total_amount; // 付款金额 必填
private String body; // 商品描述 可空
}
3.访问支付接口
4. 编写支付接口
produces属性:用于设置返回的数据类型
AlipayTemplate的pay()方法返回的就是一个用于浏览器响应的付款页面
- 订单服务下PayWebController
package com.atguigu.gulimall.order.web;
@Controller
public class PayWebController {
@Autowired
AlipayTemplate alipayTemplate;
@Autowired
OrderService orderService;
/**
* 1、将支付页让浏览器展示。
* 2、支付成功以后,我们要跳到用户的订单列表页。
* @param orderSn
* @return
* @throws AlipayApiException
*/
@ResponseBody
@GetMapping(value = "/payOrder",produces = "text/html") //处理点击支付宝跳转
public String payOrder(@RequestParam("orderSn") String orderSn) throws AlipayApiException {
// PayVo payVo = new PayVo();
// payVo.setBody();//订单的备注
// payVo.setOut_trade_no();//订单号
// payVo.setSubject()