支付系统
一、基本概念
1、加密
①对称加密
- 什么是对称加密
所谓对称加密
,就是发送方要给接收方在网络上发送一段明文,但是不能直接发。
发送方需要加密,对称加密指的就是加密和解密用的是同一把密钥
。
假设发送方准备了一串明文,123456,想要传出去,他先用 密钥A 把123456加密成一个密文,
然后把这个密文通过网络传输给接收方,即使有人在中间截取到这个密文,
由于他不知道加密用的是什么算法,什么密钥,他就办法解密得到明文,
而接收方收到这个密文之后,他知道发送方用的是哪个密钥,所以接收方使用跟发送方用同一把密钥解密就行了
解密出来的明文,他就可以使用了,这就是对称加密。
- 缺点
对称加密它有点不安全
。
只要有一方密钥泄露,都会影响完整的通信流程。
发送方因为从头到尾只有一个密钥,只要被截取了,截取的人就可以自由的与发送方通信,包括发送方的数据,截取的人也能自由的进行解码。所以这个是不安全的,在金融领域不能用。
②非对称加密
- 什么是非对称加密
那非对称加密就是加密跟解密使用的密钥不一样
,不对称,所以叫非对称加密
。
- 非对称加密的流程
举个例子:
还是发送方想要发送一个明文数据,它的这个明文数据呢先用 密钥A 加密成一段不认识的密文。
然后密文经过网络传输到达接收方,到达接收方以后呢,接收方用 密钥B 进行解密,得到明文。
这样,即使有人在传输途中将发送者的密文截取,并且那个人也知道发送方的这个密钥。他也解不出这个密文。
所以这个相对比较安全
。
但如果说有一个黑客,如果知道了这个接收方的密钥,那么他就可以进行自由的解密,但是他却不能模拟完整的通信。
完整的通信,就是接收方又要给发送方发数据。
所以为了安全期间,我们单向的第一项流程,我们使用 A、B 两把密钥。
就是发送方,要发送数据的时候,用一把密钥加密,接收方收到以后再拿另外一把密钥来解密。
然后,接收方再给发送方要传数据的时候,就再来造上两把密钥。
接收方拿 密钥C 进行加密,发送方拿 密钥D 来进行解密。
这样相当于另外一项的这个流程,那又是一个安全的。
黑客除非完全知道我们这四把密钥,否则就不能模拟完整的整个通信过程。
所以我们就使用非对称加密来做整个金融级别的数据加密。
2、公钥、私钥
①什么是公钥、私钥
还是以这个图为例,
发送方呢要给接收方发送数据,他先拿 密钥A 进行加密。但是 密钥A 不能被别人知道,他需要隐藏起来。
他加密完数据以后,让接收方拿 密钥B 来进行解密。
所以他必须告诉接收方 密钥B 是什么,那这个 密钥B 就应该暴露出来,这个 密钥B就是我们所说的公钥
暴露出给别人用的就叫公钥,我们自己藏起来的就叫私钥。
当然这个公钥和私钥是一个相对概念
,你也可以把这个 密钥B 藏起来,那 密钥B 就是私钥,密钥A 就是公钥。
所以对于第一个单项流程,密钥B 呢就是我们的公钥,他要暴露给接收方使用。
第二个流程呢,接收方又要给发送方发数据
接收方藏一把密钥叫 密钥C,也就是私钥,发送方想要接收到这个密文来进行解密,他就得用另外一把密钥叫 密钥D,
此时接收方就得告诉发送方,要用哪把密钥才能解密接收方发的密文数据。
这样的话,接收方暴露的这把 密钥D 就被称为公钥。
3、签名、验签
在我们使用支付宝期间,也同样有四把密钥
分别是商户自己藏一把,这个叫商户私钥。
商户的加密数据要传给支付宝,让支付宝解密,那就得告诉支付宝商户的公钥是什么
RSA 算法,其实会生成一对密钥,一个A、一个B,这个 A、B随便藏一个,另外一个暴露给别人,别人就可以用来进行解密。
所以们商户现在自己商户私钥、给支付宝一把商户公钥
支付宝也一样,它自己藏一把支付宝私钥,给商户一把支付宝公钥
接下商户比如想要支付一个订单,把这个订单进行一个加密,加密成一串不认识的字符直接传过去,支付宝再进行解密。
但是我们觉得这样太麻烦了,支付宝还要解密。
包括我们加密的这串密文,如果别人一直能到拿这串密文,把这串密文截取来了,别人直接把这个密文发出去,相当于伪造这个数据。
那这个可能也有点不安全。
所以我们现在做了一个签名
,这就类似于我们签了一个协议,每个人都要按照他的指纹,那协议里的数据只要改一下。
上面的这个指纹就必须发生变化,
假设这个指纹是 MD5,只要协议内容有变化,这个指纹就必须变。
相当于这个协议的明文
跟这个这个签
名其实是一套的
。任何一个变了都不行。
所以商户在传的时候。为了支付宝使用方便,把明文传给支付宝,但是在明文后边,他用私钥再给你加一个签名
最后将这个明文和这个签名全部传出去出去,把他们作为一个整体数据在网络间传输。
别人即使拦截到这个数据,比如你想支付一百,黑客恶意破坏,让你支付一万,他给明文后边加了四个零
如果他改了,那传给支付宝,支付宝收到这一串数据,里面有一个签名,有一个明文数据,明文数据他直接拿来用,相当于他要支付多少钱。
然后,这个签名他呢就要用公钥验证一下明文跟签名是不是同一个东西,
他一验证,只要这个明文稍微一个字节发生变化,签名就不一样
。
所以网络之间现在这个数据就没法篡改,所以。
我们相当于使用公钥来做了一个验证签名。
支付宝验证完了以后,觉得这是对的。然后进行支付。支付完了以后呢,
假设支付成功了,支付宝接下来就得告诉我们这个商户成不成功。
比如支付宝有一个信息叫success=true&treadeNode=1
,告诉你支付成功,以及此次的交易流水。
接下来支付宝就得把支付成功的这个消息或各种消息,发送出去,同样支付宝先用他自己的私钥,生成这条明文数据的一个签名。
这个签名生成签名的过程,我们称为加签
,相当于按指纹的过程。
接下来把整个数据再传输给商户。
商户收到完整的数据,为了验证这条数据是支付宝发过来的,他会用支付宝给他的公钥
,来验证此条数据
。
这个验证的过程就叫验签
。如果验证正确了,那商户就认为这条发回来的数据是可信的。
①那为什么要有这个验证的过程?
假设我们模拟黑客的流程,我们现在明明支付成功了,然后呢我们这个支付宝给我们传递的是成功数据。
网络间有一个节点拦截到了我们这个支付宝数据,他把明文的successors 改成false,相当于告诉商户支付失败了,命令其再次支付。
所以呢商户收到一看,支付失败了,还需要再支付吗?
实际上是支付成功了,如果不验签,商户光看明文,一看失败了,可能会再次支付,
但是如果验证了 签名,支付宝原来的明文是true,做的明文是这样,如果改成false,肯定就是另外一段签名,所以签名验证不通过,商户就会认为这个不是支付宝传回来的数据。可能是网络之间进行了一些篡改的数据。
所以整个验签过程在任何一方都非常必要。
二、内网穿透
1、如何让所有人都可以访问到自己本地的网站
首先看一下京东,现在全世界的人都可以访问到京东,
原因是京东呢有一个域名叫 jd.com
所以如果我们想要按照正规的流程
应该是把我们这个项目写好以后,我们自己买一个服务器。
这个服务器必须有一个东西叫 公网IP,公网IP 呢相当于我们分配到全世界的人都能访问的一个IP,
然后给这个IP 再来绑定一个域名,域名相当于它的别名一样
假设域名achangmall.com
以后呢只要别人访问achangmall.com
,公网上的域名解析器,就知道这个域名对应的是我们的 IP 地址,就能让请求跳转过来,用户就能访问到我们的网站
域名跟电脑绑定,这是正规流程,然后把网站放上去,最终还要去来进行我们整个网站的备案。
2、前提
它实现的功能也是让所有人可以访问到自己本地的网站
- 第一种场景
假设访问京东,肯定是能访问的,具体就是,我们要访问的时候,去 DNS 服务器,查询京东对应的 IP 地址,然后浏览器将请求路由到这个 IP 上。
- 第二种场景
为什么我们能跟别人互相聊 QQ 呢?
其实是这样的,首先我们既然能聊天,那就说明自己跟别人电脑上肯定都装了一个QQ软件,
我们聊 QQ 的整个消息发送,都是 QQ 这个软件跟 QQ 服务器建立起连接的。
QQ 服务器的 IP 地址是固定的,那我电脑的 QQ 软件也跟服务器是建立起连接的,
所以我们要发消息呢,这个消息先到达QQ服务器,QQ服务器再通过这个链接再转发给别人的电脑
大致是这样一个简化的这个流程。
所以我们目前这种情况就是我们访问外界一定访问得通。但是呢外界想要访问我们访问不通,
除非我们跟外界装了一个互通的软件才可以访问的通。
3、内网穿透原理
我们就除了走正规流程外,我们测试期间还可以做内网穿透,
内网穿透,
我们去内网穿透的服务商,下载一个软件。
软件只要一启动。我们的软件跟内网穿透服务商就会建起一个长链接,是永远在的。
然后内网服务穿透的这个服务商还会为我们这个软件临时给一个域名,这个域名呢可能很长是一个随机的域名,这个域名也不需要备案。
因为这个域名呢是服务商的二级或者三级域名。
也就是说服务商比如是 jd.com 或 hello.com,他绑定一个域名备好案以后,他下边的这些二三级域名无论多少级。
无论生成多少个域名都是无需备案的。
所以他为我们临时分配一个域名,分配好域名以后,别人想要访问我们,使用 haha.hello.com 来访问我们,
因为 haha.hello.com 属于 hello.com 旗下,所以别人会先 ping 通 hello.com。知道他的 IP 地址,
所以别人访问我们的一切请求,先会到达内网穿透的服务商,然后内网穿透服务商一看是访问 haha.hello.com
发现这个域名是之前分配给我们的域名,所以,他将所有的请求相当于透过这个传输通道,然后直接传给我们的电脑,那就实现了别人能访问我们电脑,
比如我们电脑里边,我们部署了我们商城网站,别人通过这个域名能直接访问到我们电脑的商城网站,这样我们在测试期间那就比较好用了。
4、使用场景
- 开发测试(微信、支付宝)
- 智慧互联
- 远程控制
- 私有云
5、内网穿透的几个常用软件
- natapp:https://natapp.cn/
- 哲西云:www.zhexi.tech
- 花生壳:https://www.oray.com/
三、整合支付宝
1、整合支付宝支付
https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java
- 引入依赖
<dependency><groupId>com.alipay.sdk</groupId><artifactId>alipay-sdk-java</artifactId><version>4.17.14.ALL</version>
</dependency>
- com.achang.achangmall.order.vo.PayVo
@Data
public class PayVo {private String out_trade_no; // 商户订单号 必填private String subject; // 订单名称 必填private String total_amount; // 付款金额 必填private String body; // 商品描述 可空
}
- com.achang.achangmall.order.conf.AlipayTemplate
在配置文件中配置对应的下面属性信息
@ConfigurationProperties(prefix = "alipay")
@Component
@Data
public class AlipayTemplate {// 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号public String app_id;// 商户私钥,您的PKCS8格式RSA2私钥public String merchant_private_key;// 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。public String alipay_public_key;// 服务器[异步通知]页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问// 支付成功后,支付宝会悄悄的给我们发送一个请求,告诉我们支付成功的信息public String notify_url;// 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问//同步通知,支付成功,一般跳转到成功页public String return_url;// 签名方式private String sign_type;// 字符编码格式private String charset;//订单超时时间private String timeout = "1m";// 支付宝网关; https://openapi.alipaydev.com/gateway.dopublic 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 +"\","+ "\"timeout_express\":\""+timeout+"\","+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");String result = alipayClient.pageExecute(alipayRequest).getBody();//会收到支付宝的响应,响应的是一个页面,只要浏览器显示这个页面,就会自动来到支付宝的收银台页面System.out.println("支付宝的响应:"+result);return result;}
}
- com.achang.achangmall.order.service.impl.OrderServiceImpl
@Override
public PayVo getOrderPay(String orderSn) {PayVo payVo = new PayVo();OrderEntity orderInfo = this.getOrderByOrderSn(orderSn);//保留两位小数点,向上取值BigDecimal payAmount = orderInfo.getPayAmount().setScale(2, BigDecimal.ROUND_UP);payVo.setTotal_amount(payAmount.toString());payVo.setOut_trade_no(orderInfo.getOrderSn());//查询订单项的数据List<OrderItemEntity> orderItemInfo = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_sn", orderSn));OrderItemEntity orderItemEntity = orderItemInfo.get(0);payVo.setBody(orderItemEntity.getSkuAttrsVals());payVo.setSubject(orderItemEntity.getSkuName());return payVo;
}
- com.achang.achangmall.order.web.PayWebController
/*** 用户下单:支付宝支付* 1、让支付页让浏览器展示* 2、支付成功以后,跳转到用户的订单列表页*/
@ResponseBody
@GetMapping(value = "/aliPayOrder",produces = "text/html")
public String aliPayOrder(@RequestParam("orderSn") String orderSn) throws AlipayApiException {PayVo payVo = orderService.getOrderPay(orderSn);//根据订单号获取订单信息String pay = alipayTemplate.pay(payVo);System.out.println(pay);return pay;
}
- achangmall-order/src/main/resources/application.properties
#支付宝相关的配置
#支付宝商户的appid
alipay.app_id=2016102600766009
#我们的商家私钥
alipay.merchant_private_key=MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCvZQ2XJ0kDOq4WiBoy2LCdgTdb5SMan7sFZtStjFmsqxoZuUd8ntuPikcZKXAAZcNcXDc99Ltkt+i2fMdzvg1Ung0cwKX6wqyBLU10lkMOUMs4hVl0MhW3Ihx8D9+kZIEg1yAbxuUrF8cV81Or+qxpnJ4iwNVwkOPrUOp0n8HTpVX1mjYYjkXhM/LtvVZ+IFc4yq+tVY0tTgsVPYcCtaKoYUzhSmncgkpjdMjthy4eTSje/hDMgnLFUsDs0cfdWLN8pVTxxiipr+Y/+Ax3mCfyBEtFuYn0OXKXXjHEp+WX/iQ6yqTdyhcYkxGWD4jIqP+g7mMrP7vKsbkquQOzxayTAgMBAAECggEACmL4mA/qgfdyocDzlDlC1ED3r0h1eLkm0R4S0Cg0k0YaqJVRR27835Y3uaS7jjp4hDqtxsx8YG2HqW7gPNlvXqhxbFd4PM5Uet3c7V+Mnwdn0XQMJRZmNM8fUrV57/lHsFMtApgXsCKbVpBvTwrsNODieHpk6WKbLK9BAyEG0Gqrv1xCTD55NDyr+mdKXINt5ZDaP8R2DeNrXrTrU7+qGsM7Qfsz8429egEZx/1LnCA7PeMRZvHsSex9KZtpaXYHFGMqgSAH93yKj+rW685+KwmGSK4q0ezfGtQR7V1T6r71inwR5dQzWyTyN1ZJw0ZVWroFgwUAOUvllN/ptkmKUQKBgQD0VpJvyIbRt3J2iyYnswB/tQMk5meqBuxN1oK9tirXBTyyqZaoTtk+Z49vqhSIU0yezW81htiuF5A/gepx2v4/IqtSqCKSyxqBfFwbxONuky9gHdM+I1XyazmtSbJz8vR9U/88O60Wm1vhIe+61tWh1eWIJ7IxhA11AswfhOYMuwKBgQC3xBnOECBsh9OLnhIm9PVVxF+IGQFshsmc6kdZlKO9KdDlEiOh+TMJwhMkBqAfRJTMaz8WxPL4met2ZUJL/cXfHgAmjDnGlN69lc4ELikEd3S5aB4AWVG4ybcaHsrhLqS16WSMU1kD9l5Y3q+pbiiMW8wWpCMxVstj1T3rn6cOCQKBgE0Wx0rXZJnkHAwEqPwbgMvKC3zn6Mr/Niz0wfki8W83qsffs7XUcrw6pkmfyqycQ29S94RW0CRVMOCol5RmeJLo2E7S112jEPDLkK/+NZdcfrT/k/dl5KcAZ4kh2Fi2zaaBCuUxGtIoIBvuvhkf0PUnbCzCAXmX5TsGr+o93usjAoGBAKZtEhW+Iy9HX73tPXFMnbe8LeybAOAhvgu/XTjy1cumSEp9QAocHy3yNtWErpVCziPH6Q4c9hNRip7iG8WoogBsMiS3EEgZYRR/zGGa0Ij8CpkzgyA7xDg/bvVX99MyI/ef1PEFNvPQtydzHdGrM0vSgyXqJvkzKuZSJE71exzJAoGAQcSJ+EtdoZpqe7bjNZ2IpgSCmE0ErYFVMT6IW95VcEXOciRhEYG3TQKYuhm0ZRN+D4DfZdL8vRRQ2YDMrK14UUXzp6TBI+yirTDfbK6LYpuOZqjcvxVFwF4tdI7/K7blNY4fbhApO/Nx5tUd8duErIznnDbFVBlBjl0JWYQdrN0=
#支付宝公钥
alipay.alipay_public_key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3/IpQAsmsxozQkhGNY7aSrEtDQHjuNdhX9Z/zrZ+3aSC1gK2LS3EYXzzwFoDXMHRcTZajVqYPqKTnHWzx4lXDqJs8UlfcbSnJHEPVCGXqXl04WE9PXtcxicwNEUuDh6EeQu9GnOoBYQQiueKVK9NUdAr3UJyQF4eA45LCG0ZGNTHfqSJeLPzOcrcZglHY7NYXudxA8K7Xht1oA9laRFPmYgUNg9HuypGt1AwgOcDi9nrf1xrjeOTrPzKwLHoHp3fSTkm3GiAQZzwvkXDsK5Z99SinMyxgRze+ePiWmD01ArZXx+HNM5ZQw5dD+TLWO5qct4Yj16tE4GGCGfscfSJDwIDAQAB
#支持成功后支付宝回调我们的地址
alipay.notify_url=http://hjl.mynatapp.cc/payed/notify
#支付成功后跳转页面
alipay.return_url=http://member.achangmall.com/memberOrder.html
#加密协议
alipay.sign_type=RSA2
#编码字符
alipay.charset=utf-8
#支付宝的网关
alipay.gatewayUrl=https://openapi.alipaydev.com/gateway.do
↑支付宝整合的内容↑
- achangmall-member/src/main/resources/templates/orderList.html
支付成功后的用户系统回调页面
- achangmall-member/pom.xml
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><exclusion><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId></exclusion></exclusions>
</dependency>
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId>
</dependency>
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId>
</dependency>
- com.achang.achangmall.member.feign.OrderFeignService
@FeignClient("achangmall-order")
public interface OrderFeignService {/*** 分页查询当前登录用户的所有订单信息*/@PostMapping("/order/order/listWithItem")R listWithItem(@RequestBody Map<String, Object> params);}
- com.achang.achangmall.member.web.MemberWebController
@Controller
public class MemberWebController {@Autowiredprivate OrderFeignService orderFeignService;@GetMapping(value = "/memberOrder.html")public String memberOrderPage(@RequestParam(value = "pageNum",required = false,defaultValue = "0") Integer pageNum,Model model, HttpServletRequest request) {//获取到支付宝给我们转来的所有请求数据//request,验证签名//查出当前登录用户的所有订单列表数据Map<String,Object> page = new HashMap<>();page.put("page",pageNum.toString());//远程查询订单服务订单数据R orderInfo = orderFeignService.listWithItem(page);System.out.println(JSON.toJSONString(orderInfo));model.addAttribute("orders",orderInfo);return "orderList";}}
- com.achang.achangmall.order.controller.OrderController
/*** 分页查询当前登录用户的所有订单信息*/
@PostMapping("/listWithItem")
//@RequiresPermissions("order:order:list")
public R listWithItem(@RequestBody Map<String, Object> params){PageUtils page = orderService.queryPageWithItem(params);return R.ok().put("page", page);
}
- com.achang.achangmall.order.service.impl.OrderServiceImpl
@Override
public PageUtils queryPageWithItem(Map<String, Object> params) {MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();IPage<OrderEntity> page = this.page(new Query<OrderEntity>().getPage(params),new QueryWrapper<OrderEntity>().eq("member_id",memberResponseVo.getId()).orderByDesc("create_time"));//遍历所有订单集合List<OrderEntity> orderEntityList = page.getRecords().stream().map(order -> {//根据订单号查询订单项里的数据List<OrderItemEntity> orderItemEntities = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_sn", order.getOrderSn()));order.setOrderItemEntityList(orderItemEntities);return order;}).collect(Collectors.toList());page.setRecords(orderEntityList);return new PageUtils(page);
}
- achangmall-gateway/src/main/resources/application.yml
网关配置
- id: member_routeuri: lb://achangmall-memberpredicates:- Host=member.achangmall.com
- 本地域名映射
C:\Windows\System32\drivers\etc\hosts
- com.achang.achangmall.member.web.MemberWebController
@Controller
public class MemberWebController {@Autowiredprivate OrderFeignService orderFeignService;@GetMapping(value = "/memberOrder.html")public String memberOrderPage(@RequestParam(value = "pageNum",required = false,defaultValue = "0") Integer pageNum,Model model, HttpServletRequest request) {//获取到支付宝给我们转来的所有请求数据//request,验证签名//查出当前登录用户的所有订单列表数据Map<String,Object> page = new HashMap<>();page.put("page",pageNum.toString());//远程查询订单服务订单数据R orderInfo = orderFeignService.listWithItem(page);System.out.println(JSON.toJSONString(orderInfo));model.addAttribute("orders",orderInfo);return "orderList";}}
- com.achang.achangmall.member.interceptor.LoginUserInterceptor
@Component
public class LoginUserInterceptor implements HandlerInterceptor {public static ThreadLocal<MemberResponseVo> loginUser = new ThreadLocal<>();@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String uri = request.getRequestURI();boolean match = new AntPathMatcher().match("/member/**", uri);if (match) {return true;}HttpSession session = request.getSession();//获取登录的用户信息MemberResponseVo attribute = (MemberResponseVo) session.getAttribute(LOGIN_USER);if (attribute != null) {//把登录后用户的信息放在ThreadLocal里面进行保存loginUser.set(attribute);return true;} else {//未登录,返回登录页面response.setContentType("text/html;charset=UTF-8");PrintWriter out = response.getWriter();out.println("<script>alert('请先进行登录,再进行后续操作!');location.href='http://auth.achangmall.com/login.html'</script>");// session.setAttribute("msg", "请先进行登录");// response.sendRedirect("http://auth.achangmall.com/login.html");return false;}}
}
- com.achang.achangmall.member.config.MemberWebConfig
@Configuration
public class MemberWebConfig implements WebMvcConfigurer {@Autowiredprivate LoginUserInterceptor loginUserInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginUserInterceptor).addPathPatterns("/**");}}
- com.achang.achangmall.member.config.AchangmallFeignConfig
页面跳转请求拦截器,给feign远程调用的页面cookie加上信息
@Configuration
public class AchangmallFeignConfig {@Bean("requestInterceptor")public RequestInterceptor requestInterceptor() {RequestInterceptor requestInterceptor = new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {//1、使用RequestContextHolder拿到刚进来的请求数据ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if (requestAttributes != null) {//老请求HttpServletRequest request = requestAttributes.getRequest();if (request != null) {//2、同步请求头的数据(主要是cookie)//把老请求的cookie值放到新请求上来,进行一个同步String cookie = request.getHeader("Cookie");template.header("Cookie", cookie);}}}};return requestInterceptor;}}
2、内网穿透联调
- nginx配置
因为访问外网域名会丢失请求头,所以我们根据地址直接指定转发给队友的服务
server {listen 80;server_name achangmall.com *.achangmall.com hjl.mynatapp.cc;#charset koi8-r;#access_log /var/log/nginx/log/host.access.log main;location /payed {proxy_set_header Host order.achangmall.com;proxy_pass http://achangmall;}
- com.achang.achangmall.order.interceptor.LoginUserInterceptor
//用户登录拦截器
@Component
public class LoginUserInterceptor implements HandlerInterceptor {public static ThreadLocal<MemberResponseVo> loginUser = new ThreadLocal<>();@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String uri = request.getRequestURI();AntPathMatcher antPathMatcher = new AntPathMatcher();boolean match = antPathMatcher.match("/order/order/status/**", uri);boolean match1 = antPathMatcher.match("/payed/notify", uri);if (match || match1) {return true;}//获取登录的用户信息MemberResponseVo attribute = (MemberResponseVo) request.getSession().getAttribute(LOGIN_USER);if (attribute != null) {//把登录后用户的信息放在ThreadLocal里面进行保存loginUser.set(attribute);return true;} else {//未登录,返回登录页面response.setContentType("text/html;charset=UTF-8");PrintWriter out = response.getWriter();out.println("<script>alert('请先进行登录,再进行后续操作!');location.href='http://auth.achangmall.com/login.html'</script>");// session.setAttribute("msg", "请先进行登录");// response.sendRedirect("http://auth.gulimall.com/login.html");return false;}}}
- com.achang.achangmall.order.listener.OrderCloseListener
/*** @Description: 定时关闭订单**/@RabbitListener(queues = "order.release.order.queue")
@Service
public class OrderCloseListener {@Autowiredprivate OrderService orderService;@RabbitHandlerpublic void listener(OrderEntity orderEntity, Channel channel, Message message) throws IOException {System.out.println("收到过期的订单信息,准备关闭订单" + orderEntity.getOrderSn());try {orderService.closeOrder(orderEntity);channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);} catch (Exception e) {channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);}}
}
- com.achang.achangmall.order.listener.OrderPayedListener
/*** @Description: 订单支付成功监听器**/@RestController
public class OrderPayedListener {@Autowiredprivate OrderService orderService;@Autowiredprivate AlipayTemplate alipayTemplate;@PostMapping(value = "/payed/notify")public String handleAlipayed(PayAsyncVo asyncVo, HttpServletRequest request) throws AlipayApiException, UnsupportedEncodingException {// 只要收到支付宝的异步通知,返回 success 支付宝便不再通知// 获取支付宝POST过来反馈信息//TODO 需要验签Map<String, String> params = new HashMap<>();Map<String, String[]> requestParams = request.getParameterMap();for (String name : requestParams.keySet()) {String[] values = requestParams.get(name);String valueStr = "";for (int i = 0; i < values.length; i++) {valueStr = (i == values.length - 1) ? valueStr + values[i]: valueStr + values[i] + ",";}//乱码解决,这段代码在出现乱码时使用// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");params.put(name, valueStr);}boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayTemplate.getAlipay_public_key(),alipayTemplate.getCharset(), alipayTemplate.getSign_type()); //调用SDK验证签名if (signVerified) {System.out.println("签名验证成功...");//去修改订单状态String result = orderService.handlePayResult(asyncVo);return result;} else {System.out.println("签名验证失败...");return "error";}}@PostMapping(value = "/pay/notify")public String asyncNotify(@RequestBody String notifyData) {//异步通知结果return orderService.asyncNotify(notifyData);}}
- com.achang.achangmall.order.service.impl.OrderServiceImpl
/*** 处理支付宝的支付结果*/
@Transactional(rollbackFor = Exception.class)
@Override
public String handlePayResult(PayAsyncVo asyncVo) {//保存交易流水信息PaymentInfoEntity paymentInfo = new PaymentInfoEntity();paymentInfo.setOrderSn(asyncVo.getOut_trade_no());paymentInfo.setAlipayTradeNo(asyncVo.getTrade_no());paymentInfo.setTotalAmount(new BigDecimal(asyncVo.getBuyer_pay_amount()));paymentInfo.setSubject(asyncVo.getBody());paymentInfo.setPaymentStatus(asyncVo.getTrade_status());paymentInfo.setCreateTime(new Date());paymentInfo.setCallbackTime(asyncVo.getNotify_time());//添加到数据库中this.paymentInfoService.save(paymentInfo);//修改订单状态//获取当前状态String tradeStatus = asyncVo.getTrade_status();if (tradeStatus.equals("TRADE_SUCCESS") || tradeStatus.equals("TRADE_FINISHED")) {//支付成功状态String orderSn = asyncVo.getOut_trade_no(); //获取订单号this.updateOrderStatus(orderSn,OrderStatusEnum.PAYED.getCode(),PayConstant.ALIPAY);}return "success";
}
- com.achang.achangmall.order.vo.PayAsyncVo
@ToString
@Data
public class PayAsyncVo {private String gmt_create;private String charset;private String gmt_payment;private Date notify_time;private String subject;private String sign;private String buyer_id;//支付者的idprivate String body;//订单的信息private String invoice_amount;//支付金额private String version;private String notify_id;//通知idprivate String fund_bill_list;private String notify_type;//通知类型; trade_status_syncprivate String out_trade_no;//订单号private String total_amount;//支付的总额private String trade_status;//交易状态 TRADE_SUCCESSprivate String trade_no;//流水号private String auth_app_id;//private String receipt_amount;//商家收到的款private String point_amount;//private String app_id;//应用idprivate String buyer_pay_amount;//最终支付的金额private String sign_type;//签名类型private String seller_id;//商家的id}
- com.achang.achangmall.order.constant.PayConstant
public class PayConstant {public static final Integer ALIPAY = 1;public static final Integer WXPAY = 2;}
- com.achang.achangmall.order.service.impl.OrderServiceImpl
/*** 处理支付宝的支付结果* @param asyncVo* @return*/
@Transactional(rollbackFor = Exception.class)
@Override
public String handlePayResult(PayAsyncVo asyncVo) {//保存交易流水信息PaymentInfoEntity paymentInfo = new PaymentInfoEntity();paymentInfo.setOrderSn(asyncVo.getOut_trade_no());paymentInfo.setAlipayTradeNo(asyncVo.getTrade_no());paymentInfo.setTotalAmount(new BigDecimal(asyncVo.getBuyer_pay_amount()));paymentInfo.setSubject(asyncVo.getBody());paymentInfo.setPaymentStatus(asyncVo.getTrade_status());paymentInfo.setCreateTime(new Date());paymentInfo.setCallbackTime(asyncVo.getNotify_time());//添加到数据库中this.paymentInfoService.save(paymentInfo);//修改订单状态//获取当前状态String tradeStatus = asyncVo.getTrade_status();if (tradeStatus.equals("TRADE_SUCCESS") || tradeStatus.equals("TRADE_FINISHED")) {//支付成功状态String orderSn = asyncVo.getOut_trade_no(); //获取订单号this.updateOrderStatus(orderSn,OrderStatusEnum.PAYED.getCode(), PayConstant.ALIPAY);}return "success";
}/**
* 修改订单状态
*/
private void updateOrderStatus(String orderSn, Integer code,Integer payType) {this.baseMapper.updateOrderStatus(orderSn,code,payType);
}
- achangmall-order/src/main/resources/mapper/order/OrderDao.xml
<update id="updateOrderStatus">UPDATE oms_orderSET `status` = #{code},modify_time = NOW(),pay_type = #{payType},payment_time = NOW()WHERE order_sn = #{orderSn}
</update>
3、收单
- com.achang.achangmall.order.conf.AlipayTemplate
指定让支付宝对这段支付的请求作出相对时间的倒计时,没在指定时间支付就关闭此次的支付会话
在会话关闭之后,再支付,就会出现如下:↓