目录

 

1.订单系统接口

1.1.导入订单服务

1.2.Swagger-UI

1.2.1.什么是OpenAPI

1.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规范的工具集。

官网:https://swagger.io/

看官方的说明:

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,代表支付成功了!

 

 

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐