支付业务、秒杀服务、Sentinel总结
1.支付业务
1.demo测试
把电脑支付demo导入idea的方法就是new ->第二行->选择自己需要的项目->导入->去到project structure->moudle去掉抱错的jar->去到facet中设置web项目的位置->然后就是create atificial->最后就是设置tomcat.
2.沙箱支付Demo
密钥介绍
①沙箱服务这里用到是公钥和密钥,非对称加密。
②也就是我们idea商户有商户密钥和支付宝公钥,支付宝有支付宝密钥和商户的公钥,我们可以通过支付宝密钥生成商户的公钥和密钥。然后传输公钥到支付宝,获取支付宝的公钥放到idea里面。
拓展与坑
①填入appid
②注意下面的return_url和notify_url需要根据web里面的路径来进行匹配而不是官方给定那个。
3.内网穿透原理
本质上就是别人通过内网穿透服务器端传输请求到我们电脑中下载好的客户端,然后通过这个通道来传输请求并且获取资源。
napapp使用
①下载客户端,注册免费隧道
②根据token去到软件位置cmd执行 natapp -authtoken yourauthtoken
③测试是否能够成功
4.应用到项目中的支付功能
思路
①导入sdk依赖,第二就是导入template。
②点击支付宝支付的时候跳转到payOrder请求处理,主要通过AlipayTemplate和订单号,查询订单信息封装到payVo,生成页面的字符串。本质就是一个表单,然后再次提交去获取其它的页面。
③接着就是配置好appid、商户密钥和支付宝公钥。
拓展与坑
①测试的时候需要开启demo也就是电脑网络支付的demo。用于跳转到return之后的页面。因为之前在隧道绑定的接口就是demo的默认接口8080。所以回调会使用之前demo的资源。如果想用自己的需要进行配置
@Controller
public class PayWebController {
@Autowired
AlipayTemplate alipayTemplate;
@Autowired
OrderService orderService;
@ResponseBody
@GetMapping(value = "payOrder",produces = "text/html")
public String payOrder(@RequestParam("orderSn")String orderSn) throws AlipayApiException {
PayVo payVo=orderService.payOrder(orderSn);
String pay = alipayTemplate.pay(payVo);
System.out.println(pay);
return pay;
}
}
@Override
public PayVo payOrder(String orderSn) {
PayVo payVo = new PayVo();
OrderEntity orderEntity = this.getOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderSn));
payVo.setOut_trade_no(orderSn);
BigDecimal payAmount = orderEntity.getPayAmount();
BigDecimal bigDecimal = payAmount.setScale(2, BigDecimal.ROUND_UP);
payVo.setTotal_amount(bigDecimal.toString());
//查询订单项
List<OrderItemEntity> orderItems = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_sn", orderSn));
OrderItemEntity orderItem=orderItems.get(0);
payVo.setSubject(orderItem.getSkuName());
payVo.setBody(orderItem.getSkuAttrsVals());
return payVo;
}
5.支付成功后跳到订单列表。
思路
①member的nginx静态资源,host,gateway处理。
②登录拦截器(放行member,远程调用请求头去掉的问题。)和springsession(yml->type是redis,开启注解,SessionConfig->存入redis的数据转换类型)引入。
③然后就是在回调页面的时候调用member写好的memberObject.html就可以了.
6.渲染页面
思路
①支付成功之后跳转到订单列表,可以在这里根据用户id获取订单列表和订单项类表(远程调用)
②获取数据之后进行渲染
拓展与坑
①远程调用order出现的问题就是远程调用的请求头丢失,需要通过FeignConfig来把这个请求头给补上
②然后就是页面404的各种原因(1)没有更新服务,导致con没有这个方法(2)method类型错误(3)访问了其它服务。
③500页面的原因(1)服务器异常,可能出现了各种奇怪的内部异常问题
④如果需要展示总记录和总页数需要用到mybatisplus的插件来完成。
Order远程调用
con
@PostMapping("/listWithItems")
//@RequiresPermissions("order:order:list")
public R listWithItems(@RequestBody Map<String, Object> params){
PageUtils page = orderService.queryPageWithItems(params);
return R.ok().put("page", page);
}
ser
@Override
public PageUtils queryPageWithItems(Map<String, Object> params) {
MemberResVo memberResVo = LoginUserInterceptor.loginUser.get();
//查询用户的订单
IPage<OrderEntity> page = this.page(
new Query<OrderEntity>().getPage(params),
new QueryWrapper<OrderEntity>().eq("member_id",memberResVo.getId()).orderByDesc("id")
);
//查询用户的订单中的订单项
List<OrderEntity> records = page.getRecords().stream().map(order -> {
String orderSn = order.getOrderSn();
List<OrderItemEntity> orderItems = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_sn", orderSn));
order.setItemEntities(orderItems);
return order;
}).collect(Collectors.toList());
page.setRecords(records);
return new PageUtils(page);
}
MemberWebCon
@GetMapping("memberOrder.html")
public String memberList(@RequestParam(value = "pageNum",defaultValue = "1")Integer pageNum,
Model model){
Map<String,Object> params=new HashMap<>();
params.put("page",pageNum);
R r = orderFeignService.listWithItems(params);
String s = JSON.toJSONString(r);
System.out.println(s);
model.addAttribute("orders",r);
return "orderList";
}
7.异步通知
思路
①其实就是支付之后为了防止网站突然崩溃,支付宝可以通过异步的方式来调用商城的接口。notify。这里采用的是最大努力通知,它会每隔一段时间告诉商城已经完成支付了。
②隧道进入的是虚拟机的80端口,进行内网穿透访问
③配置好nginx,内网穿透的路径就是natapp提供的域名,host就是order.gulimall.com自己设置的。但是去到nginx首先需要拦截这个路径,然后重新设置host,因为host会丢失。
④然后就是创建notifyListener,等待支付宝调用并返回success,支付宝接收success之后就明白应用已经支付成功。
⑤拦截器需要放payed路径过去,因为我们要访问订单,而且是内网穿透的方式,支付宝来访问,肯定没有user,这个时候就需要直接放行这个路径。
拓展与坑
①nginx这个地方如果用的是natapp,通过*.cc来作为host映射。
②如果发现传输到后端拦截到的uri是error可以检查一下自己发送的请求是不是method出现了错误
listen 80;
server_name gulimall.com *.gulimall.com *.cc;
#charset koi8-r;
#access_log /var/log/nginx/log/host.access.log main;
location /static{
root /usr/share/nginx/html;
}
location /payed {
proxy_set_header Host order.gulimall.com;
proxy_pass http://gulimall;
}
8.支付完成
思路
①支付完成之后支付宝会调用异步请求来访问内网,这个时候可以接收vo
②然后就是验收签名
③最后就是保存支付记录,并且修改订单状态为已经支付。
拓展与坑
①如果发现了验收签名失败,可能就是前面的一行转码没有去掉
②沙箱容易出bug,而且很卡。需要不断重新尝试
orderSer
@Override
public String handlePayResult(PayAsyncVo payAsyncVo) {
try {
//1.创建支付记录
PaymentInfoEntity paymentInfoEntity = new PaymentInfoEntity();
paymentInfoEntity.setAlipayTradeNo(payAsyncVo.getTrade_no());
paymentInfoEntity.setCallbackTime(payAsyncVo.getNotify_time());
paymentInfoEntity.setOrderSn(payAsyncVo.getOut_trade_no());
paymentInfoEntity.setPaymentStatus(payAsyncVo.getTrade_status());
paymentInfoService.save(paymentInfoEntity);
//2.修改订单状态
if(payAsyncVo.getTrade_status().equals("TRADE_SUCCESS")||payAsyncVo.getTrade_status().equals("TRADE_FINISHED")){
this.baseMapper.updateOrderStatus(payAsyncVo.getOut_trade_no(),OrderStatusEnum.PAYED.getCode());
}
return "success";
} catch (Exception e) {
e.printStackTrace();
return "error";
}
}
OrderPayListener
@RestController
public class OrderPayListener {
@Autowired
OrderService orderService;
@Autowired
AlipayTemplate alipayTemplate;
@PostMapping("/payed/notify")
public String handlePaySuccess(PayAsyncVo payAsyncVo, HttpServletRequest request) throws UnsupportedEncodingException, AlipayApiException {
System.out.println("执行成功");
//获取支付宝POST过来反馈信息
Map<String,String> params = new HashMap<String,String>();
Map<String,String[]> requestParams = request.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i