(一)启动leyou-order
(二)Swagger-UI
(三)测试订单接口
(四)生成ID的方式
(五)跳转到订单结算页
(六)订单页的渲染
(七)完成下单功能
导入leyou-order后发现它变成root了,如下:
我们要修改最顶层的pom.xml,让它成为子工程,如下:
什么是OpenAPI?
随着互联网技术的发展,现在的网站架构基本都由原来的后端渲染,变成了:前端渲染、前后端分离的形态,而且前端技术和后端技术在各自的道路上越走越远。 前端和后端的唯一联系,变成了API接口;API文档变成了前后端开发人员联系的纽带,变得越来越重要。
没有API文档工具之前,大家都是手写API文档的,在什么地方书写的都有,而且API文档没有统一规范和格式,每个公司都不一样。这无疑给开发带来了灾难。
OpenAPI规范(OpenAPI Specification 简称OAS)是Linux基金会的一个项目,试图通过定义一种用来描述API格式或API定义的语言,来规范RESTful服务开发过程。目前V3.0版本的OpenAPI规范已经发布并开源在github上 。
官网:https://github.com/OAI/OpenAPI-Specification
什么是Swagger?
OpenAPI是一个编写API文档的规范,然而如果手动去编写OpenAPI规范的文档,是非常麻烦的。而Swagger就是一个实现了OpenAPI规范的工具集。
官网:https://swagger.io/
快速入门:引入依赖
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.8.0version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.8.0version>
dependency>
快速入门:编写配置
@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();
}
}
快速入门:接口声明
@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:多个请求参数
快速入门:启动测试
启动服务,然后访问:http://localhost:8089/swagger-ui.html
在商城页面登录后,复制cookie信息到swagger页面来伪造cookie,把笔记中的测试json字符串复制进去测试,生成订单成功,如下:
订单id的特殊性
订单数据非常庞大,将来一定会做分库分表。那么这种情况下, 要保证id的唯一,就不能靠数据库自增,而是自己来实现算法,生成唯一id。
要求:id唯一、可读性好、长度一致
雪花算法
这里的订单id是通过一个工具类生成的:
工具类所采用的生成id算法,是由Twitter公司开源的snowflake(雪花)算法。
雪花算法会生成一个64位的二进制数据,为一个Long型(转换成字符串后长度最多19) ,其基本结构:
snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。经测试snowflake每秒能够产生26万个ID。
在购物车页面的最下方,有一个去结算按钮:
当点击结算,我们应该跳转到订单结算页,即:getOrderInfo.html
可以看到,地址是正确的。但是只有登录用户才可以去结算付款,因此我们不能直接跳转,而是在跳转前校验用户的登录状态,如果发现是未登录,应该重定向到登录页!
我们给这个按钮绑定点击事件:
事件中判断登录状态,进行页面跳转:
toOrderInfo() {
// 判断是否登录
ly.verifyUser().then(() => {
// 已登录
window.location.href = "/getOrderInfo.html"
}).catch(() => {
// 未登录
window.location.href = "/login.html?returnUrl=" + window.location.href;
})
}
此处页面需要渲染的内容主要包含3部分:
我们修改cart.html
中的页面跳转逻辑,把用户选中的购物车信息传递过来:
然后在created
钩子函数中获取购物车数据,保存到本地属性,要注意的是,我们应该在获取数据前校验用户登录状态,如果发现未登录,则直接重定向到登录页:
支付方式
支付方式有2种:
与我们订单数据中的paymentType
关联:
所以我们可以在Vue实例中定义一个属性来记录支付方式:
然后在页面渲染时与这个变量关联:
商品列表
<ul class="send-detail">
<li v-for="(cart,index) in carts" :key="index">
<div class="sendGoods">
<ul class="yui3-g">
<li class="yui3-u-1-6">
<span><img width="70px" height="70px" :src="cart.image"/>span>
li>
<li class="yui3-u-7-12">
<div class="desc">{
{cart.title}}div>
<div class="seven">
<span v-for="(v) in JSON.parse(cart.ownSpec)">{
{v + " "}} span>
div>
li>
<li class="yui3-u-1-12">
<div class="price">¥{
{ly.formatPrice(cart.price * cart.num)}}div>
li>
<li class="yui3-u-1-12">
<div class="num">{
{cart.num}}div>
li>
<li class="yui3-u-1-12">
<div class="exit">有货div>
li>
ul>
div>
li>
ul>
总金额
另外在商品列表下面,还有一个总金额的计算:
可以看出这里主要有4个数据:
不过我们没有做优惠活动,另外运费需要结合物流系统来计算,暂时我们都设置为0,在order属性中写死:
我们通过计算属性来得到totalPay
和actualPay
值:
computed: {
totalNum(){
return this.carts.reduce((c1, c2) => c1 + c2.num, 0)
},
totalPay(){
return this.carts.reduce((c1, c2) => c1 + c2.price * c2.num, 0);
},
actualPay(){
return this.totalPay + this.order.postFee - this.order.discount;
}
},
页面提交
methods: {
submit() {
// 把购物车数据处理成订单详情
const orderDetails = this.carts.map(({
userId, ...rest}) => rest);
// 处理物流信息
const addr = this.addresses[this.selectedAddress];
const obj = {
receiver: addr.name,
receiverState: addr.state,
receiverCity: addr.city,
receiverAddress: addr.address,
receiverDistrict: addr.district,
receiverMobile: addr.phone,
receiverZip: addr.zipCode
};
// 复制到订单对象
Object.assign(this.order, obj, {
orderDetails,
totalPay: this.totalPay,
actualPay: this.actualPay,
});
// 提交订单
ly.http.post("/order/order", this.order).then(({
data}) => {
// 在线支付,需要到付款页
window.location = "pay.html?orderId=" + data;
}).catch((resp) => {
alert("订单提交失败,可能是缺货!")
})
}
},
精度损失问题
在页面点击提交测试:
成功生成订单!下面看看数据库的订单号,如下:
好像有什么不对?订单号的最后2位不正确啊!
这其实是因为JS的长整数精度有限,java的Long类型数据超出了范围,所以出现了精度损失。
我们后台返回的是Json的字符串,在axios内部会自动调用 JSON.parse()方法把json字符串转为JS数据,就会出现进度损失。如果不进行转换,依然当做字符串来使用,就不会有问题了。