我们不做开发,只讲解
把课前资料提供的leyou-order
复制到D:\heima\code\leyou
目录。
打开父工程leyou的pom文件,添加ly-order
模块:
丝袜哥
随着互联网技术的发展,现在的网站架构基本都由原来的后端渲染,变成了:前端渲染、前后端分离的形态,而且前端技术和后端技术在各自的道路上越走越远。 前端和后端的唯一联系,变成了API接口;API文档变成了前后端开发人员联系的纽带,变得越来越重要。
没有API文档工具之前,大家都是手写API文档的,在什么地方书写的都有,而且API文档没有统一规范和格式,每个公司都不一样。这无疑给开发带来了灾难。
OpenAPI规范(OpenAPI Specification 简称OAS)是Linux基金会的一个项目,试图通过定义一种用来描述API格式或API定义的语言,来规范RESTful服务开发过程。目前V3.0版本的OpenAPI规范已经发布并开源在github上 。
官网:https://github.com/OAI/OpenAPI-Specification
OpenAPI是一个编写API文档的规范,然而如果手动去编写OpenAPI规范的文档,是非常麻烦的。而Swagger就是一个实现了OpenAPI规范的工具集。
官网:https://swagger.io/
Swagger包含的工具集:
SpringBoot已经集成了Swagger,使用简单注解即可生成swagger的API文档。
<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();
}
}
在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:多个请求参数
*/
启动服务,然后访问:http://localhost:8089/swagger-ui.html
可以通过页面看到接口信息:
输入数据:
{
"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
}
添加成功,响应订单编号。但是和数据库保存的订单编号不太一样(后几位不一样,浏览器展示长整型会出现精度损失)
订单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());
}
}
接口说明:
接口说明:
接口说明:
接口说明:
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;
}
}
}
接口说明:
因为尚未付款,所以查询返回0。
通过JS把链接变成二维码。
状态码为1,代表支付成功了!