import com.leyou.order.interceptors.UserInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableConfigurationProperties(JwtProperties.class)
public class MvcConfig implements WebMvcConfigurer {
@Autowired
private JwtProperties prop;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInterceptor(prop)).addPathPatterns(“/**”);
}
}
DROP TABLE IF EXISTS tb_order
;
CREATE TABLE tb_order
(
order_id
bigint(20) NOT NULL COMMENT ‘订单id’,
total_pay
bigint(20) NOT NULL COMMENT ‘总金额,单位为分’,
actual_pay
bigint(20) NOT NULL COMMENT ‘实付金额。单位:分。如:20007,表示:200元7分’,
promotion_ids
varchar(256) COLLATE utf8_bin DEFAULT ‘’,
payment_type
tinyint(1) unsigned zerofill NOT NULL COMMENT ‘支付类型,1、在线支付,2、货到付款’,
post_fee
bigint(20) NOT NULL COMMENT ‘邮费。单位:分。如:20007,表示:200元7分’,
create_time
datetime DEFAULT NULL COMMENT ‘订单创建时间’,
shipping_name
varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT ‘物流名称’,
shipping_code
varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT ‘物流单号’,
user_id
varchar(32) COLLATE utf8_bin NOT NULL COMMENT ‘用户id’,
buyer_message
varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT ‘买家留言’,
buyer_nick
varchar(32) COLLATE utf8_bin NOT NULL COMMENT ‘买家昵称’,
buyer_rate
tinyint(1) DEFAULT NULL COMMENT ‘买家是否已经评价,0未评价,1已评价’,
receiver_state
varchar(128) COLLATE utf8_bin DEFAULT ‘’ COMMENT ‘收获地址(省)’,
receiver_city
varchar(256) COLLATE utf8_bin DEFAULT ‘’ COMMENT ‘收获地址(市)’,
receiver_district
varchar(256) COLLATE utf8_bin DEFAULT ‘’ COMMENT ‘收获地址(区/县)’,
receiver_address
varchar(256) COLLATE utf8_bin DEFAULT ‘’ COMMENT ‘收获地址(街道、住址等详细地址)’,
receiver_mobile
varchar(11) COLLATE utf8_bin DEFAULT NULL COMMENT ‘收货人手机’,
receiver_zip
varchar(16) COLLATE utf8_bin DEFAULT NULL COMMENT ‘收货人邮编’,
receiver
varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT ‘收货人’,
invoice_type
int(1) DEFAULT ‘0’ COMMENT ‘发票类型(0无发票1普通发票,2电子发票,3增值税发票)’,
source_type
int(1) DEFAULT ‘2’ COMMENT ‘订单来源:1:app端,2:pc端,3:M端,4:微信端,5:手机qq端’,
PRIMARY KEY (order_id
) USING BTREE,
KEY create_time
(create_time
) USING BTREE,
KEY buyer_nick
(buyer_nick
) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;
DROP TABLE IF EXISTS tb_order_detail
;
CREATE TABLE tb_order_detail
(
id
bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单详情id ',
order_id
bigint(20) NOT NULL COMMENT ‘订单id’,
sku_id
bigint(20) NOT NULL COMMENT ‘sku商品id’,
num
int(11) NOT NULL COMMENT ‘购买数量’,
title
varchar(256) NOT NULL COMMENT ‘商品标题’,
own_spec
varchar(1024) DEFAULT ‘’ COMMENT ‘商品动态属性键值集’,
price
bigint(20) NOT NULL COMMENT ‘价格,单位:分’,
image
varchar(128) DEFAULT ‘’ COMMENT ‘商品图片’,
PRIMARY KEY (id
) USING BTREE,
KEY key_order_id
(order_id
) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=126 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT=‘订单详情表’;
DROP TABLE IF EXISTS tb_order_status
;
CREATE TABLE tb_order_status
(
order_id
bigint(20) NOT NULL COMMENT ‘订单id’,
status
int(1) DEFAULT NULL COMMENT ‘状态:1、未付款 2、已付款,未发货 3、已发货,未确认 4、交易成功 5、交易关闭 6、已评价’,
create_time
datetime DEFAULT NULL COMMENT ‘订单创建时间’,
payment_time
datetime DEFAULT NULL COMMENT ‘付款时间’,
consign_time
datetime DEFAULT NULL COMMENT ‘发货时间’,
end_time
datetime DEFAULT NULL COMMENT ‘交易完成时间’,
close_time
datetime DEFAULT NULL COMMENT ‘交易关闭时间’,
comment_time
datetime DEFAULT NULL COMMENT ‘评价时间’,
PRIMARY KEY (order_id
) USING BTREE,
KEY status
(status
) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT=‘订单状态表’;
package com.leyou.order.pojo;
import lombok.Data;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.util.Date;
import java.util.List;
@Data
@Table(name = “tb_order”)
public class Order {
@Id
private Long orderId;// id
private Long totalPay;// 总金额
private Long actualPay;// 实付金额
private Integer paymentType; // 支付类型,1、在线支付,2、货到付款
private String promotionIds; // 参与促销活动的id
private Long postFee = 0L;// 邮费
private Date createTime;// 创建时间
private String shippingName;// 物流名称
private String shippingCode;// 物流单号
private Long userId;// 用户id
private String buyerMessage;// 买家留言
private String buyerNick;// 买家昵称
private Boolean buyerRate;// 买家是否已经评价
private String receiver; // 收货人全名
private String receiverMobile; // 移动电话
private String receiverState; // 省份
private String receiverCity; // 城市
private String receiverDistrict; // 区/县
private String receiverAddress; // 收货地址,如:xx路xx号
private String receiverZip; // 邮政编码,如:310001
private Integer invoiceType = 0;// 发票类型,0无发票,1普通发票,2电子发票,3增值税发票
private Integer sourceType = 1;// 订单来源 1:app端,2:pc端,3:M端,4:微信端,5:手机qq端
@Transient
private OrderStatus orderStatus;
@Transient
private List orderDetails;
}
package com.leyou.order.pojo;
import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;
import javax.persistence.Id;
import javax.persistence.Table;
@Data
@Table(name = “tb_order_detail”)
public class OrderDetail {
@Id
@KeySql(useGeneratedKeys = true)
private Long id;
private Long orderId;// 订单id
private Long skuId;// 商品id
private Integer num;// 商品购买数量
private String title;// 商品标题
private Long price;// 商品单价
private String ownSpec;// 商品规格数据
private String image;// 图片
}
package com.leyou.order.pojo;
import lombok.Data;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
@Data
@Table(name = “tb_order_status”)
public class OrderStatus {
@Id
private Long orderId;
private Integer status;
private Date createTime;// 创建时间
private Date paymentTime;// 付款时间
private Date consignTime;// 发货时间
private Date endTime;// 交易结束时间
private Date closeTime;// 交易关闭时间
private Date commentTime;// 评价时间
}
package com.leyou.order.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CartDTO {
private Long skuId;//商品skuId
private Integer num;//购买数量
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderDTO {//数据传输对象
@NonNull //@NonNull校验不能为空
private Long addressId;
@NonNull
private Integer paymentType;
@NonNull
private List carts;
}
package com.leyou.order.web;
import com.leyou.order.dto.OrderDTO;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(“order”)
public class OrderController {
/*
创建订单
*/
@PostMapping
public ResponseEntity createOrder(@RequestBody OrderDTO orderDTO){
return null;
}
}
package com.leyou.order.service;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
}
package com.leyou.order.mapper;
import com.leyou.common.mapper.BaseMapper;
import com.leyou.order.pojo.Order;
public interface OrderMapper extends BaseMapper {
}
package com.leyou.order.mapper;
import com.leyou.common.mapper.BaseMapper;
import com.leyou.order.pojo.OrderDetail;
public interface OrderDetailMapper extends BaseMapper {
}
package com.leyou.order.mapper;
import com.leyou.common.mapper.BaseMapper;
import com.leyou.order.pojo.OrderStatus;
public interface OrderStatusMapper extends BaseMapper {
}
package com.leyou.order.service;
import com.leyou.order.mapper.OrderDetailMapper;
import com.leyou.order.mapper.OrderMapper;
import com.leyou.order.mapper.OrderStatusMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderDetailMapper detailMapper;
@Autowired
private OrderStatusMapper statusMapper;
}
2.0.实现orderService的createOrder方法
创建订单逻辑比较复杂,需要组装订单数据,基本步骤如下:
-
获取登录用户信息
-
生成订单编号,初始化订单基本信息
-
查询收货人信息
-
查询商品信息
-
封装OrderDetail信息
-
计算总金额、实付金额
-
保存订单状态信息
-
删除购物车中已购买商品减库存
2.0.1生成订单编号(雪花算法)
雪花算法是由Twitter公司开源的snowflake(雪花)算法。
- 雪花算法的原理
雪花算法会生成一个64位的二进制数据,为一个Long型。
(转换成字符串后长度最多19),其基本结构:
第一位:为未使用
第二部分:41位为毫秒级时间(41位的长度可以使用69年)
第三部分:5位datacenterld
和5位workerld
(10位的长度最多支持部署1024个节点)
第四部分:最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
snowflake
生成的ID整体上按照时间自增排序,
并且整个分布式系统内不会产生ID碰撞(由datacenter
和workerld
作区分),
并且效率较高。
经测试snowflake
每秒能够产生26万个ID。
- 配置
ly:
worker:
workerId : 1
dataCenterId : 1
- 加载属性
package com.leyou.order.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = “ly.worker”)
public class IdWorkerProperties {
private long workerId;
private long dataCenterId;
}
- 编写配置类
package com.leyou.order.config;
import com.leyou.common.utils.IdWorker;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(IdWorkerProperties.class)
public class IdWorkerConfig {
@Bean
public IdWorker idWorker(IdWorkerProperties prop){
return new IdWorker(prop.getWorkerId(),prop.getDataCenterId());
}
}
2.0.2 准备假物流数据
我们前端页面传来的是addressld,我们需要根据id查询物流信息,
但是因为还没做物流地址管理。
所以我们准备一些假数据。
首先是实体类:
package com.leyou.order.dto;
import lombok.Data;
@Data
public class AddressDTO {
private Long id;
private String name;//收件人姓名
private String phone;//电话
private String state;//省份
private String city;//城市
private String district;//区
private String address;//街道地址
private String zipCode;//邮编
private Boolean isDefault;
}
package com.leyou.order.client;
import com.leyou.order.dto.AddressDTO;
import java.util.ArrayList;
import java.util.List;
public abstract class AddressClient {
public static final List addressList = new ArrayList() {
{
AddressDTO address = new AddressDTO();
address.setId(1L);
address.setAddress(“太阳系”);
address.setCity(“银河系”);
address.setDistrict(“火星”);
address.setName(“大哥”);
address.setPhone(“15800000000”);
address.setState(“阿拉比亚大陆(Arabia Terra)”);
address.setZipCode(“210000” ) ;
address.setIsDefault(true) ;
add(address);
AddressDTO address2 = new AddressDTO();address2.setId(2L);
address.setAddress(“太阳系”);
address.setCity(“银河系”);
address2.setDistrict(“天王星”);
address2.setName(“张三”);
address2.setPhone(“13600000000” );
address2.setState(“北京”);
address2.setZipCode(“100000”);
address2.setIsDefault(false);
add(address2);
}
};
public static AddressDTO findById(Long id) {
for (AddressDTO addressDTO : addressList) {
if (addressDTO.getId() == id)
return addressDTO;
}
return null;
}
}
2.0.3 在ly-item的ly-item-interface当中新增通过sku的id集合查询其所有sku的接口
/*
通过sku的id集合查询其所有sku
*/
@GetMapping(“sku/list/ids”)
List querySkuBySpuId(@RequestParam(“ids”) List ids);
2.0.4 在Order当中定义对应client使用上述的接口
package com.leyou.order.client;
import com.leyou.item.api.GoodsApi;
import org.springframework.cloud.openfeign.FeignClient;
@FeignClient(“item-service”)
public interface GoodsClient extends GoodsApi {
}
2.0.5 完善OrderService相关的工具类
2.0.5.1 定义异常枚举
CREATE_ORDER_ERROR(500,“创建订单失败”),
2.0.5.2 定义订单状态枚举
package com.leyou.order.enums;
public enum OrderStatusEnum {
UN_PAY(1,“未付款”),
PAYED(2,“已付款,未发货”),
DELIVERED(3,“已发货,未确认”),
SUCCESS(4,“已确认,未评价”),
CLOSED(5,“已关闭,交易失败”),
RATED(6,“已评价”),
;
private int code;
private String desc;
OrderStatusEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int value(){
return this.code;
}
}
2.0.5.3 在StockMapper当中编写键库存的代码
public interface StockMapper extends BaseMapper{
@Update(“UPDATE tb_stock SET stock = stock - #{num} WHERE sku_id = #{id} AND stock >= #{num}”)
int decreaseStock(@Param(“id”) Long id, @Param(“num”) Integer num);
}
这里减库存并没有采用先查询库存,判断充足才减库存的方案,那样会有线程安全问题,当然可以通过加锁解决。不过我们此处为了效率,并没有使用悲观锁,而是对库存采用了乐观锁方案
2.0.5.4 在商品微服务当中定义减库存的内容
- 在ly-common当中创建CartDTO,将下面order当中CartDTO复制一份到ly-common当中
- GoodsController当中创建decreaseStock方法
/*
减库存
*/
@PostMapping(“stock/decrease”)
public ResponseEntity decreaseStock(@RequestBody List carts){
goodsService.decreaseStock(carts);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
- GoodsService
@Transactional
public void decreaseStock(List carts) {
for (CartDTO cart : carts) {
//键库存
int count = stockMapper.decreaseStock(cart.getSkuId(), cart.getNum());
if(count != 1){
throw new LyException(ExceptionEnum.STOCK_NOT_ENOUGH);
}
}
}
- 测试
package com.leyou.item.service;
import com.leyou.common.dto.CartDTO;
import com.leyou.item.api.GoodsApi;
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;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class GoodsServiceTest {
@Autowired
private GoodsService goodsService;
@Test
public void decreaseStock(){
List cartDTOS =
Arrays.asList(
new CartDTO(2600242L, 2),
new CartDTO(2600248L, 2)
);
goodsService.decreaseStock(cartDTOS);
}
}
运行成功
2.0.5.5 将接口提供到API当中
/*
减库存
*/
@PostMapping(“stock/decrease”)
void decreaseStock(@RequestBody List carts);
2.0.6 完善OrderService的createOrder方法
package com.leyou.order.service;
import com.leyou.auth.entity.UserInfo;
import com.leyou.common.dto.CartDTO;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.IdWorker;
import com.leyou.item.pojo.Sku;
import com.leyou.order.client.AddressClient;
import com.leyou.order.client.GoodsClient;
import com.leyou.order.dto.AddressDTO;
import com.leyou.order.dto.OrderDTO;
import com.leyou.order.enums.OrderStatusEnum;
import com.leyou.order.interceptors.UserInterceptor;
import com.leyou.order.mapper.OrderDetailMapper;
import com.leyou.order.mapper.OrderMapper;
import com.leyou.order.mapper.OrderStatusMapper;
import com.leyou.order.pojo.Order;
import com.leyou.order.pojo.OrderDetail;
import com.leyou.order.pojo.OrderStatus;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderDetailMapper detailMapper;
@Autowired
private OrderStatusMapper statusMapper;
@Autowired
private IdWorker idWorker;
@Autowired
private GoodsClient goodsClient;
@Transactional
public Long createOrder(OrderDTO orderDTO) {
//新增订单
Order order = new Order();
//订单编号
long orderId = idWorker.nextId();
order.setOrderId(orderId);
order.setCreateTime(new Date());
order.setPaymentType(orderDTO.getPaymentType());
//用户信息
UserInfo user = UserInterceptor.getLoginUser();
order.setUserId(user.getId());
order.setBuyerNick(user.getUserName());
order.setBuyerRate(false);
//收货人地址信息
//获取收货人信息
AddressDTO addr = AddressClient.findById(orderDTO.getAddressId());
order.setReceiver(addr.getName());
order.setReceiverAddress(addr.getAddress());
order.setReceiverCity(addr.getCity());
order.setReceiverDistrict(addr.getDistrict());
order.setReceiverMobile(addr.getPhone());
order.setReceiverState(addr.getState());
order.setReceiverZip(addr.getZipCode());
//金额相关
//把CartDTO 转为一个map,key是sku的id,值是num
Map<Long, Integer> numMap = orderDTO.getCarts().stream()
.collect(Collectors.toMap(CartDTO::getSkuId, CartDTO::getNum));
//获取所有的sku的id
Set ids = numMap.keySet();
//根据id查询sku
List skus = goodsClient.querySkuBySpuId(new ArrayList<>(ids));
//准备orderDetail
List details = new ArrayList<>();
long totalPay = 0L;
for (Sku sku : skus) {
//计算商品总价
totalPay += sku.getPrice() * numMap.get(sku.getId());
//封装OrderDetail
OrderDetail detail = new OrderDetail();
detail.setImage(StringUtils.substringBefore(sku.getImages(),“,”));
detail.setNum(numMap.get(sku.getId()));
detail.setOrderId(orderId);
detail.setOwnSpec(sku.getOwnSpec());
detail.setPrice(sku.getPrice());
detail.setSkuId(sku.getId());
detail.setTitle(sku.getTitle());
details.add(detail);
}
//总金额
order.setTotalPay(totalPay);
//实付金额:总金额+邮费-优惠金额
order.setActualPay(totalPay + order.getPostFee() - 0);
//把order写入数据库
int count = orderMapper.insertSelective(order);
if(count != 1){
log.error(“[创建订单] 创建订单失败,orderId:{}”,orderId);
throw new LyException(ExceptionEnum.CREATE_ORDER_ERROR);
}
//新增订单详情
count = detailMapper.insertList(details);
if(count != details.size()){
log.error(“[创建订单状态] 创建订单失败,orderId:{}”,orderId);
throw new LyException(ExceptionEnum.CREATE_ORDER_ERROR);
}
//新增订单状态
OrderStatus orderStatus = new OrderStatus();
orderStatus.setCreateTime(order.getCreateTime());
orderStatus.setOrderId(orderId);
orderStatus.setStatus(OrderStatusEnum.UN_PAY.value());
count = statusMapper.insertSelective(orderStatus);
if (count != 1) {
log.error(“[创建订单状态] 创建订单失败,orderId:{}”,orderId);
throw new LyException(ExceptionEnum.CREATE_ORDER_ERROR);
}
//减库存
List carts = orderDTO.getCarts();
goodsClient.decreaseStock(carts);
return orderId;
}
}
2.0.7 测试创建订单
===============================================================
(a)OrderController当中queryOrderById方法
@GetMapping(“{id}”)
public ResponseEntity queryOrderById(@PathVariable(“id”) Long id){
return ResponseEntity.ok(orderService.queryOrderById(id));
}
(b)完善OrderService当中对应的方法
ORDER_NOT_FOUND(404,“订单不存在”),
ORDER_DETAIL_NOT_FOUND(404,“订单详情不存在”),
ORDER_STATUS_NOT_FOUND(404,“订单状态不存在”),
public Order queryOrderById(Long id) {
//查询订单
Order order = orderMapper.selectByPrimaryKey(id);
if(order == null){
//订单不存在
throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
}
//查询订单详情
OrderDetail detail = new OrderDetail();
detail.setOrderId(id);
List details = detailMapper.select(detail);
if(CollectionUtils.isEmpty(details)){
throw new LyException(ExceptionEnum.ORDER_DETAIL_NOT_FOUND);
}
order.setOrderDetails(details);
//查询订单状态
OrderStatus orderStatus = statusMapper.selectByPrimaryKey(id);
if(orderStatus == null){
throw new LyException(ExceptionEnum.ORDER_STATUS_NOT_FOUND);
}
order.setOrderStatus(orderStatus);
return order;
}
重新运行并测试
刷新支付页面
成功显示对应的数据
=================================================================
https://pay.weixin.qq.com/index.php
填写信息
模式二与模式一相比,流程更为简单,不依赖设置的回调支付URL。
商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url;
商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。
注意:code_url有效期为2小时,过期后扫码不能再发起支付。
流程图:
-
1、商户生成订单
-
2、商户调用微信下单接口,获取预交易的链接
-
3、商户将链接生成二维码图片,展示给用户;(码中包含,价格,收款方,订单号)
-
4、用户支付并确认
-
5、支付结果通知:
-
微信异步通知商户支付结果,商户告知微信支付接收情况
-
商户如果没有收到通知,可以调用接口,查询支付状态
-
6、如果支付成功,发货,修改订单状态
在前面的业务中,我们已经完成了:
- 1、生成订单
接下来,我们需要做的是:
-
2、调用微信接口,生成链接。
-
3、并且生成二维码图片
1)创建PayConfig
package com.leyou.order.config;
import com.github.wxpay.sdk.WXPayConfig;
import lombok.Data;
import java.io.InputStream;
@Data
public class PayConfig implements WXPayConfig {
private String appID; //公众账号ID
private String mchID; //商户号
private String key; //生成签名的密钥
private int httpConnectTimeoutMs; //连接超时时间
private int httpReadTimeoutMs; //读取超时时间
private String notifyUrl;// 下单通知回调地址
@Override
public InputStream getCertStream() {
return null;
}
}
2)创建上述对应的配置文件
pay:
appId: wx8397f8696b538317
mchId: 1473426802
key: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
connectTimeoutMs: 5000
readTimeoutMs: 10000
notifyUrl: http://k4v7yt.natappfree.cc/notify/wxpay
3)自定义WXPayConfiguration类封装PayConfig
package com.leyou.order.config;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.Basic;
@Configuration
public class WXPayConfiguration {
@Bean
@ConfigurationProperties(prefix = “ly.pay”)
public PayConfig payConfig(){
return new PayConfig();
}
@Bean
public WXPay wxPay(PayConfig payConfig){
return new WXPay(payConfig, WXPayConstants.SignType.HMACSHA256);
}
}
4)支付工具类PayHelper
package com.leyou.order.utils;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.order.config.PayConfig;
import com.leyou.order.config.WXPayConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component //将其添加到Spring当中
@Slf4j
public class PayHelper {
@Autowired
private WXPay wxPay;
@Autowired
private PayConfig config;
public String createPayUrl(Long orderId, Long totalPay, String description) {
try {
HashMap<String, String> data = new HashMap<>();
//描述
data.put(“body”, description);
//订单号
data.put(“out_trade_no”, orderId.toString());
//货币(默认就是人民币)
//data.put(“fee_type”, “CNY”);
//总金额
data.put(“total_fee”, totalPay.toString());
//调用微信支付的终端ip
data.put(“spbill_create_ip”, “127.0.0.1”);
//回调地址
data.put(“notify_url”, config.getNotifyUrl());
//交易类型为扫码支付
data.put(“trade_type”, “NATIVE”);
//利用wxPay工具,完成下单
Map<String, String> result = this.wxPay.unifiedOrder(data);
//判断通信的标识
String return_code = result.get(“return_code”);
if(WXPayConstants.FAIL.equals(return_code)){
//通信失败
log.error(“[微信下单] 微信下单通信失败,失败原因:{}”,result.get(“return_msg”));
throw new LyException(ExceptionEnum.WX_PAY_ORDER_FAIL);
}
//判断业务标识
String result_code = result.get(“result_code”);
if(WXPayConstants.FAIL.equals(result_code)){
//通信失败
log.error(“[微信下单] 微信下单业务失败,错误码:{},错误原因:{}”,result.get(“err_code”),result.get(“err_code_des”));
throw new LyException(ExceptionEnum.WX_PAY_ORDER_FAIL);
}
//下单成功,获取支付连接
String url = result.get(“code_url”);
return url;
} catch (Exception e) {
log.error(“【微信下单】创建预交易订单异常”, e);
return null;
}
}
}
5)编写OrderController的createOrderUrl方法
/*
创建支付链接
*/
@GetMapping(“/url/{id}”)
public ResponseEntity createPayUrl(@PathVariable(“id”) Long orderId ){
return ResponseEntity.ok(orderService.createPayUrl(orderId));
}
6)完善OrderService
ORDER_STATUS_ERROR(400,“订单状态异常”),
@Autowired
private PayHelper payHelper;
public String createPayUrl(Long orderId) {
//查询订单 //获取订单总金额
Order order = queryOrderById(orderId);
//判断订单状态
if(order.getOrderStatus().getStatus() != OrderStatusEnum.UN_PAY.value()){
//订单状态异常
throw new LyException(ExceptionEnum.ORDER_STATUS_ERROR);
}
//获取总金额
Long actualPay = order.getActualPay();
actualPay = 1L;
//获取商品描述
OrderDetail orderDetail = order.getOrderDetails().get(0);
String desc = orderDetail.getTitle();
return payHelper.createPayUrl(orderId,actualPay,desc);
}
查询启动运行并测试
刷新支付页面
返回支付链接
1)二维码生成插件qrious
qrious是一款基于HTML5 Canvas的纯JS二维码生成插件。
通过qrious.js可以快速生成各种二维码,你可以控制二维码的尺寸颜色,
还可以将生成的二维码进行Base64编码。
官网
https://github.com/neocotic/qrious
在js目录下引入qrcode.min.js
有pay.html当中已经写好了代码
刷新页面
1)内网穿透(外网访问本地服务)
内网穿透,也即 NAT 穿透,进行 NAT 穿透是为了使具有某一个特定源 IP 地址和源端口号的数据包不被 NAT 设备屏蔽而正确路由到内网主机。
下面就相互通信的主机在网络中与 NAT 设备的相对位置介绍内网穿透方法。
UDP 内网穿透的实质是利用路由器上的NAT 系统。
NAT 是一种将私有(保留)地址转化为合法IP地址的转换技术,它被广泛应用于各种类型 Internet 接入方式和各种类型的网络中。
NAT可以完成重用地址,并且对于内部的网络结构可以实现对外隐蔽。
免费的内网穿透工具
NATAPP
登录注册
点击复制,即可得到 authtoken 这个authtoken便是您的隧道登录凭证.如这里得到的authtoken为9ab6b9040a624f40
下载客户端
解压
8.运行natapp
natapp支持两种运行方式
a) config.ini方式 (推荐)
根据操作系统下载不同的config.ini文件到刚才下载的natapp.exe同级目录 详见
将第7步得到的authtoken填进去 (其他地方都不填),然后保存
#将本文件放置于natapp同级目录程序将读取[default]段
#在命令行参数模式如natapp -authtoken=xxx等厢同参数将会覆盖掉此配置
[default]
authtoken=9ab6b9040a624f40
#对应一条隧道的authtoken
c1ienttoken=
#对应客户端的c1ienttoken,将会忽略authtoken,若无请留空,
1og=none
#1og且志文件,可以是none 代表不记录或者stdout.代表直接屏幕输出﹐默认为none
1oglevel=INFO
#日志等级DEBUG,INFO,WARNING,ERROR默认为 DEBUG
http_proxy=
#代理设置如http://110.123.10.10:3128
windows下,直接双击natapp.exe 即可.
在Linux/Mac 下 需要先给执行权限
chmod a+x natapp
然后再运行
./natapp
b) cmd -authtoken= 参数方式运行.
windows ,点击开始->运行->命令行提示符 后进入 natapp.exe的目录
运行
natapp -authtoken=9ab6b9040a624f40
linux ,同样给予可执行权限之后,运行
./natapp -authtoken=9ab6b9040a624f40
注意参数输入正确性,不要有多余的空格等!
9.运行成功,都可以得到如下界面:
Tunnel Status Online 代表链接成功
Version 当前客户端版本,如果有新版本,会有提示
Forwarding 当前穿透 网址 或者端口
Web Interface 是本地Web管理界面,可在隧道配置打开或关闭,仅用于web开发测试
Total Connections 总连接数
Avg Conn Time 0.00ms 这里不代表,不代表,不代表 延时,需要注意!
10.将natapp分配的网址(上图Forwarding ),鼠标选定然后复制下来(选定之后单击鼠标右键),在浏览器中访问,可以看到内网穿透成功了!
该网址 就是可以全球访问的网址,可以发给您的小伙伴试试 😃
2)在ly-order当中重新定义一个controller
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
下载客户端
解压
8.运行natapp
natapp支持两种运行方式
a) config.ini方式 (推荐)
根据操作系统下载不同的config.ini文件到刚才下载的natapp.exe同级目录 详见
将第7步得到的authtoken填进去 (其他地方都不填),然后保存
#将本文件放置于natapp同级目录程序将读取[default]段
#在命令行参数模式如natapp -authtoken=xxx等厢同参数将会覆盖掉此配置
[default]
authtoken=9ab6b9040a624f40
#对应一条隧道的authtoken
c1ienttoken=
#对应客户端的c1ienttoken,将会忽略authtoken,若无请留空,
1og=none
#1og且志文件,可以是none 代表不记录或者stdout.代表直接屏幕输出﹐默认为none
1oglevel=INFO
#日志等级DEBUG,INFO,WARNING,ERROR默认为 DEBUG
http_proxy=
#代理设置如http://110.123.10.10:3128
windows下,直接双击natapp.exe 即可.
在Linux/Mac 下 需要先给执行权限
chmod a+x natapp
然后再运行
./natapp
b) cmd -authtoken= 参数方式运行.
windows ,点击开始->运行->命令行提示符 后进入 natapp.exe的目录
运行
natapp -authtoken=9ab6b9040a624f40
linux ,同样给予可执行权限之后,运行
./natapp -authtoken=9ab6b9040a624f40
注意参数输入正确性,不要有多余的空格等!
9.运行成功,都可以得到如下界面:
Tunnel Status Online 代表链接成功
Version 当前客户端版本,如果有新版本,会有提示
Forwarding 当前穿透 网址 或者端口
Web Interface 是本地Web管理界面,可在隧道配置打开或关闭,仅用于web开发测试
Total Connections 总连接数
Avg Conn Time 0.00ms 这里不代表,不代表,不代表 延时,需要注意!
10.将natapp分配的网址(上图Forwarding ),鼠标选定然后复制下来(选定之后单击鼠标右键),在浏览器中访问,可以看到内网穿透成功了!
该网址 就是可以全球访问的网址,可以发给您的小伙伴试试 😃
2)在ly-order当中重新定义一个controller
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-uiSUxxLw-1714921708401)]
[外链图片转存中…(img-DdGHJYX4-1714921708401)]
[外链图片转存中…(img-h2JV06la-1714921708401)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!