微服务项目之电商--27.订单系统接口和Swagger-UI及生成产品ID的方式及生成微信付款链接及查询支付状态
目录1.订单系统接口1.1.导入订单服务1.2.Swagger-UI1.2.1.什么是OpenAPI1.2.2.什么是swagger?1.2.3.快速入门1.3.测试接口1.3.1.创建订单接口1.3.2.生成ID的方式1.3.2.查询订单接口1.3.3.更新订单状态1.3.4.分页查询订单1.3.5.生成微信付款链接1.3.6.查询支...
目录
1.订单系统接口
1.1.导入订单服务
把资料提供的leyou-order
复制到D:\heima\code\leyou
目录。
然后在工程内导入:
然后导入module:
选择导入module:
选择目录中的 ly-order
:
打开父工程leyou的pom文件,添加ly-order
模块:
1.2.Swagger-UI
1.2.1.什么是OpenAPI
随着互联网技术的发展,现在的网站架构基本都由原来的后端渲染,变成了:前端渲染、前后端分离的形态,而且前端技术和后端技术在各自的道路上越走越远。 前端和后端的唯一联系,变成了API接口;API文档变成了前后端开发人员联系的纽带,变得越来越重要。
没有API文档工具之前,大家都是手写API文档的,在什么地方书写的都有,而且API文档没有统一规范和格式,每个公司都不一样。这无疑给开发带来了灾难。
OpenAPI规范(OpenAPI Specification 简称OAS)是Linux基金会的一个项目,试图通过定义一种用来描述API格式或API定义的语言,来规范RESTful服务开发过程。目前V3.0版本的OpenAPI规范已经发布并开源在github上 。
官网:https://github.com/OAI/OpenAPI-Specification
1.2.2.什么是swagger?
OpenAPI是一个编写API文档的规范,然而如果手动去编写OpenAPI规范的文档,是非常麻烦的。而Swagger就是一个实现了OpenAPI规范的工具集。
看官方的说明:
Swagger包含的工具集:
-
Swagger编辑器: Swagger Editor允许您在浏览器中编辑YAML中的OpenAPI规范并实时预览文档。
-
Swagger UI: Swagger UI是HTML,Javascript和CSS资产的集合,可以从符合OAS标准的API动态生成漂亮的文档。
-
Swagger Codegen:允许根据OpenAPI规范自动生成API客户端库(SDK生成),服务器存根和文档。
-
Swagger Parser:用于解析来自Java的OpenAPI定义的独立库
-
Swagger Core:与Java相关的库,用于创建,使用和使用OpenAPI定义
-
Swagger Inspector(免费): API测试工具,可让您验证您的API并从现有API生成OpenAPI定义
-
SwaggerHub(免费和商业): API设计和文档,为使用OpenAPI的团队构建。
1.2.3.快速入门
SpringBoot已经集成了Swagger,使用简单注解即可生成swagger的API文档。
1)引入依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
2)编写配置
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.host("http://order.leyou.com")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.leyou.order.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("乐优商城订单系统")
.description("乐优商城订单系统接口文档")
.version("1.0")
.build();
}
}
3)接口声明
在controller的每个handler上添加接口说明注解:
@RestController
@RequestMapping("order")
@Api("订单服务接口")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private PayHelper payHelper;
/**
* 创建订单
*
* @param order 订单对象
* @return 订单编号
*/
@PostMapping
@ApiOperation(value = "创建订单接口,返回订单编号", notes = "创建订单")
@ApiImplicitParam(name = "order", required = true, value = "订单的json对象,包含订单条目和物流信息")
public ResponseEntity<Long> createOrder(@RequestBody @Valid Order order) {
Long id = this.orderService.createOrder(order);
return new ResponseEntity<>(id, HttpStatus.CREATED);
}
/**
* 分页查询当前用户订单
*
* @param status 订单状态
* @return 分页订单数据
*/
@GetMapping("list")
@ApiOperation(value = "分页查询当前用户订单,并且可以根据订单状态过滤",
notes = "分页查询当前用户订单")
@ApiImplicitParams({
@ApiImplicitParam(name = "page", value = "当前页",
defaultValue = "1", type = "Integer"),
@ApiImplicitParam(name = "rows", value = "每页大小",
defaultValue = "5", type = "Integer"),
@ApiImplicitParam(
name = "status",
value = "订单状态:1未付款,2已付款未发货,3已发货未确认,4已确认未评价,5交易关闭,6交易成功,已评价", type = "Integer"),
})
public ResponseEntity<PageResult<Order>> queryUserOrderList(
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "rows", defaultValue = "5") Integer rows,
@RequestParam(value = "status", required = false) Integer status) {
PageResult<Order> result = this.orderService.queryUserOrderList(page, rows, status);
if (result == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
return ResponseEntity.ok(result);
}
}
常用注解说明:
/**
@Api:修饰整个类,描述Controller的作用
@ApiOperation:描述一个类的一个方法,或者说一个接口
@ApiParam:单个参数描述
@ApiModel:用对象来接收参数
@ApiProperty:用对象接收参数时,描述对象的一个字段
@ApiResponse:HTTP响应其中1个描述
@ApiResponses:HTTP响应整体描述
@ApiIgnore:使用该注解忽略这个API
@ApiError :发生错误返回的信息
@ApiImplicitParam:一个请求参数
@ApiImplicitParams:多个请求参数
*/
点击order-controller,查看接口信息:
点击任意一个接口,即可看到详细信息:
1.3.测试接口
1.3.1.创建订单接口
可以通过页面看到接口信息:
-
请求方式:POST
-
请求路径:/order
-
请求参数:包含订单、订单详情等数据的json对象。
-
返回结果:订单编号
点击Try It Out
来测试:
输入数据:
{
"totalPay": 236800,
"postFee": 0,
"paymentType": 2,
"actualPay": 236800,
"buyerMessage": null,
"buyerNick": "huge",
"orderDetails": [
{
"skuId": 3893493,
"num": 1,
"title": "苹果(Apple)iPhone 6 (A1586) 16GB 金色 移动联通电信4G手机3",
"price": 236800,
"ownSpec": "{\"机身颜色\":\"钻雕蓝\",\"内存\":\"4GB\",\"机身存储\":\"64GB\"}",
"image": "http://image.leyou.com/images/9/4/1524297342728.jpg"
}
],
"receiver": "锋哥",
"receiverMobile": "15800000000",
"receiverState": "上海",
"receiverCity": "上海",
"receiverDistrict": "浦东新签",
"receiverAddress": "航头镇航头路18号传智播客3号楼",
"receiverZip": "210000",
"invoiceType": 0,
"sourceType":2
}
然后点击execute:
结果:
下单需要登录,通过登录生成token:
把token的值手动加入到浏览器的cookie中:
添加成功,响应订单编号。但是和数据库保存的订单编号不太一样(后几位不一样,浏览器展示长整型会出现精度损失)
1.3.2.生成ID的方式
订单id的特殊性
订单数据非常庞大,将来一定会做分库分表。那么这种情况下, 要保证id的唯一,就不能靠数据库自增,而是自己来实现算法,生成唯一id。
雪花算法
这里的订单id是通过一个工具类生成的:
而工具类所采用的生成id算法,是由Twitter公司开源的snowflake(雪花)算法。
简单原理
雪花算法会生成一个64位的二进制数据,为一个Long型。(转换成字符串后长度最多19位) ,其基本结构:
第一位:为未使用
第二部分:41位为毫秒级时间(41位的长度可以使用69年)
第三部分:5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点)
第四部分:最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。经测试snowflake每秒能够产生26万个ID。
配置
为了保证不重复,我们给每个部署的节点都配置机器id:
leyou:
worker:
workerId: 1
datacenterId: 1
加载属性:
@ConfigurationProperties(prefix = "leyou.worker")
public class IdWorkerProperties {
private long workerId;// 当前机器id
private long datacenterId;// 序列号
public long getWorkerId() {
return workerId;
}
public void setWorkerId(long workerId) {
this.workerId = workerId;
}
public long getDatacenterId() {
return datacenterId;
}
public void setDatacenterId(long datacenterId) {
this.datacenterId = datacenterId;
}
}
编写配置类:
@Configuration
@EnableConfigurationProperties(IdWorkerProperties.class)
public class IdWorkerConfig {
@Bean
public IdWorker idWorker(IdWorkerProperties prop) {
return new IdWorker(prop.getWorkerId(), prop.getDatacenterId());
}
}
使用:
1.3.2.查询订单接口
接口说明:
-
请求方式:GET
-
请求路径:/order/{id}
-
请求参数:id,订单编号
-
返回结果:Order,订单的json对象
测试:
结果:
1.3.3.更新订单状态
接口说明:
-
请求参数:PUT
-
请求路径:/order/{id}/{status}
-
请求参数:
-
id:订单编号,String类型,不能为空
-
status:订单状态,不能为空
-
-
返回结果:null
测试:
结果:
数据库中也发生了改变:
1.3.4.分页查询订单
接口说明:
-
请求方式:Get
-
请求路径:/order/list
-
请求参数:
-
page:当前页,Integer类型,默认为1
-
rows:每页大小,Integer类型,默认为5
-
status:订单状态,String类型,默认查询全部状态订单
-
-
返回结果:PageResult 对象,包含下面属性:
-
total:总条数
-
items:当前页订单数组
-
订单对象
-
-
测试:
结果:
1.3.5.生成微信付款链接
接口说明:
-
请求方式:Get
-
请求路径:/order/url/{id}
-
请求参数:id,订单编号
-
返回结果:String类型,生成的微信支付链接
测试:
结果:
微信支付工具
PayHelper
@Component
public class PayHelper {
private WXPay wxPay;
private static final Logger logger = LoggerFactory.getLogger(PayHelper.class);
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private OrderService orderService;
public PayHelper(PayConfig payConfig) {
// 真实开发时
wxPay = new WXPay(payConfig);
// 测试时
// wxPay = new WXPay(payConfig, WXPayConstants.SignType.MD5, true);
}
public String createPayUrl(Long orderId) {
String key = "ly.pay.url." + orderId;
try {
String url = this.redisTemplate.opsForValue().get(key);
if (StringUtils.isNotBlank(url)) {
return url;
}
} catch (Exception e) {
logger.error("查询缓存付款链接异常,订单编号:{}", orderId, e);
}
try {
Map<String, String> data = new HashMap<>();
// 商品描述
data.put("body", "乐优商城测试");
// 订单号
data.put("out_trade_no", orderId.toString());
//货币
data.put("fee_type", "CNY");
//金额,单位是分
data.put("total_fee", "1");
//调用微信支付的终端IP(estore商城的IP)
data.put("spbill_create_ip", "127.0.0.1");
//回调地址
data.put("notify_url", "http://test.leyou.com/wxpay/notify");
// 交易类型为扫码支付
data.put("trade_type", "NATIVE");
//商品id,使用假数据
data.put("product_id", "1234567");
Map<String, String> result = this.wxPay.unifiedOrder(data);
if ("SUCCESS".equals(result.get("return_code"))) {
String url = result.get("code_url");
// 将付款地址缓存,时间为10分钟
try {
this.redisTemplate.opsForValue().set(key, url, 10, TimeUnit.MINUTES);
} catch (Exception e) {
logger.error("缓存付款链接异常,订单编号:{}", orderId, e);
}
return url;
} else {
logger.error("创建预交易订单失败,错误信息:{}", result.get("return_msg"));
return null;
}
} catch (Exception e) {
logger.error("创建预交易订单异常", e);
return null;
}
}
/**
* 查询订单状态
*
* @param orderId
* @return
*/
public PayState queryOrder(Long orderId) {
Map<String, String> data = new HashMap<>();
// 订单号
data.put("out_trade_no", orderId.toString());
try {
Map<String, String> result = this.wxPay.orderQuery(data);
if (result == null) {
// 未查询到结果,认为是未付款
return PayState.NOT_PAY;
}
String state = result.get("trade_state");
if ("SUCCESS".equals(state)) {
// success,则认为付款成功
// 修改订单状态
this.orderService.updateStatus(orderId, 2);
return PayState.SUCCESS;
} else if (StringUtils.equals("USERPAYING", state)
|| StringUtils.equals("NOTPAY", state)) {
// 未付款或正在付款,都认为是未付款
return PayState.NOT_PAY;
} else {
// 其它状态认为是付款失败
return PayState.FAIL;
}
} catch (Exception e) {
logger.error("查询订单状态异常", e);
return PayState.NOT_PAY;
}
}
}
跟支付相关的其它几个类:
1.3.6.查询支付状态
接口说明:
-
请求方式: Get
-
请求路径: /state/{id}
-
请求参数: id,订单编号
-
返回结果:0, 未查询到支付信息 1,支付成功 2,支付失败(查询失败,或者订单过期)
1.3.6.1.未付款
未付款时查询,测试:
结果:
因为尚未付款,所以查询返回0。
1.3.6.2.付款
通过JS把链接变成二维码。
找到课前资料提供的JS页面:
进入,并输入刚刚生成的地址:
1.3.6.3.已付款
扫码支付,然后再次查询:
状态码为1,代表支付成功了!
更多推荐
所有评论(0)