技术标签: spring cloud实战 【实战-Spring Cloud Finchley实战】
如果还没有系统的学过Spring Cloud ,先到我的专栏去逛逛吧
这里我们简单的说下业务相关的需求,重点是体会微服务这种理念是如何落地的。
通常来讲,微服务都是分数据库的。这里我们新建个数据库给订单微服务 ,数据库实例名 o2o-order
-- ----------------------------
-- Table structure for order
-- ----------------------------
-- 订单
create table `artisan_order` (
`order_id` varchar(32) not null,
`buyer_name` varchar(32) not null comment '买家名字',
`buyer_phone` varchar(32) not null comment '买家电话',
`buyer_address` varchar(128) not null comment '买家地址',
`buyer_openid` varchar(64) not null comment '买家微信openid',
`order_amount` decimal(8,2) not null comment '订单总金额',
`order_status` tinyint(3) not null default '0' comment '订单状态, 默认为新下单',
`pay_status` tinyint(3) not null default '0' comment '支付状态, 默认未支付',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间',
primary key (`order_id`),
key `idx_buyer_openid` (`buyer_openid`)
);
-- ----------------------------
-- Table structure for order_detail
-- ----------------------------
-- 订单详情
create table `order_detail` (
`detail_id` varchar(32) not null,
`order_id` varchar(32) not null,
`product_id` varchar(32) not null,
`product_name` varchar(64) not null comment '商品名称',
`product_price` decimal(8,2) not null comment '当前价格,单位分',
`product_quantity` int not null comment '数量',
`product_icon` varchar(512) comment '小图',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间',
primary key (`detail_id`),
key `idx_order_id` (`order_id`)
);
订单与订单详情是一对多的关系,一个订单中可能包含多个订单详情,比如我下一个订单,这个订单中买了1杯奶茶、2杯可乐等。
order_detail中不仅设计了product_id,同时也冗余了 product_name product_price product_icon等,主要是考虑到有些促销活动这些字段会经常更改这些因素。
请求:
POST方式 /order/create
内容:
"name": "xxx",
"phone": "xxxx",
"address": "xxxx",
"openid": "xxxx", //用户的微信openid
"items": [
{
"productId": "xxxxxx",
"productQuantity": 2 //购买数量
}
]
后端尽量少依赖前端传递的数据,为了安全起见,产品相关的数据,只传递了一个productId和productQuantity,而没有将价格、描述等等一并传递,不传递就不会被篡改,也减少了交互数据的大小。
返回:
{
"code": 0,
"msg": "成功",
"data": {
"orderId": "123456"
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.artisan</groupId>
<artifactId>artisan_order</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>artisan_order</name>
<description>Order</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
这里我们连接到 order微服务的数据库。 我这里本地环境,就新建了个数据库实例。
server:
port: 8081
spring:
application:
name: artisan-order
# datasource
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/o2o-order?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: root
#jpa
jpa:
show-sql: true
# Eureka
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
application.yml中配置了Eureka的信息后,我们在启动类增加@EnableEurekaClient
即可
启动注册中心微服务,启动该服务
访问 http://localhost:8761/
注册成功
package com.artisan.order.domain;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.math.BigDecimal;
import java.util.Date;
@Data
// 必不可少
@Entity
@Table(name = "artisan_order")
public class Order {
/**
* 订单id.
*/
@Id
private String orderId;
/**
* 买家名字.
*/
private String buyerName;
/**
* 买家手机号.
*/
private String buyerPhone;
/**
* 买家地址.
*/
private String buyerAddress;
/**
* 买家微信Openid.
*/
private String buyerOpenid;
/**
* 订单总金额.
*/
private BigDecimal orderAmount;
/**
* 订单状态, 默认为0新下单.
*/
private Integer orderStatus;
/**
* 支付状态, 默认为0未支付.
*/
private Integer payStatus;
/**
* 创建时间.
*/
private Date createTime;
/**
* 更新时间.
*/
private Date updateTime;
}
package com.artisan.order.domain;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.math.BigDecimal;
@Data
// 必不可少
@Entity
// 如果实体类是OrderDetail,表名是order_detail,则这个注解可省略
@Table(name = "order_detail")
public class OrderDetail {
// 必不可少
@Id
private String detailId;
/**
* 订单id.
*/
private String orderId;
/**
* 商品id.
*/
private String productId;
/**
* 商品名称.
*/
private String productName;
/**
* 商品单价.
*/
private BigDecimal productPrice;
/**
* 商品数量.
*/
private Integer productQuantity;
/**
* 商品小图.
*/
private String productIcon;
}
创建订单无非就是往这两个表里写入数据。直接利用jpa提供的save方法即可。
空实现 ,利用jpa本身提供的save方法
package com.artisan.order.repository;
import com.artisan.order.domain.Order;
import org.springframework.data.jpa.repository.JpaRepository;
// JpaRepository<Order,String> 第一个是要操作的对象,第二个是实体类中标注的@Id的字段的类型 (主键类型)
public interface OrderRepository extends JpaRepository<Order,String> {
}
空实现 ,利用jpa本身提供的save方法
package com.artisan.order.repository;
import com.artisan.order.domain.OrderDetail;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderDetailRepository extends JpaRepository<OrderDetail ,String> {
}
OrderRepositoryTest
package com.artisan.order.repository;
import com.artisan.order.ArtisanOrderApplicationTests;
import com.artisan.order.domain.Order;
import com.artisan.order.enums.OrderStatusEnum;
import com.artisan.order.enums.PayStatusEnum;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
@Component
public class OrderRepositoryTest extends ArtisanOrderApplicationTests {
@Autowired
private OrderRepository orderRepository;
@Test
public void testSave(){
Order order = new Order();
order.setOrderId("1222");
order.setBuyerName("artisan");
order.setBuyerPhone("123445664");
order.setBuyerAddress("Artisan Tech");
order.setBuyerOpenid("11112233");
order.setOrderAmount(new BigDecimal(3.9));
order.setOrderStatus(OrderStatusEnum.NEW.getCode());
order.setPayStatus(PayStatusEnum.WAIT.getCode());
Order result = orderRepository.save(order);
Assert.assertNotNull(result);
}
}
数据库记录
package com.artisan.order.repository;
import com.artisan.order.ArtisanOrderApplicationTests;
import com.artisan.order.domain.OrderDetail;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
@Component
public class OrderDetailRepositoryTest extends ArtisanOrderApplicationTests {
@Autowired
private OrderDetailRepository orderDetailRepository;
@Test
public void testSave() {
OrderDetail orderDetail = new OrderDetail();
orderDetail.setDetailId("1111");
orderDetail.setOrderId("111111");
orderDetail.setProductIcon("http://xxx.com");
orderDetail.setProductId("22222");
orderDetail.setProductName("拿铁");
orderDetail.setProductPrice(new BigDecimal(0.01));
orderDetail.setProductQuantity(2);
OrderDetail result = orderDetailRepository.save(orderDetail);
Assert.assertTrue(result != null);
}
}
单元测试 通过。
分析下,我们要往artisan_order 和 order_detail中写入数据,肯定要传入Order和OrderDetail实体类,类似于 createOrder(Order order , OrderDetail orderDetail)
,根据业务规则,一个Order中可能有多个OrderDetail, 所以入参OrderDetail 必须是个集合,并且返回结果也不好定义。 因此我们将这俩合并一下,封装成DTO来使用,作为入参和返回结果。
下图中类上少儿个注解 @Data,注意补上
package com.artisan.order.service;
import com.artisan.order.dto.OrderDTO;
public interface OrderService {
OrderDTO createOrder(OrderDTO orderDTO);
}
我们来分析下前端的请求
"name": "xxx",
"phone": "xxxx",
"address": "xxxx",
"openid": "xxxx", //用户的微信openid
"items": [
{
"productId": "xxxxxx",
"productQuantity": 2 //购买数量
}
]
结合业务逻辑
校验前台入参
查询商品信息(调用商品微服务)
计算订单总价
扣减库存(调用商品微服务)
订单入库
逐一分析下目前的可行性
参数校验,我们放在Controller层校验,所以Service层这里不写
调用微服务的,我们目前还不具备,没法做
计算订单总价,前台入参仅仅传了ProductId, 而Product的数据需要调用商品微服务,目前没法做
订单入库,其实分两部分,第一个是artisan_order表,第二个是Order_detail表。 order_detail表包含了Product的内容,目前也是做不了。
综合分析,目前在Service层能做的仅仅是 入库artisan_order表
那在实现类里,我们先实现部分吧
package com.artisan.order.service.impl;
import com.artisan.order.domain.Order;
import com.artisan.order.dto.OrderDTO;
import com.artisan.order.enums.OrderStatusEnum;
import com.artisan.order.enums.PayStatusEnum;
import com.artisan.order.repository.OrderDetailRepository;
import com.artisan.order.repository.OrderRepository;
import com.artisan.order.service.OrderService;
import com.artisan.order.utils.KeyUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
OrderRepository orderRepository;
@Autowired
OrderDetailRepository orderDetailRepository;
@Override
public OrderDTO createOrder(OrderDTO orderDTO) {
// TODO 查询商品信息(调用商品微服务)
// TODO 计算订单总价
// TODO 扣减库存(调用商品微服务)
//订单入库
Order order = new Order();
orderDTO.setOrderId(KeyUtil.genUniqueKey());
// 复制属性
BeanUtils.copyProperties(orderDTO, order);
// 设置其他属性
order.setOrderAmount(new BigDecimal("100")); // TODO 后需要修改
order.setOrderStatus(OrderStatusEnum.NEW.getCode());
order.setPayStatus(PayStatusEnum.WAIT.getCode());
orderRepository.save(order);
return orderDTO;
}
}
这里仅列出关键代码,其余请参考github
package com.artisan.order.form;
import lombok.Data;
import org.hibernate.validator.constraints.NotEmpty;
import java.util.List;
@Data
public class OrderForm {
/**
* 对应
*
* {
* "name": "xxx",
* "phone": "xxxx",
* "address": "xxxx",
* "openid": "xxxx", //用户的微信openid
* "items": [
* {
* "productId": "xxxxxx",
* "productQuantity": 2 //购买数量
* }
* ]
* }
*
*
*
*/
/**
* 买家姓名
*/
@NotEmpty(message = "姓名必填")
private String name;
/**
* 买家手机号
*/
@NotEmpty(message = "手机号必填")
private String phone;
/**
* 买家地址
*/
@NotEmpty(message = "地址必填")
private String address;
/**
* 买家微信openid
*/
@NotEmpty(message = "openid必填")
private String openid;
/**
* 购物车
*/
@NotEmpty(message = "购物车不能为空")
private String items;
}
package com.artisan.order.converter;
import com.artisan.order.domain.OrderDetail;
import com.artisan.order.dto.OrderDTO;
import com.artisan.order.enums.ResultEnum;
import com.artisan.order.exception.OrderException;
import com.artisan.order.form.OrderForm;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class OrderForm2OrderDTOConverter {
public static OrderDTO convert(OrderForm orderForm) {
Gson gson = new Gson();
OrderDTO orderDTO = new OrderDTO();
orderDTO.setBuyerName(orderForm.getName());
orderDTO.setBuyerPhone(orderForm.getPhone());
orderDTO.setBuyerAddress(orderForm.getAddress());
orderDTO.setBuyerOpenid(orderForm.getOpenid());
List<OrderDetail> orderDetailList = new ArrayList<>();
try {
// fromJson 从Json相关对象到Java实体的方法 ,转换成列表类型
orderDetailList = gson.fromJson(orderForm.getItems(),
new TypeToken<List<OrderDetail>>() {
}.getType());
}catch(Exception e){
log.error("【json转换】错误, string={}", orderForm.getItems());
throw new OrderException(ResultEnum.PARAM_ERROR);
}
orderDTO.setOrderDetailList(orderDetailList);
return orderDTO;
}
}
package com.artisan.order.controller;
import com.artisan.order.converter.OrderForm2OrderDTOConverter;
import com.artisan.order.dto.OrderDTO;
import com.artisan.order.enums.ResultEnum;
import com.artisan.order.exception.OrderException;
import com.artisan.order.form.OrderForm;
import com.artisan.order.service.OrderService;
import com.artisan.order.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@Autowired
OrderService orderService;
@PostMapping("/create")
public Result create(@Valid OrderForm orderForm,
BindingResult bindingResult) {
if (bindingResult.hasErrors()){
log.error("【Create Order】参数不正确, orderForm={}", orderForm);
throw new OrderException(ResultEnum.PARAM_ERROR.getCode(),
bindingResult.getFieldError().getDefaultMessage());
}
// orderForm -> orderDTO
OrderDTO orderDTO = OrderForm2OrderDTOConverter.convert(orderForm);
if (CollectionUtils.isEmpty(orderDTO.getOrderDetailList())) {
log.error("【Create Order】购物车信息为空");
throw new OrderException(ResultEnum.CART_EMPTY);
}
OrderDTO result = orderService.createOrder(orderDTO);
Map<String, String> map = new HashMap<>();
map.put("orderId", result.getOrderId());
return Result.success(map);
}
}
使用PostMan
查看数据库
OK
说明下: x-www-form-urlencoded 这种格式 就是application/x-www-from-urlencoded,会将表单内的数据转换为键值对,比如:name=artisan&phone=123
谷歌提供的 JSON – Java Object 相互转换的 Java序列化/反序列化库。
orderDetailList = gson.fromJson(orderForm.getItems(),
new TypeToken<List<OrderDetail>>() {
}.getType());
https://github.com/yangshangwei/springcloud-o2o/tree/master/artisan-product
复赛 龙虎斗题目描述轩轩和凯凯正在玩一款叫《龙虎斗》的游戏,游戏的棋盘是一条线段,线段上有 nn 个兵营(自左至右编号 1 \sim n1∼n),相邻编号的兵营之间相隔 1 厘米,即棋盘为长度为 n−1 厘米的线段。i 号兵营里有 ci 位工兵。轩轩在左侧,代表“龙”;凯凯在右侧,代表“虎”。 他们以 mm 号兵营作为分界, 靠左的工兵属于龙势力,靠右的工兵属于虎势力,而第 mm 号兵营中的工兵很纠结,他们不属于任何一方。一个兵营的气势为:该兵营中的工兵数\times× 该兵营到 mm 号兵营的距离
Stream 流看之前:需要一些Lambda语法知识,可以查看参考资料中的文章:万字详解,JDK1.8的Lambda、Stream和日期的使用详解。正文Java8 API Stream 允许你以声明性方式处理数据集合或数组(通过查询语句来表达。Stream 的特点:Stream 自己不会存储数据Stream 不会改变源对象,他们会返回一个持有结果的新对象Stream 操作是延迟执行的,意味着他们只会在需要结果的时候才会执行。Stream 只能消费一次,消费完毕之后就会关闭。Stream
Scales, Axes and Legends6.1 简介标度(scale)是将数据的取值映射到图形空间,例如颜色、大小和形状表示不同的数值。展现标度的常见做法是绘制图例和坐标轴。每一种标度都是从数据空间的某个区域(标度的定义域)到图层属性空间的某个值域(标度的值域)的一个函数。标度的定义域赌赢者提供给这个标度的变量的取值范围。定义域(变量)可以是连续型、离散型、有序或者无序...
bellman-ford,单源最短路算法,即可以以O(VE)内求已知起点到所有点的最短路。poj3268有向图,1..n的奶牛出发到x,再回家,求其中路程最长的奶牛的路程。以x为起点就可以求所有奶牛回家的最短路。而要求以x为终点的最短路,就将所有边的方向反一反,即可。#include #include #include #includ
反编译以及重新打包并重新签名apk最近去网上学习了反编译及重写apk,下面是我吸取了网上的知识后梳理的知识,记下来以后方便用:网上学习时浏览过的博客(ps:有些不是CSDN的,不知道会不会被打额/捂脸):1、https://www.jianshu.com/p/792a08d5452c2、https://www.oschina.net/question/54100_334573、https...
程序员的终端文件管理器 nnn v2.1已经发布,nnn可能是您使用过的最快文件管理器。 它与您的DE和最喜欢的GUI实用程序无缝集成,具有独特的导航即可模式,具有自动选择,磁盘使用分析器模式,书签,上下文,应用程序启动器,熟悉的导航快捷方式,子shell产生等等。轻松集成sxiv或fzy等实用程序,或将其用作(neo)vim插件; nnn支持您需要的尽可能多的脚本!nnn不包含配置文件,并且mi..._nnn linux
234. 回文链表请判断一个链表是否为回文链表。示例 1:输入: 1->2输出: false示例 2:输入: 1->2->2->1输出: true采用栈的数据结构,遍历链表,把链表压入栈,然后从头开始对比出栈元素是否相等,相等则返回true// need n extra space public static boolean isPalindrome1(Node head) { Stack<Node> stack = new Stack<
测试并不能够最大限度的保证软件的质量,软件的高质量是开发和设计出来的,而不是测试出来的,它不仅要通过对软件开发流程的监控,使得软件开发的各个阶段都要按照指定的规程进行,通过对各个阶段产物的评审,QA 对流程的监控,对功能及配置的审计来达到开发的最优化。根据我以前的工作和学习经验,我认为做好工作首先要有一个良好的沟通,只有沟通无障碍了,才会有好的协作,才会有更好的效率,再一个就是技术一定要过关,做测试要有足够的耐心,和一个良好的工作习惯,不懂的就要问,实时与同事沟通这样的话才能做好测试工作。_软件测试面试题
摘要:明明广告点击率很高,下单量却很少?推广费用大力砸,成交额却不见涨?新品推广难,口碑带不动销量?是投放的人群不对,还是内容的吸引力不够?多少经营难题,让广告人挠秃了头。如何找出背后症结所在,优化整体的转化链路,从根本上避免“无效营销”?来源:巨量引擎如需查看完整报告和报告下载或了解更多,公众号:行业报告智库今日报告分享2021-11-28-巨量云图O-5A关系资产经营应用手册-巨量引擎.pdf2021-11-28-2021年..
在main.js加入以下代码import * as ElIconModules from '@element-plus/icons-vue'const app = createApp(App)for(let iconName in ElIconModules){ app.component(iconName,ElIconModules[iconName])}app.use(store).use(router).use(ElementPlus, {locale: zhCn}).use_import * as eliconmodules from '@element-plus/icons-vue'; ts报错
nextLine和next都是读取输入台输入的字符。next()只读取有效字符,不会读取有效字符前的空格、tab、回车,读取到有效字符后遇到空格、tab、回车才会停止读取。nextLine()会读取字符前后的空格、tab,不管是在字符前还是后遇到回车都会停止读取,读取的是回车前的所有字符。next()平时用的比较多,一般没什么问题,主要是nextLine(),以及nextLine()在next()后面使用时会出现错误。1.nextLine() Scanner sc = new Scanner(_nextline 问题
转自:http://www.2cto.com/kf/201210/163104.html一、问题引入在java程序中,需要访问数据库,做增删改查等相关操作。如何访问数据库,做数据库的相关操作呢? 二、Java连接数据库方法概述java.sql提供了一些接口和类,用于支持数据库增删改查等相关的操作。该jar包定义了java访问各种不同数据库(mysql,o_java数据库连接