添加15章示例
parent
c11b5cc7db
commit
7118be6d3d
@ -0,0 +1,26 @@
|
||||
HELP.md
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/build/
|
@ -0,0 +1,125 @@
|
||||
<?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.1.4.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>geektime.spring.springbucks</groupId>
|
||||
<artifactId>waiter-service</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>waiter-service</name>
|
||||
<description>Demo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</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-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-consul-config</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-spring-boot2</artifactId>
|
||||
<version>0.14.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.joda</groupId>
|
||||
<artifactId>joda-money</artifactId>
|
||||
<version>1.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jadira.usertype</groupId>
|
||||
<artifactId>usertype.core</artifactId>
|
||||
<version>6.0.1.GA</version>
|
||||
</dependency>
|
||||
<!-- 增加Jackson的Hibernate类型支持 -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-hibernate5</artifactId>
|
||||
<version>2.9.8</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</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>
|
@ -0,0 +1,49 @@
|
||||
package geektime.spring.springbucks.waiter;
|
||||
|
||||
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
|
||||
import geektime.spring.springbucks.waiter.controller.PerformanceInteceptor;
|
||||
import geektime.spring.springbucks.waiter.integration.Barista;
|
||||
import geektime.spring.springbucks.waiter.integration.Customer;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.TimeZone;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableJpaRepositories
|
||||
@EnableCaching
|
||||
@EnableDiscoveryClient
|
||||
@EnableBinding({ Barista.class, Customer.class })
|
||||
public class WaiterServiceApplication implements WebMvcConfigurer {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(WaiterServiceApplication.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new PerformanceInteceptor())
|
||||
.addPathPatterns("/coffee/**").addPathPatterns("/order/**");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Hibernate5Module hibernate5Module() {
|
||||
return new Hibernate5Module();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer jacksonBuilderCustomizer() {
|
||||
return builder -> {
|
||||
builder.indentOutput(true);
|
||||
builder.timeZone(TimeZone.getTimeZone("Asia/Shanghai"));
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package geektime.spring.springbucks.waiter.controller;
|
||||
|
||||
import geektime.spring.springbucks.waiter.controller.request.NewCoffeeRequest;
|
||||
import geektime.spring.springbucks.waiter.model.Coffee;
|
||||
import geektime.spring.springbucks.waiter.service.CoffeeService;
|
||||
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.apache.tomcat.util.http.fileupload.IOUtils;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/coffee")
|
||||
@RateLimiter(name = "coffee")
|
||||
@Slf4j
|
||||
public class CoffeeController {
|
||||
@Autowired
|
||||
private CoffeeService coffeeService;
|
||||
|
||||
@PostMapping(path = "/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Coffee addCoffeeWithoutBindingResult(@Valid NewCoffeeRequest newCoffee) {
|
||||
return coffeeService.saveCoffee(newCoffee.getName(), newCoffee.getPrice());
|
||||
}
|
||||
|
||||
@PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Coffee addJsonCoffeeWithoutBindingResult(@Valid @RequestBody NewCoffeeRequest newCoffee) {
|
||||
return coffeeService.saveCoffee(newCoffee.getName(), newCoffee.getPrice());
|
||||
}
|
||||
|
||||
@PostMapping(path = "/", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public List<Coffee> batchAddCoffee(@RequestParam("file") MultipartFile file) {
|
||||
List<Coffee> coffees = new ArrayList<>();
|
||||
if (!file.isEmpty()) {
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
reader = new BufferedReader(
|
||||
new InputStreamReader(file.getInputStream()));
|
||||
String str;
|
||||
while ((str = reader.readLine()) != null) {
|
||||
String[] arr = StringUtils.split(str, " ");
|
||||
if (arr != null && arr.length == 2) {
|
||||
coffees.add(coffeeService.saveCoffee(arr[0],
|
||||
Money.of(CurrencyUnit.of("CNY"),
|
||||
NumberUtils.createBigDecimal(arr[1]))));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("exception", e);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(reader);
|
||||
}
|
||||
}
|
||||
return coffees;
|
||||
}
|
||||
|
||||
@GetMapping(path = "/", params = "!name")
|
||||
public List<Coffee> getAll() {
|
||||
return coffeeService.getAllCoffee();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public Coffee getById(@PathVariable Long id) {
|
||||
Coffee coffee = coffeeService.getCoffee(id);
|
||||
log.info("Coffee {}:", coffee);
|
||||
return coffee;
|
||||
}
|
||||
|
||||
@GetMapping(path = "/", params = "name")
|
||||
public Coffee getByName(@RequestParam String name) {
|
||||
return coffeeService.getCoffee(name);
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package geektime.spring.springbucks.waiter.controller;
|
||||
|
||||
import geektime.spring.springbucks.waiter.controller.request.NewOrderRequest;
|
||||
import geektime.spring.springbucks.waiter.controller.request.OrderStateRequest;
|
||||
import geektime.spring.springbucks.waiter.model.Coffee;
|
||||
import geektime.spring.springbucks.waiter.model.CoffeeOrder;
|
||||
import geektime.spring.springbucks.waiter.service.CoffeeOrderService;
|
||||
import geektime.spring.springbucks.waiter.service.CoffeeService;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiter;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
|
||||
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/order")
|
||||
@Slf4j
|
||||
public class CoffeeOrderController {
|
||||
@Autowired
|
||||
private CoffeeOrderService orderService;
|
||||
@Autowired
|
||||
private CoffeeService coffeeService;
|
||||
private RateLimiter rateLimiter;
|
||||
|
||||
public CoffeeOrderController(RateLimiterRegistry rateLimiterRegistry) {
|
||||
rateLimiter = rateLimiterRegistry.rateLimiter("order");
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public CoffeeOrder getOrder(@PathVariable("id") Long id) {
|
||||
CoffeeOrder order = null;
|
||||
try {
|
||||
order = rateLimiter.executeSupplier(() -> orderService.get(id));
|
||||
log.info("Get Order: {}", order);
|
||||
} catch(RequestNotPermitted e) {
|
||||
log.warn("Request Not Permitted! {}", e.getMessage());
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
@PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
@io.github.resilience4j.ratelimiter.annotation.RateLimiter(name = "order")
|
||||
public CoffeeOrder create(@RequestBody NewOrderRequest newOrder) {
|
||||
log.info("Receive new Order {}", newOrder);
|
||||
Coffee[] coffeeList = coffeeService.getCoffeeByName(newOrder.getItems())
|
||||
.toArray(new Coffee[] {});
|
||||
return orderService.createOrder(newOrder.getCustomer(), coffeeList);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public CoffeeOrder updateState(@PathVariable("id") Long id,
|
||||
@RequestBody OrderStateRequest orderState) {
|
||||
log.info("Update order {} with state {}", id, orderState);
|
||||
CoffeeOrder order = orderService.get(id);
|
||||
orderService.updateState(order, orderState.getState());
|
||||
return order;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package geektime.spring.springbucks.waiter.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Slf4j
|
||||
public class PerformanceInteceptor implements HandlerInterceptor {
|
||||
private ThreadLocal<StopWatch> stopWatch = new ThreadLocal<>();
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
StopWatch sw = new StopWatch();
|
||||
stopWatch.set(sw);
|
||||
sw.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
stopWatch.get().stop();
|
||||
stopWatch.get().start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
StopWatch sw = stopWatch.get();
|
||||
sw.stop();
|
||||
String method = handler.getClass().getSimpleName();
|
||||
if (handler instanceof HandlerMethod) {
|
||||
String beanType = ((HandlerMethod) handler).getBeanType().getName();
|
||||
String methodName = ((HandlerMethod) handler).getMethod().getName();
|
||||
method = beanType + "." + methodName;
|
||||
}
|
||||
log.info("{};{};{};{};{}ms;{}ms;{}ms", request.getRequestURI(), method,
|
||||
response.getStatus(), ex == null ? "-" : ex.getClass().getSimpleName(),
|
||||
sw.getTotalTimeMillis(), sw.getTotalTimeMillis() - sw.getLastTaskTimeMillis(),
|
||||
sw.getLastTaskTimeMillis());
|
||||
stopWatch.remove();
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package geektime.spring.springbucks.waiter.controller.request;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.joda.money.Money;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class NewCoffeeRequest {
|
||||
@NotEmpty
|
||||
private String name;
|
||||
@NotNull
|
||||
private Money price;
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package geektime.spring.springbucks.waiter.controller.request;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class NewOrderRequest {
|
||||
private String customer;
|
||||
private List<String> items;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package geektime.spring.springbucks.waiter.controller.request;
|
||||
|
||||
import geektime.spring.springbucks.waiter.model.OrderState;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class OrderStateRequest {
|
||||
private OrderState state;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package geektime.spring.springbucks.waiter.integration;
|
||||
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
public interface Barista {
|
||||
String NEW_ORDERS = "newOrders";
|
||||
String FINISHED_ORDERS = "finishedOrders";
|
||||
|
||||
@Input
|
||||
SubscribableChannel finishedOrders();
|
||||
|
||||
@Output
|
||||
MessageChannel newOrders();
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package geektime.spring.springbucks.waiter.integration;
|
||||
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
public interface Customer {
|
||||
String NOTIFY_ORDERS = "notifyOrders";
|
||||
|
||||
@Output(NOTIFY_ORDERS)
|
||||
MessageChannel notification();
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package geektime.spring.springbucks.waiter.integration;
|
||||
|
||||
import geektime.spring.springbucks.waiter.model.CoffeeOrder;
|
||||
import geektime.spring.springbucks.waiter.service.CoffeeOrderService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class OrderListener {
|
||||
@Autowired
|
||||
private Customer customer;
|
||||
@Autowired
|
||||
private CoffeeOrderService orderService;
|
||||
|
||||
@StreamListener(Barista.FINISHED_ORDERS)
|
||||
public void listenFinishedOrders(Long id) {
|
||||
log.info("We've finished an order [{}].", id);
|
||||
CoffeeOrder order = orderService.get(id);
|
||||
Message<Long> message = MessageBuilder.withPayload(id)
|
||||
.setHeader("customer", order.getCustomer())
|
||||
.build();
|
||||
log.info("Notify the customer: {}", order.getCustomer());
|
||||
customer.notification().send(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package geektime.spring.springbucks.waiter.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@MappedSuperclass
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
// 增加了jackson-datatype-hibernate5就不需要这个Ignore了
|
||||
//@JsonIgnoreProperties(value = {"hibernateLazyInitializer"})
|
||||
public class BaseEntity implements Serializable {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
@Column(updatable = false)
|
||||
@CreationTimestamp
|
||||
private Date createTime;
|
||||
@UpdateTimestamp
|
||||
private Date updateTime;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package geektime.spring.springbucks.waiter.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.joda.money.Money;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Entity
|
||||
@Table(name = "T_COFFEE")
|
||||
@Builder
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Coffee extends BaseEntity implements Serializable {
|
||||
private String name;
|
||||
@Type(type = "org.jadira.usertype.moneyandcurrency.joda.PersistentMoneyMinorAmount",
|
||||
parameters = {@org.hibernate.annotations.Parameter(name = "currencyCode", value = "CNY")})
|
||||
private Money price;
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package geektime.spring.springbucks.waiter.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.joda.money.Money;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.JoinTable;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.OrderBy;
|
||||
import javax.persistence.Table;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(name = "T_ORDER")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class CoffeeOrder extends BaseEntity implements Serializable {
|
||||
private String customer;
|
||||
@ManyToMany
|
||||
@JoinTable(name = "T_ORDER_COFFEE")
|
||||
@OrderBy("id")
|
||||
private List<Coffee> items;
|
||||
@Enumerated
|
||||
@Column(nullable = false)
|
||||
private OrderState state;
|
||||
private Integer discount;
|
||||
@Type(type = "org.jadira.usertype.moneyandcurrency.joda.PersistentMoneyMinorAmount",
|
||||
parameters = {@org.hibernate.annotations.Parameter(name = "currencyCode", value = "CNY")})
|
||||
private Money total;
|
||||
private String waiter;
|
||||
private String barista;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package geektime.spring.springbucks.waiter.model;
|
||||
|
||||
public enum OrderState {
|
||||
INIT, PAID, BREWING, BREWED, TAKEN, CANCELLED
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package geektime.spring.springbucks.waiter.repository;
|
||||
|
||||
import geektime.spring.springbucks.waiter.model.CoffeeOrder;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface CoffeeOrderRepository extends JpaRepository<CoffeeOrder, Long> {
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package geektime.spring.springbucks.waiter.repository;
|
||||
|
||||
import geektime.spring.springbucks.waiter.model.Coffee;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface CoffeeRepository extends JpaRepository<Coffee, Long> {
|
||||
List<Coffee> findByNameInOrderById(List<String> list);
|
||||
Coffee findByName(String name);
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package geektime.spring.springbucks.waiter.service;
|
||||
|
||||
import geektime.spring.springbucks.waiter.integration.Barista;
|
||||
import geektime.spring.springbucks.waiter.model.Coffee;
|
||||
import geektime.spring.springbucks.waiter.model.CoffeeOrder;
|
||||
import geektime.spring.springbucks.waiter.model.OrderState;
|
||||
import geektime.spring.springbucks.waiter.repository.CoffeeOrderRepository;
|
||||
import geektime.spring.springbucks.waiter.support.OrderProperties;
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.joda.money.Money;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
@Slf4j
|
||||
public class CoffeeOrderService implements MeterBinder {
|
||||
@Autowired
|
||||
private CoffeeOrderRepository orderRepository;
|
||||
@Autowired
|
||||
private OrderProperties orderProperties;
|
||||
@Autowired
|
||||
private Barista barista;
|
||||
|
||||
private String waiterId = UUID.randomUUID().toString();
|
||||
|
||||
private Counter orderCounter = null;
|
||||
|
||||
public CoffeeOrder get(Long id) {
|
||||
return orderRepository.getOne(id);
|
||||
}
|
||||
|
||||
public CoffeeOrder createOrder(String customer, Coffee...coffee) {
|
||||
CoffeeOrder order = CoffeeOrder.builder()
|
||||
.customer(customer)
|
||||
.items(new ArrayList<>(Arrays.asList(coffee)))
|
||||
.discount(orderProperties.getDiscount())
|
||||
.total(calcTotal(coffee))
|
||||
.state(OrderState.INIT)
|
||||
.waiter(orderProperties.getWaiterPrefix() + waiterId)
|
||||
.build();
|
||||
CoffeeOrder saved = orderRepository.save(order);
|
||||
log.info("New Order: {}", saved);
|
||||
orderCounter.increment();
|
||||
return saved;
|
||||
}
|
||||
|
||||
public boolean updateState(CoffeeOrder order, OrderState state) {
|
||||
if (order == null) {
|
||||
log.warn("Can not find order.");
|
||||
return false;
|
||||
}
|
||||
if (state.compareTo(order.getState()) <= 0) {
|
||||
log.warn("Wrong State order: {}, {}", state, order.getState());
|
||||
return false;
|
||||
}
|
||||
order.setState(state);
|
||||
orderRepository.save(order);
|
||||
log.info("Updated Order: {}", order);
|
||||
if (state == OrderState.PAID) {
|
||||
// 有返回值,如果要关注发送结果,则判断返回值
|
||||
// 一般消息体不会这么简单
|
||||
barista.newOrders().send(MessageBuilder.withPayload(order.getId()).build());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTo(MeterRegistry meterRegistry) {
|
||||
this.orderCounter = meterRegistry.counter("order.count");
|
||||
}
|
||||
|
||||
private Money calcTotal(Coffee...coffee) {
|
||||
List<Money> items = Stream.of(coffee).map(c -> c.getPrice())
|
||||
.collect(Collectors.toList());
|
||||
return Money.total(items).multipliedBy(orderProperties.getDiscount())
|
||||
.dividedBy(100, RoundingMode.HALF_UP);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package geektime.spring.springbucks.waiter.service;
|
||||
|
||||
import geektime.spring.springbucks.waiter.model.Coffee;
|
||||
import geektime.spring.springbucks.waiter.repository.CoffeeRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.joda.money.Money;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheConfig;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@CacheConfig(cacheNames = "CoffeeCache")
|
||||
public class CoffeeService {
|
||||
@Autowired
|
||||
private CoffeeRepository coffeeRepository;
|
||||
|
||||
public Coffee saveCoffee(String name, Money price) {
|
||||
return coffeeRepository.save(Coffee.builder().name(name).price(price).build());
|
||||
}
|
||||
|
||||
@Cacheable
|
||||
public List<Coffee> getAllCoffee() {
|
||||
return coffeeRepository.findAll(Sort.by("id"));
|
||||
}
|
||||
|
||||
public Coffee getCoffee(Long id) {
|
||||
// return coffeeRepository.findById(id).get();
|
||||
return coffeeRepository.getOne(id);
|
||||
}
|
||||
|
||||
public long getCoffeeCount() {
|
||||
return coffeeRepository.count();
|
||||
}
|
||||
|
||||
public Coffee getCoffee(String name) {
|
||||
return coffeeRepository.findByName(name);
|
||||
}
|
||||
|
||||
public List<Coffee> getCoffeeByName(List<String> names) {
|
||||
return coffeeRepository.findByNameInOrderById(names);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package geektime.spring.springbucks.waiter.support;
|
||||
|
||||
import geektime.spring.springbucks.waiter.service.CoffeeService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class CoffeeIndicator implements HealthIndicator {
|
||||
@Autowired
|
||||
private CoffeeService coffeeService;
|
||||
|
||||
@Override
|
||||
public Health health() {
|
||||
long count = coffeeService.getCoffeeCount();
|
||||
Health health;
|
||||
if (count > 0) {
|
||||
health = Health.up()
|
||||
.withDetail("count", count)
|
||||
.withDetail("message", "We have enough coffee.")
|
||||
.build();
|
||||
} else {
|
||||
health = Health.down()
|
||||
.withDetail("count", 0)
|
||||
.withDetail("message", "We are out of coffee.")
|
||||
.build();
|
||||
}
|
||||
return health;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package geektime.spring.springbucks.waiter.support;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.springframework.boot.jackson.JsonComponent;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@JsonComponent
|
||||
public class MoneyDeserializer extends StdDeserializer<Money> {
|
||||
protected MoneyDeserializer() {
|
||||
super(Money.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Money deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
return Money.of(CurrencyUnit.of("CNY"), p.getDecimalValue());
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package geektime.spring.springbucks.waiter.support;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import org.joda.money.Money;
|
||||
import org.springframework.boot.jackson.JsonComponent;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@JsonComponent
|
||||
public class MoneySerializer extends StdSerializer<Money> {
|
||||
protected MoneySerializer() {
|
||||
super(Money.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Money money, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
|
||||
jsonGenerator.writeNumber(money.getAmount());
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package geektime.spring.springbucks.waiter.support;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@ConfigurationProperties("order")
|
||||
@RefreshScope
|
||||
@Data
|
||||
@Component
|
||||
public class OrderProperties {
|
||||
private Integer discount = 100;
|
||||
private String waiterPrefix = "springbucks-";
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
spring.jpa.hibernate.ddl-auto=none
|
||||
spring.jpa.properties.hibernate.show_sql=false
|
||||
spring.jpa.properties.hibernate.format_sql=false
|
||||
# 用来解决 LazyInitializationException: no Session
|
||||
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
|
||||
|
||||
# 运行过一次后,如果不想清空数据库就注释掉下面这行
|
||||
spring.datasource.initialization-mode=always
|
||||
|
||||
management.endpoints.web.exposure.include=*
|
||||
management.endpoint.health.show-details=always
|
||||
|
||||
info.app.author=DigitalSonic
|
||||
info.app.encoding=@project.build.sourceEncoding@
|
||||
|
||||
spring.output.ansi.enabled=ALWAYS
|
||||
|
||||
server.port=0
|
||||
|
||||
spring.datasource.url=jdbc:mysql://localhost/springbucks
|
||||
spring.datasource.username=springbucks
|
||||
spring.datasource.password=springbucks
|
||||
|
||||
order.discount=95
|
||||
|
||||
resilience4j.ratelimiter.limiters.coffee.limit-for-period=5
|
||||
resilience4j.ratelimiter.limiters.coffee.limit-refresh-period-in-millis=30000
|
||||
resilience4j.ratelimiter.limiters.coffee.timeout-in-millis=5000
|
||||
resilience4j.ratelimiter.limiters.coffee.subscribe-for-events=true
|
||||
resilience4j.ratelimiter.limiters.coffee.register-health-indicator=true
|
||||
|
||||
resilience4j.ratelimiter.limiters.order.limit-for-period=3
|
||||
resilience4j.ratelimiter.limiters.order.limit-refresh-period-in-millis=30000
|
||||
resilience4j.ratelimiter.limiters.order.timeout-in-millis=1000
|
||||
resilience4j.ratelimiter.limiters.order.subscribe-for-events=true
|
||||
resilience4j.ratelimiter.limiters.order.register-health-indicator=true
|
||||
|
||||
spring.rabbitmq.host=localhost
|
||||
spring.rabbitmq.port=5672
|
||||
spring.rabbitmq.username=spring
|
||||
spring.rabbitmq.password=spring
|
||||
|
||||
spring.cloud.stream.bindings.finishedOrders.group=waiter-service
|
||||
spring.cloud.stream.rabbit.bindings.notifyOrders.producer.routing-key-expression=headers.customer
|
@ -0,0 +1,8 @@
|
||||
spring.application.name=waiter-service
|
||||
|
||||
spring.cloud.consul.host=localhost
|
||||
spring.cloud.consul.port=8500
|
||||
spring.cloud.consul.discovery.prefer-ip-address=true
|
||||
|
||||
spring.cloud.consul.config.enabled=true
|
||||
spring.cloud.consul.config.format=yaml
|
@ -0,0 +1,2 @@
|
||||
Americano 25.0
|
||||
Italian 30.0
|
@ -0,0 +1,5 @@
|
||||
insert into t_coffee (name, price, create_time, update_time) values ('espresso', 2000, now(), now());
|
||||
insert into t_coffee (name, price, create_time, update_time) values ('latte', 2500, now(), now());
|
||||
insert into t_coffee (name, price, create_time, update_time) values ('capuccino', 2500, now(), now());
|
||||
insert into t_coffee (name, price, create_time, update_time) values ('mocha', 3000, now(), now());
|
||||
insert into t_coffee (name, price, create_time, update_time) values ('macchiato', 3000, now(), now());
|
@ -0,0 +1,33 @@
|
||||
# 注意 MySQL 与 H2 的语法差异
|
||||
# H2: drop table tbl if exists;
|
||||
# MySQL: drop table if exists tbl;
|
||||
drop table if exists t_coffee;
|
||||
drop table if exists t_order;
|
||||
drop table if exists t_order_coffee;
|
||||
|
||||
create table t_coffee (
|
||||
id bigint auto_increment,
|
||||
create_time timestamp,
|
||||
update_time timestamp,
|
||||
name varchar(255),
|
||||
price bigint,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table t_order (
|
||||
id bigint auto_increment,
|
||||
create_time timestamp,
|
||||
update_time timestamp,
|
||||
customer varchar(255),
|
||||
waiter varchar(255),
|
||||
barista varchar(255),
|
||||
discount integer,
|
||||
total bigint,
|
||||
state integer,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table t_order_coffee (
|
||||
coffee_order_id bigint not null,
|
||||
items_id bigint not null
|
||||
);
|
@ -0,0 +1,16 @@
|
||||
package geektime.spring.springbucks.waiter;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class WaiterServiceApplicationTests {
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
HELP.md
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
@ -0,0 +1,286 @@
|
||||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Maven2 Start Up Batch script
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
# JAVA_HOME - location of a JDK home dir
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# M2_HOME - location of maven2's installed home dir
|
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
# e.g. to debug Maven itself, use
|
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||
|
||||
if [ -f /etc/mavenrc ] ; then
|
||||
. /etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then
|
||||
. "$HOME/.mavenrc"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# OS specific support. $var _must_ be set to either true or false.
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
mingw=false
|
||||
case "`uname`" in
|
||||
CYGWIN*) cygwin=true ;;
|
||||
MINGW*) mingw=true;;
|
||||
Darwin*) darwin=true
|
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if [ -x "/usr/libexec/java_home" ]; then
|
||||
export JAVA_HOME="`/usr/libexec/java_home`"
|
||||
else
|
||||
export JAVA_HOME="/Library/Java/Home"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
if [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=`java-config --jre-home`
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$M2_HOME" ] ; then
|
||||
## resolve links - $0 may be a link to maven's home
|
||||
PRG="$0"
|
||||
|
||||
# need this for relative symlinks
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG="`dirname "$PRG"`/$link"
|
||||
fi
|
||||
done
|
||||
|
||||
saveddir=`pwd`
|
||||
|
||||
M2_HOME=`dirname "$PRG"`/..
|
||||
|
||||
# make it fully qualified
|
||||
M2_HOME=`cd "$M2_HOME" && pwd`
|
||||
|
||||
cd "$saveddir"
|
||||
# echo Using m2 at $M2_HOME
|
||||
fi
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||
if $cygwin ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --unix "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
|
||||
fi
|
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||
if $mingw ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME="`(cd "$M2_HOME"; pwd)`"
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
||||
# TODO classpath?
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
javaExecutable="`which javac`"
|
||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
|
||||
# readlink(1) is not available as standard on Solaris 10.
|
||||
readLink=`which readlink`
|
||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
|
||||
if $darwin ; then
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
|
||||
else
|
||||
javaExecutable="`readlink -f \"$javaExecutable\"`"
|
||||
fi
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
|
||||
JAVA_HOME="$javaHome"
|
||||
export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$JAVACMD" ] ; then
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
else
|
||||
JAVACMD="`which java`"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||
echo " We cannot execute $JAVACMD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
echo "Warning: JAVA_HOME environment variable is not set."
|
||||
fi
|
||||
|
||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Path not specified to find_maven_basedir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
basedir="$1"
|
||||
wdir="$1"
|
||||
while [ "$wdir" != '/' ] ; do
|
||||
if [ -d "$wdir"/.mvn ] ; then
|
||||
basedir=$wdir
|
||||
break
|
||||
fi
|
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||
if [ -d "${wdir}" ]; then
|
||||
wdir=`cd "$wdir/.."; pwd`
|
||||
fi
|
||||
# end of workaround
|
||||
done
|
||||
echo "${basedir}"
|
||||
}
|
||||
|
||||
# concatenates all lines of a file
|
||||
concat_lines() {
|
||||
if [ -f "$1" ]; then
|
||||
echo "$(tr -s '\n' ' ' < "$1")"
|
||||
fi
|
||||
}
|
||||
|
||||
BASE_DIR=`find_maven_basedir "$(pwd)"`
|
||||
if [ -z "$BASE_DIR" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
##########################################################################################
|
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
##########################################################################################
|
||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found .mvn/wrapper/maven-wrapper.jar"
|
||||
fi
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||
fi
|
||||
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
|
||||
while IFS="=" read key value; do
|
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||
esac
|
||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Downloading from: $jarUrl"
|
||||
fi
|
||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
|
||||
if command -v wget > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found wget ... using wget"
|
||||
fi
|
||||
wget "$jarUrl" -O "$wrapperJarPath"
|
||||
elif command -v curl > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found curl ... using curl"
|
||||
fi
|
||||
curl -o "$wrapperJarPath" "$jarUrl"
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Falling back to using Java to download"
|
||||
fi
|
||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
if [ -e "$javaClass" ]; then
|
||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Compiling MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
# Compiling the Java class
|
||||
("$JAVA_HOME/bin/javac" "$javaClass")
|
||||
fi
|
||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
# Running the downloader
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Running MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
##########################################################################################
|
||||
# End of extension
|
||||
##########################################################################################
|
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo $MAVEN_PROJECTBASEDIR
|
||||
fi
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --path --windows "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
|
||||
fi
|
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
@ -0,0 +1,161 @@
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM https://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven2 Start Up Batch script
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM M2_HOME - location of maven2's installed home dir
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM set title of command window
|
||||
title %0
|
||||
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
|
||||
FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
|
||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
||||
)
|
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
if exist %WRAPPER_JAR% (
|
||||
echo Found %WRAPPER_JAR%
|
||||
) else (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
echo Downloading from: %DOWNLOAD_URL%
|
||||
powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
|
||||
echo Finished downloading %WRAPPER_JAR%
|
||||
)
|
||||
@REM End of extension
|
||||
|
||||
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||
|
||||
exit /B %ERROR_CODE%
|
@ -0,0 +1,83 @@
|
||||
<?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.1.4.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>geektime.spring.springbucks</groupId>
|
||||
<artifactId>barista-service</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>barista-service</name>
|
||||
<description>Demo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</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>
|
@ -0,0 +1,18 @@
|
||||
package geektime.spring.springbucks.barista;
|
||||
|
||||
import geektime.spring.springbucks.barista.integration.Waiter;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
|
||||
@EnableJpaRepositories
|
||||
@SpringBootApplication
|
||||
@EnableBinding(Waiter.class)
|
||||
public class BaristaServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BaristaServiceApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package geektime.spring.springbucks.barista.integration;
|
||||
|
||||
import geektime.spring.springbucks.barista.model.CoffeeOrder;
|
||||
import geektime.spring.springbucks.barista.model.OrderState;
|
||||
import geektime.spring.springbucks.barista.repository.CoffeeOrderRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
@Transactional
|
||||
public class OrderListener {
|
||||
@Autowired
|
||||
private CoffeeOrderRepository orderRepository;
|
||||
@Autowired
|
||||
@Qualifier(Waiter.FINISHED_ORDERS)
|
||||
private MessageChannel finishedOrdersMessageChannel;
|
||||
@Value("${order.barista-prefix}${random.uuid}")
|
||||
private String barista;
|
||||
|
||||
@StreamListener(Waiter.NEW_ORDERS)
|
||||
@SendTo(Waiter.FINISHED_ORDERS)
|
||||
public Long processNewOrder(Long id) {
|
||||
CoffeeOrder o = orderRepository.getOne(id);
|
||||
if (o == null) {
|
||||
log.warn("Order id {} is NOT valid.", id);
|
||||
throw new IllegalArgumentException("Order ID is INVAILD!");
|
||||
}
|
||||
log.info("Receive a new Order {}. Waiter: {}. Customer: {}",
|
||||
id, o.getWaiter(), o.getCustomer());
|
||||
o.setState(OrderState.BREWED);
|
||||
o.setBarista(barista);
|
||||
orderRepository.save(o);
|
||||
log.info("Order {} is READY.", id);
|
||||
return id;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package geektime.spring.springbucks.barista.integration;
|
||||
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
public interface Waiter {
|
||||
String NEW_ORDERS = "newOrders";
|
||||
String FINISHED_ORDERS = "finishedOrders";
|
||||
|
||||
@Input(NEW_ORDERS)
|
||||
SubscribableChannel newOrders();
|
||||
|
||||
@Output(FINISHED_ORDERS)
|
||||
MessageChannel finishedOrders();
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package geektime.spring.springbucks.barista.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
@Entity
|
||||
@Table(name = "T_ORDER")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CoffeeOrder {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
private String customer;
|
||||
private String waiter;
|
||||
private String barista;
|
||||
@Enumerated
|
||||
@Column(nullable = false)
|
||||
private OrderState state;
|
||||
@Column(updatable = false)
|
||||
@CreationTimestamp
|
||||
private Date createTime;
|
||||
@UpdateTimestamp
|
||||
private Date updateTime;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package geektime.spring.springbucks.barista.model;
|
||||
|
||||
public enum OrderState {
|
||||
INIT, PAID, BREWING, BREWED, TAKEN, CANCELLED
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package geektime.spring.springbucks.barista.repository;
|
||||
|
||||
import geektime.spring.springbucks.barista.model.CoffeeOrder;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface CoffeeOrderRepository extends JpaRepository<CoffeeOrder, Long> {
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
spring.application.name=barista-service
|
||||
|
||||
order.barista-prefix=springbucks-
|
||||
|
||||
server.port=8070
|
||||
|
||||
management.endpoints.web.exposure.include=*
|
||||
management.endpoint.health.show-details=always
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=none
|
||||
spring.jpa.properties.hibernate.show_sql=true
|
||||
spring.jpa.properties.hibernate.format_sql=true
|
||||
|
||||
spring.datasource.url=jdbc:mysql://localhost/springbucks
|
||||
spring.datasource.username=springbucks
|
||||
spring.datasource.password=springbucks
|
||||
|
||||
spring.cloud.stream.kafka.binder.brokers=localhost
|
||||
spring.cloud.stream.kafka.binder.defaultBrokerPort=9092
|
||||
|
||||
spring.cloud.stream.bindings.newOrders.group=barista-service
|
@ -0,0 +1,16 @@
|
||||
package geektime.spring.springbucks.barista;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class BaristaServiceApplicationTests {
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
HELP.md
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/build/
|
@ -0,0 +1,19 @@
|
||||
---
|
||||
version: '2'
|
||||
services:
|
||||
zookeeper:
|
||||
image: zookeeper:latest
|
||||
|
||||
kafka:
|
||||
image: confluentinc/cp-kafka:latest
|
||||
depends_on:
|
||||
- zookeeper
|
||||
ports:
|
||||
- 9092:9092
|
||||
environment:
|
||||
KAFKA_BROKER_ID: 1
|
||||
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
||||
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
|
||||
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
|
||||
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
|
||||
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
@ -0,0 +1,125 @@
|
||||
<?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.1.4.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>geektime.spring.springbucks</groupId>
|
||||
<artifactId>waiter-service</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>waiter-service</name>
|
||||
<description>Demo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</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-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-consul-config</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-spring-boot2</artifactId>
|
||||
<version>0.14.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.joda</groupId>
|
||||
<artifactId>joda-money</artifactId>
|
||||
<version>1.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jadira.usertype</groupId>
|
||||
<artifactId>usertype.core</artifactId>
|
||||
<version>6.0.1.GA</version>
|
||||
</dependency>
|
||||
<!-- 增加Jackson的Hibernate类型支持 -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-hibernate5</artifactId>
|
||||
<version>2.9.8</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</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>
|
@ -0,0 +1,48 @@
|
||||
package geektime.spring.springbucks.waiter;
|
||||
|
||||
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
|
||||
import geektime.spring.springbucks.waiter.controller.PerformanceInteceptor;
|
||||
import geektime.spring.springbucks.waiter.integration.Barista;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.TimeZone;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableJpaRepositories
|
||||
@EnableCaching
|
||||
@EnableDiscoveryClient
|
||||
@EnableBinding(Barista.class)
|
||||
public class WaiterServiceApplication implements WebMvcConfigurer {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(WaiterServiceApplication.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new PerformanceInteceptor())
|
||||
.addPathPatterns("/coffee/**").addPathPatterns("/order/**");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Hibernate5Module hibernate5Module() {
|
||||
return new Hibernate5Module();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer jacksonBuilderCustomizer() {
|
||||
return builder -> {
|
||||
builder.indentOutput(true);
|
||||
builder.timeZone(TimeZone.getTimeZone("Asia/Shanghai"));
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package geektime.spring.springbucks.waiter.controller;
|
||||
|
||||
import geektime.spring.springbucks.waiter.controller.request.NewCoffeeRequest;
|
||||
import geektime.spring.springbucks.waiter.model.Coffee;
|
||||
import geektime.spring.springbucks.waiter.service.CoffeeService;
|
||||
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.apache.tomcat.util.http.fileupload.IOUtils;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/coffee")
|
||||
@RateLimiter(name = "coffee")
|
||||
@Slf4j
|
||||
public class CoffeeController {
|
||||
@Autowired
|
||||
private CoffeeService coffeeService;
|
||||
|
||||
@PostMapping(path = "/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Coffee addCoffeeWithoutBindingResult(@Valid NewCoffeeRequest newCoffee) {
|
||||
return coffeeService.saveCoffee(newCoffee.getName(), newCoffee.getPrice());
|
||||
}
|
||||
|
||||
@PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Coffee addJsonCoffeeWithoutBindingResult(@Valid @RequestBody NewCoffeeRequest newCoffee) {
|
||||
return coffeeService.saveCoffee(newCoffee.getName(), newCoffee.getPrice());
|
||||
}
|
||||
|
||||
@PostMapping(path = "/", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public List<Coffee> batchAddCoffee(@RequestParam("file") MultipartFile file) {
|
||||
List<Coffee> coffees = new ArrayList<>();
|
||||
if (!file.isEmpty()) {
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
reader = new BufferedReader(
|
||||
new InputStreamReader(file.getInputStream()));
|
||||
String str;
|
||||
while ((str = reader.readLine()) != null) {
|
||||
String[] arr = StringUtils.split(str, " ");
|
||||
if (arr != null && arr.length == 2) {
|
||||
coffees.add(coffeeService.saveCoffee(arr[0],
|
||||
Money.of(CurrencyUnit.of("CNY"),
|
||||
NumberUtils.createBigDecimal(arr[1]))));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("exception", e);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(reader);
|
||||
}
|
||||
}
|
||||
return coffees;
|
||||
}
|
||||
|
||||
@GetMapping(path = "/", params = "!name")
|
||||
public List<Coffee> getAll() {
|
||||
return coffeeService.getAllCoffee();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public Coffee getById(@PathVariable Long id) {
|
||||
Coffee coffee = coffeeService.getCoffee(id);
|
||||
log.info("Coffee {}:", coffee);
|
||||
return coffee;
|
||||
}
|
||||
|
||||
@GetMapping(path = "/", params = "name")
|
||||
public Coffee getByName(@RequestParam String name) {
|
||||
return coffeeService.getCoffee(name);
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package geektime.spring.springbucks.waiter.controller;
|
||||
|
||||
import geektime.spring.springbucks.waiter.controller.request.NewOrderRequest;
|
||||
import geektime.spring.springbucks.waiter.controller.request.OrderStateRequest;
|
||||
import geektime.spring.springbucks.waiter.model.Coffee;
|
||||
import geektime.spring.springbucks.waiter.model.CoffeeOrder;
|
||||
import geektime.spring.springbucks.waiter.service.CoffeeOrderService;
|
||||
import geektime.spring.springbucks.waiter.service.CoffeeService;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiter;
|
||||
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
|
||||
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/order")
|
||||
@Slf4j
|
||||
public class CoffeeOrderController {
|
||||
@Autowired
|
||||
private CoffeeOrderService orderService;
|
||||
@Autowired
|
||||
private CoffeeService coffeeService;
|
||||
private RateLimiter rateLimiter;
|
||||
|
||||
public CoffeeOrderController(RateLimiterRegistry rateLimiterRegistry) {
|
||||
rateLimiter = rateLimiterRegistry.rateLimiter("order");
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public CoffeeOrder getOrder(@PathVariable("id") Long id) {
|
||||
CoffeeOrder order = null;
|
||||
try {
|
||||
order = rateLimiter.executeSupplier(() -> orderService.get(id));
|
||||
log.info("Get Order: {}", order);
|
||||
} catch(RequestNotPermitted e) {
|
||||
log.warn("Request Not Permitted! {}", e.getMessage());
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
@PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
@io.github.resilience4j.ratelimiter.annotation.RateLimiter(name = "order")
|
||||
public CoffeeOrder create(@RequestBody NewOrderRequest newOrder) {
|
||||
log.info("Receive new Order {}", newOrder);
|
||||
Coffee[] coffeeList = coffeeService.getCoffeeByName(newOrder.getItems())
|
||||
.toArray(new Coffee[] {});
|
||||
return orderService.createOrder(newOrder.getCustomer(), coffeeList);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public CoffeeOrder updateState(@PathVariable("id") Long id,
|
||||
@RequestBody OrderStateRequest orderState) {
|
||||
log.info("Update order {} with state {}", id, orderState);
|
||||
CoffeeOrder order = orderService.get(id);
|
||||
orderService.updateState(order, orderState.getState());
|
||||
return order;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package geektime.spring.springbucks.waiter.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Slf4j
|
||||
public class PerformanceInteceptor implements HandlerInterceptor {
|
||||
private ThreadLocal<StopWatch> stopWatch = new ThreadLocal<>();
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
StopWatch sw = new StopWatch();
|
||||
stopWatch.set(sw);
|
||||
sw.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
stopWatch.get().stop();
|
||||
stopWatch.get().start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
StopWatch sw = stopWatch.get();
|
||||
sw.stop();
|
||||
String method = handler.getClass().getSimpleName();
|
||||
if (handler instanceof HandlerMethod) {
|
||||
String beanType = ((HandlerMethod) handler).getBeanType().getName();
|
||||
String methodName = ((HandlerMethod) handler).getMethod().getName();
|
||||
method = beanType + "." + methodName;
|
||||
}
|
||||
log.info("{};{};{};{};{}ms;{}ms;{}ms", request.getRequestURI(), method,
|
||||
response.getStatus(), ex == null ? "-" : ex.getClass().getSimpleName(),
|
||||
sw.getTotalTimeMillis(), sw.getTotalTimeMillis() - sw.getLastTaskTimeMillis(),
|
||||
sw.getLastTaskTimeMillis());
|
||||
stopWatch.remove();
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package geektime.spring.springbucks.waiter.controller.request;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.joda.money.Money;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class NewCoffeeRequest {
|
||||
@NotEmpty
|
||||
private String name;
|
||||
@NotNull
|
||||
private Money price;
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package geektime.spring.springbucks.waiter.controller.request;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class NewOrderRequest {
|
||||
private String customer;
|
||||
private List<String> items;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package geektime.spring.springbucks.waiter.controller.request;
|
||||
|
||||
import geektime.spring.springbucks.waiter.model.OrderState;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class OrderStateRequest {
|
||||
private OrderState state;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package geektime.spring.springbucks.waiter.integration;
|
||||
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
public interface Barista {
|
||||
String NEW_ORDERS = "newOrders";
|
||||
String FINISHED_ORDERS = "finishedOrders";
|
||||
|
||||
@Input
|
||||
SubscribableChannel finishedOrders();
|
||||
|
||||
@Output
|
||||
MessageChannel newOrders();
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package geektime.spring.springbucks.waiter.integration;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class OrderListener {
|
||||
@StreamListener(Barista.FINISHED_ORDERS)
|
||||
public void listenFinishedOrders(Long id) {
|
||||
log.info("We've finished an order [{}].", id);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package geektime.spring.springbucks.waiter.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@MappedSuperclass
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
// 增加了jackson-datatype-hibernate5就不需要这个Ignore了
|
||||
//@JsonIgnoreProperties(value = {"hibernateLazyInitializer"})
|
||||
public class BaseEntity implements Serializable {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
@Column(updatable = false)
|
||||
@CreationTimestamp
|
||||
private Date createTime;
|
||||
@UpdateTimestamp
|
||||
private Date updateTime;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package geektime.spring.springbucks.waiter.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.joda.money.Money;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Entity
|
||||
@Table(name = "T_COFFEE")
|
||||
@Builder
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Coffee extends BaseEntity implements Serializable {
|
||||
private String name;
|
||||
@Type(type = "org.jadira.usertype.moneyandcurrency.joda.PersistentMoneyMinorAmount",
|
||||
parameters = {@org.hibernate.annotations.Parameter(name = "currencyCode", value = "CNY")})
|
||||
private Money price;
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package geektime.spring.springbucks.waiter.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.joda.money.Money;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.JoinTable;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.OrderBy;
|
||||
import javax.persistence.Table;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(name = "T_ORDER")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class CoffeeOrder extends BaseEntity implements Serializable {
|
||||
private String customer;
|
||||
@ManyToMany
|
||||
@JoinTable(name = "T_ORDER_COFFEE")
|
||||
@OrderBy("id")
|
||||
private List<Coffee> items;
|
||||
@Enumerated
|
||||
@Column(nullable = false)
|
||||
private OrderState state;
|
||||
private Integer discount;
|
||||
@Type(type = "org.jadira.usertype.moneyandcurrency.joda.PersistentMoneyMinorAmount",
|
||||
parameters = {@org.hibernate.annotations.Parameter(name = "currencyCode", value = "CNY")})
|
||||
private Money total;
|
||||
private String waiter;
|
||||
private String barista;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package geektime.spring.springbucks.waiter.model;
|
||||
|
||||
public enum OrderState {
|
||||
INIT, PAID, BREWING, BREWED, TAKEN, CANCELLED
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package geektime.spring.springbucks.waiter.repository;
|
||||
|
||||
import geektime.spring.springbucks.waiter.model.CoffeeOrder;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface CoffeeOrderRepository extends JpaRepository<CoffeeOrder, Long> {
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package geektime.spring.springbucks.waiter.repository;
|
||||
|
||||
import geektime.spring.springbucks.waiter.model.Coffee;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface CoffeeRepository extends JpaRepository<Coffee, Long> {
|
||||
List<Coffee> findByNameInOrderById(List<String> list);
|
||||
Coffee findByName(String name);
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package geektime.spring.springbucks.waiter.service;
|
||||
|
||||
import geektime.spring.springbucks.waiter.integration.Barista;
|
||||
import geektime.spring.springbucks.waiter.model.Coffee;
|
||||
import geektime.spring.springbucks.waiter.model.CoffeeOrder;
|
||||
import geektime.spring.springbucks.waiter.model.OrderState;
|
||||
import geektime.spring.springbucks.waiter.repository.CoffeeOrderRepository;
|
||||
import geektime.spring.springbucks.waiter.support.OrderProperties;
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.joda.money.Money;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
@Slf4j
|
||||
public class CoffeeOrderService implements MeterBinder {
|
||||
@Autowired
|
||||
private CoffeeOrderRepository orderRepository;
|
||||
@Autowired
|
||||
private OrderProperties orderProperties;
|
||||
@Autowired
|
||||
private Barista barista;
|
||||
|
||||
private String waiterId = UUID.randomUUID().toString();
|
||||
|
||||
private Counter orderCounter = null;
|
||||
|
||||
public CoffeeOrder get(Long id) {
|
||||
return orderRepository.getOne(id);
|
||||
}
|
||||
|
||||
public CoffeeOrder createOrder(String customer, Coffee...coffee) {
|
||||
CoffeeOrder order = CoffeeOrder.builder()
|
||||
.customer(customer)
|
||||
.items(new ArrayList<>(Arrays.asList(coffee)))
|
||||
.discount(orderProperties.getDiscount())
|
||||
.total(calcTotal(coffee))
|
||||
.state(OrderState.INIT)
|
||||
.waiter(orderProperties.getWaiterPrefix() + waiterId)
|
||||
.build();
|
||||
CoffeeOrder saved = orderRepository.save(order);
|
||||
log.info("New Order: {}", saved);
|
||||
orderCounter.increment();
|
||||
return saved;
|
||||
}
|
||||
|
||||
public boolean updateState(CoffeeOrder order, OrderState state) {
|
||||
if (order == null) {
|
||||
log.warn("Can not find order.");
|
||||
return false;
|
||||
}
|
||||
if (state.compareTo(order.getState()) <= 0) {
|
||||
log.warn("Wrong State order: {}, {}", state, order.getState());
|
||||
return false;
|
||||
}
|
||||
order.setState(state);
|
||||
orderRepository.save(order);
|
||||
log.info("Updated Order: {}", order);
|
||||
if (state == OrderState.PAID) {
|
||||
// 有返回值,如果要关注发送结果,则判断返回值
|
||||
// 一般消息体不会这么简单
|
||||
barista.newOrders().send(MessageBuilder.withPayload(order.getId()).build());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTo(MeterRegistry meterRegistry) {
|
||||
this.orderCounter = meterRegistry.counter("order.count");
|
||||
}
|
||||
|
||||
private Money calcTotal(Coffee...coffee) {
|
||||
List<Money> items = Stream.of(coffee).map(c -> c.getPrice())
|
||||
.collect(Collectors.toList());
|
||||
return Money.total(items).multipliedBy(orderProperties.getDiscount())
|
||||
.dividedBy(100, RoundingMode.HALF_UP);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package geektime.spring.springbucks.waiter.service;
|
||||
|
||||
import geektime.spring.springbucks.waiter.model.Coffee;
|
||||
import geektime.spring.springbucks.waiter.repository.CoffeeRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.joda.money.Money;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheConfig;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@CacheConfig(cacheNames = "CoffeeCache")
|
||||
public class CoffeeService {
|
||||
@Autowired
|
||||
private CoffeeRepository coffeeRepository;
|
||||
|
||||
public Coffee saveCoffee(String name, Money price) {
|
||||
return coffeeRepository.save(Coffee.builder().name(name).price(price).build());
|
||||
}
|
||||
|
||||
@Cacheable
|
||||
public List<Coffee> getAllCoffee() {
|
||||
return coffeeRepository.findAll(Sort.by("id"));
|
||||
}
|
||||
|
||||
public Coffee getCoffee(Long id) {
|
||||
// return coffeeRepository.findById(id).get();
|
||||
return coffeeRepository.getOne(id);
|
||||
}
|
||||
|
||||
public long getCoffeeCount() {
|
||||
return coffeeRepository.count();
|
||||
}
|
||||
|
||||
public Coffee getCoffee(String name) {
|
||||
return coffeeRepository.findByName(name);
|
||||
}
|
||||
|
||||
public List<Coffee> getCoffeeByName(List<String> names) {
|
||||
return coffeeRepository.findByNameInOrderById(names);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package geektime.spring.springbucks.waiter.support;
|
||||
|
||||
import geektime.spring.springbucks.waiter.service.CoffeeService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class CoffeeIndicator implements HealthIndicator {
|
||||
@Autowired
|
||||
private CoffeeService coffeeService;
|
||||
|
||||
@Override
|
||||
public Health health() {
|
||||
long count = coffeeService.getCoffeeCount();
|
||||
Health health;
|
||||
if (count > 0) {
|
||||
health = Health.up()
|
||||
.withDetail("count", count)
|
||||
.withDetail("message", "We have enough coffee.")
|
||||
.build();
|
||||
} else {
|
||||
health = Health.down()
|
||||
.withDetail("count", 0)
|
||||
.withDetail("message", "We are out of coffee.")
|
||||
.build();
|
||||
}
|
||||
return health;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package geektime.spring.springbucks.waiter.support;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.springframework.boot.jackson.JsonComponent;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@JsonComponent
|
||||
public class MoneyDeserializer extends StdDeserializer<Money> {
|
||||
protected MoneyDeserializer() {
|
||||
super(Money.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Money deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
return Money.of(CurrencyUnit.of("CNY"), p.getDecimalValue());
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package geektime.spring.springbucks.waiter.support;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import org.joda.money.Money;
|
||||
import org.springframework.boot.jackson.JsonComponent;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@JsonComponent
|
||||
public class MoneySerializer extends StdSerializer<Money> {
|
||||
protected MoneySerializer() {
|
||||
super(Money.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Money money, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
|
||||
jsonGenerator.writeNumber(money.getAmount());
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package geektime.spring.springbucks.waiter.support;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@ConfigurationProperties("order")
|
||||
@RefreshScope
|
||||
@Data
|
||||
@Component
|
||||
public class OrderProperties {
|
||||
private Integer discount = 100;
|
||||
private String waiterPrefix = "springbucks-";
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
spring.jpa.hibernate.ddl-auto=none
|
||||
spring.jpa.properties.hibernate.show_sql=false
|
||||
spring.jpa.properties.hibernate.format_sql=false
|
||||
|
||||
# 运行过一次后,如果不想清空数据库就注释掉下面这行
|
||||
spring.datasource.initialization-mode=always
|
||||
|
||||
management.endpoints.web.exposure.include=*
|
||||
management.endpoint.health.show-details=always
|
||||
|
||||
info.app.author=DigitalSonic
|
||||
info.app.encoding=@project.build.sourceEncoding@
|
||||
|
||||
server.port=8080
|
||||
|
||||
spring.datasource.url=jdbc:mysql://localhost/springbucks
|
||||
spring.datasource.username=springbucks
|
||||
spring.datasource.password=springbucks
|
||||
|
||||
order.discount=95
|
||||
|
||||
resilience4j.ratelimiter.limiters.coffee.limit-for-period=5
|
||||
resilience4j.ratelimiter.limiters.coffee.limit-refresh-period-in-millis=30000
|
||||
resilience4j.ratelimiter.limiters.coffee.timeout-in-millis=5000
|
||||
resilience4j.ratelimiter.limiters.coffee.subscribe-for-events=true
|
||||
resilience4j.ratelimiter.limiters.coffee.register-health-indicator=true
|
||||
|
||||
resilience4j.ratelimiter.limiters.order.limit-for-period=3
|
||||
resilience4j.ratelimiter.limiters.order.limit-refresh-period-in-millis=30000
|
||||
resilience4j.ratelimiter.limiters.order.timeout-in-millis=1000
|
||||
resilience4j.ratelimiter.limiters.order.subscribe-for-events=true
|
||||
resilience4j.ratelimiter.limiters.order.register-health-indicator=true
|
||||
|
||||
spring.cloud.stream.kafka.binder.brokers=localhost
|
||||
spring.cloud.stream.kafka.binder.defaultBrokerPort=9092
|
||||
|
||||
spring.cloud.stream.bindings.finishedOrders.group=waiter-service
|
@ -0,0 +1,8 @@
|
||||
spring.application.name=waiter-service
|
||||
|
||||
spring.cloud.consul.host=localhost
|
||||
spring.cloud.consul.port=8500
|
||||
spring.cloud.consul.discovery.prefer-ip-address=true
|
||||
|
||||
spring.cloud.consul.config.enabled=true
|
||||
spring.cloud.consul.config.format=yaml
|
@ -0,0 +1,2 @@
|
||||
Americano 25.0
|
||||
Italian 30.0
|
@ -0,0 +1,5 @@
|
||||
insert into t_coffee (name, price, create_time, update_time) values ('espresso', 2000, now(), now());
|
||||
insert into t_coffee (name, price, create_time, update_time) values ('latte', 2500, now(), now());
|
||||
insert into t_coffee (name, price, create_time, update_time) values ('capuccino', 2500, now(), now());
|
||||
insert into t_coffee (name, price, create_time, update_time) values ('mocha', 3000, now(), now());
|
||||
insert into t_coffee (name, price, create_time, update_time) values ('macchiato', 3000, now(), now());
|
@ -0,0 +1,33 @@
|
||||
# 注意 MySQL 与 H2 的语法差异
|
||||
# H2: drop table tbl if exists;
|
||||
# MySQL: drop table if exists tbl;
|
||||
drop table if exists t_coffee;
|
||||
drop table if exists t_order;
|
||||
drop table if exists t_order_coffee;
|
||||
|
||||
create table t_coffee (
|
||||
id bigint auto_increment,
|
||||
create_time timestamp,
|
||||
update_time timestamp,
|
||||
name varchar(255),
|
||||
price bigint,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table t_order (
|
||||
id bigint auto_increment,
|
||||
create_time timestamp,
|
||||
update_time timestamp,
|
||||
customer varchar(255),
|
||||
waiter varchar(255),
|
||||
barista varchar(255),
|
||||
discount integer,
|
||||
total bigint,
|
||||
state integer,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table t_order_coffee (
|
||||
coffee_order_id bigint not null,
|
||||
items_id bigint not null
|
||||
);
|
@ -0,0 +1,16 @@
|
||||
package geektime.spring.springbucks.waiter;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class WaiterServiceApplicationTests {
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
HELP.md
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/build/
|
@ -0,0 +1,104 @@
|
||||
<?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.1.4.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>geektime.spring.springbucks</groupId>
|
||||
<artifactId>customer-service</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>customer-service</name>
|
||||
<description>Demo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-spring-boot2</artifactId>
|
||||
<version>0.14.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.joda</groupId>
|
||||
<artifactId>joda-money</artifactId>
|
||||
<version>1.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.7</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</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>
|
@ -0,0 +1,41 @@
|
||||
package geektime.spring.springbucks.customer;
|
||||
|
||||
import geektime.spring.springbucks.customer.integration.Waiter;
|
||||
import geektime.spring.springbucks.customer.support.CustomConnectionKeepAliveStrategy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@SpringBootApplication
|
||||
@Slf4j
|
||||
@EnableDiscoveryClient
|
||||
@EnableFeignClients
|
||||
@EnableAspectJAutoProxy
|
||||
@EnableBinding(Waiter.class)
|
||||
public class CustomerServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CustomerServiceApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CloseableHttpClient httpClient() {
|
||||
return HttpClients.custom()
|
||||
.setConnectionTimeToLive(30, TimeUnit.SECONDS)
|
||||
.evictIdleConnections(30, TimeUnit.SECONDS)
|
||||
.setMaxConnTotal(200)
|
||||
.setMaxConnPerRoute(20)
|
||||
.disableAutomaticRetries()
|
||||
.setKeepAliveStrategy(new CustomConnectionKeepAliveStrategy())
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package geektime.spring.springbucks.customer.controller;
|
||||
|
||||
import geektime.spring.springbucks.customer.integration.CoffeeOrderService;
|
||||
import geektime.spring.springbucks.customer.integration.CoffeeService;
|
||||
import geektime.spring.springbucks.customer.model.Coffee;
|
||||
import geektime.spring.springbucks.customer.model.CoffeeOrder;
|
||||
import geektime.spring.springbucks.customer.model.NewOrderRequest;
|
||||
import geektime.spring.springbucks.customer.model.OrderState;
|
||||
import geektime.spring.springbucks.customer.model.OrderStateRequest;
|
||||
import io.github.resilience4j.bulkhead.Bulkhead;
|
||||
import io.github.resilience4j.bulkhead.BulkheadFullException;
|
||||
import io.github.resilience4j.bulkhead.BulkheadRegistry;
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreakerOpenException;
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
|
||||
import io.vavr.control.Try;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/customer")
|
||||
@Slf4j
|
||||
public class CustomerController {
|
||||
@Autowired
|
||||
private CoffeeService coffeeService;
|
||||
@Autowired
|
||||
private CoffeeOrderService coffeeOrderService;
|
||||
@Value("${customer.name}")
|
||||
private String customer;
|
||||
private CircuitBreaker circuitBreaker;
|
||||
private Bulkhead bulkhead;
|
||||
|
||||
public CustomerController(CircuitBreakerRegistry circuitBreakerRegistry,
|
||||
BulkheadRegistry bulkheadRegistry) {
|
||||
circuitBreaker = circuitBreakerRegistry.circuitBreaker("menu");
|
||||
bulkhead = bulkheadRegistry.bulkhead("menu");
|
||||
}
|
||||
|
||||
@GetMapping("/menu")
|
||||
public List<Coffee> readMenu() {
|
||||
return Try.ofSupplier(
|
||||
Bulkhead.decorateSupplier(bulkhead,
|
||||
CircuitBreaker.decorateSupplier(circuitBreaker,
|
||||
() -> coffeeService.getAll())))
|
||||
.recover(CircuitBreakerOpenException.class, Collections.emptyList())
|
||||
.recover(BulkheadFullException.class, Collections.emptyList())
|
||||
.get();
|
||||
}
|
||||
|
||||
@PostMapping("/order")
|
||||
@io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker(name = "order")
|
||||
@io.github.resilience4j.bulkhead.annotation.Bulkhead(name = "order")
|
||||
public CoffeeOrder createAndPayOrder() {
|
||||
NewOrderRequest orderRequest = NewOrderRequest.builder()
|
||||
.customer(customer)
|
||||
.items(Arrays.asList("capuccino"))
|
||||
.build();
|
||||
CoffeeOrder order = coffeeOrderService.create(orderRequest);
|
||||
log.info("Create order: {}", order != null ? order.getId() : "-");
|
||||
order = coffeeOrderService.updateState(order.getId(),
|
||||
OrderStateRequest.builder().state(OrderState.PAID).build());
|
||||
log.info("Order is PAID: {}", order);
|
||||
return order;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package geektime.spring.springbucks.customer.integration;
|
||||
|
||||
import geektime.spring.springbucks.customer.model.CoffeeOrder;
|
||||
import geektime.spring.springbucks.customer.model.NewOrderRequest;
|
||||
import geektime.spring.springbucks.customer.model.OrderStateRequest;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
@FeignClient(name = "waiter-service", contextId = "coffeeOrder")
|
||||
public interface CoffeeOrderService {
|
||||
@GetMapping("/order/{id}")
|
||||
CoffeeOrder getOrder(@PathVariable("id") Long id);
|
||||
|
||||
@PostMapping(path = "/order/", consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
CoffeeOrder create(@RequestBody NewOrderRequest newOrder);
|
||||
|
||||
@PutMapping("/order/{id}")
|
||||
CoffeeOrder updateState(@PathVariable("id") Long id,
|
||||
@RequestBody OrderStateRequest orderState);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package geektime.spring.springbucks.customer.integration;
|
||||
|
||||
import geektime.spring.springbucks.customer.model.Coffee;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@FeignClient(name = "waiter-service", contextId = "coffee", path = "/coffee")
|
||||
public interface CoffeeService {
|
||||
@GetMapping(path = "/", params = "!name")
|
||||
List<Coffee> getAll();
|
||||
|
||||
@GetMapping("/{id}")
|
||||
Coffee getById(@PathVariable Long id);
|
||||
|
||||
@GetMapping(path = "/", params = "name")
|
||||
Coffee getByName(@RequestParam String name);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package geektime.spring.springbucks.customer.integration;
|
||||
|
||||
import geektime.spring.springbucks.customer.model.CoffeeOrder;
|
||||
import geektime.spring.springbucks.customer.model.OrderState;
|
||||
import geektime.spring.springbucks.customer.model.OrderStateRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class NotificationListener {
|
||||
@Autowired
|
||||
private CoffeeOrderService orderService;
|
||||
@Value("${customer.name}")
|
||||
private String customer;
|
||||
|
||||
@StreamListener(Waiter.NOTIFY_ORDERS)
|
||||
public void takeOrder(@Payload Long id) {
|
||||
CoffeeOrder order = orderService.getOrder(id);
|
||||
if (OrderState.BREWED == order.getState()) {
|
||||
log.info("Order {} is READY, I'll take it.", id);
|
||||
orderService.updateState(id,
|
||||
OrderStateRequest.builder().state(OrderState.TAKEN).build());
|
||||
} else {
|
||||
log.warn("Order {} is NOT READY. Why are you notify me?", id);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package geektime.spring.springbucks.customer.integration;
|
||||
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
public interface Waiter {
|
||||
String NOTIFY_ORDERS = "notifyOrders";
|
||||
|
||||
@Input(NOTIFY_ORDERS)
|
||||
SubscribableChannel notification();
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package geektime.spring.springbucks.customer.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.joda.money.Money;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Coffee implements Serializable {
|
||||
private Long id;
|
||||
private String name;
|
||||
private Money price;
|
||||
private Date createTime;
|
||||
private Date updateTime;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package geektime.spring.springbucks.customer.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.joda.money.Money;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CoffeeOrder {
|
||||
private Long id;
|
||||
private String customer;
|
||||
private List<Coffee> items;
|
||||
private OrderState state;
|
||||
private String waiter;
|
||||
private String barista;
|
||||
private Integer discount;
|
||||
private Money total;
|
||||
private Date createTime;
|
||||
private Date updateTime;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package geektime.spring.springbucks.customer.model;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
@Setter
|
||||
public class NewOrderRequest {
|
||||
private String customer;
|
||||
private List<String> items;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package geektime.spring.springbucks.customer.model;
|
||||
|
||||
public enum OrderState {
|
||||
INIT, PAID, BREWING, BREWED, TAKEN, CANCELLED
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package geektime.spring.springbucks.customer.model;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@Builder
|
||||
public class OrderStateRequest {
|
||||
private OrderState state;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package geektime.spring.springbucks.customer.support;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.conn.ConnectionKeepAliveStrategy;
|
||||
import org.apache.http.protocol.HTTP;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class CustomConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy {
|
||||
private final long DEFAULT_SECONDS = 30;
|
||||
|
||||
@Override
|
||||
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
|
||||
return Arrays.asList(response.getHeaders(HTTP.CONN_KEEP_ALIVE))
|
||||
.stream()
|
||||
.filter(h -> StringUtils.equalsIgnoreCase(h.getName(), "timeout")
|
||||
&& StringUtils.isNumeric(h.getValue()))
|
||||
.findFirst()
|
||||
.map(h -> NumberUtils.toLong(h.getValue(), DEFAULT_SECONDS))
|
||||
.orElse(DEFAULT_SECONDS) * 1000;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package geektime.spring.springbucks.customer.support;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.springframework.boot.jackson.JsonComponent;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@JsonComponent
|
||||
public class MoneyDeserializer extends StdDeserializer<Money> {
|
||||
protected MoneyDeserializer() {
|
||||
super(Money.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Money deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
return Money.of(CurrencyUnit.of("CNY"), p.getDecimalValue());
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package geektime.spring.springbucks.customer.support;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import org.joda.money.Money;
|
||||
import org.springframework.boot.jackson.JsonComponent;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@JsonComponent
|
||||
public class MoneySerializer extends StdSerializer<Money> {
|
||||
protected MoneySerializer() {
|
||||
super(Money.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Money money, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
|
||||
jsonGenerator.writeNumber(money.getAmount());
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package geektime.spring.springbucks.customer.support;
|
||||
|
||||
import geektime.spring.springbucks.customer.model.CoffeeOrder;
|
||||
import lombok.Data;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
@Data
|
||||
public class OrderWaitingEvent extends ApplicationEvent {
|
||||
private CoffeeOrder order;
|
||||
|
||||
public OrderWaitingEvent(CoffeeOrder order) {
|
||||
super(order);
|
||||
this.order = order;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
server.port=0
|
||||
|
||||
customer.name=spring-${server.port}
|
||||
|
||||
management.endpoints.web.exposure.include=*
|
||||
management.endpoint.health.show-details=always
|
||||
|
||||
spring.output.ansi.enabled=ALWAYS
|
||||
|
||||
feign.client.config.default.connect-timeout=500
|
||||
feign.client.config.default.read-timeout=500
|
||||
|
||||
spring.cloud.consul.host=localhost
|
||||
spring.cloud.consul.port=8500
|
||||
spring.cloud.consul.discovery.prefer-ip-address=true
|
||||
|
||||
resilience4j.circuitbreaker.backends.order.failure-rate-threshold=50
|
||||
resilience4j.circuitbreaker.backends.order.wait-duration-in-open-state=5000
|
||||
resilience4j.circuitbreaker.backends.order.ring-buffer-size-in-closed-state=5
|
||||
resilience4j.circuitbreaker.backends.order.ring-buffer-size-in-half-open-state=3
|
||||
resilience4j.circuitbreaker.backends.order.event-consumer-buffer-size=10
|
||||
|
||||
resilience4j.circuitbreaker.backends.menu.failure-rate-threshold=50
|
||||
resilience4j.circuitbreaker.backends.menu.wait-duration-in-open-state=5000
|
||||
resilience4j.circuitbreaker.backends.menu.ring-buffer-size-in-closed-state=5
|
||||
resilience4j.circuitbreaker.backends.menu.ring-buffer-size-in-half-open-state=3
|
||||
resilience4j.circuitbreaker.backends.menu.event-consumer-buffer-size=10
|
||||
|
||||
resilience4j.bulkhead.backends.order.max-concurrent-call=1
|
||||
resilience4j.bulkhead.backends.order.max-wait-time=5
|
||||
|
||||
resilience4j.bulkhead.backends.menu.max-concurrent-call=5
|
||||
resilience4j.bulkhead.backends.menu.max-wait-time=5
|
||||
|
||||
spring.rabbitmq.host=localhost
|
||||
spring.rabbitmq.port=5672
|
||||
spring.rabbitmq.username=spring
|
||||
spring.rabbitmq.password=spring
|
||||
|
||||
spring.cloud.stream.rabbit.bindings.notifyOrders.consumer.binding-routing-key=${customer.name}
|
@ -0,0 +1 @@
|
||||
spring.application.name=customer-service
|
@ -0,0 +1,16 @@
|
||||
package geektime.spring.springbucks.customer;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class CustomerServiceApplicationTests {
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
HELP.md
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
@ -0,0 +1,83 @@
|
||||
<?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.1.4.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>geektime.spring.springbucks</groupId>
|
||||
<artifactId>barista-service</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>barista-service</name>
|
||||
<description>Demo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</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>
|
@ -0,0 +1,18 @@
|
||||
package geektime.spring.springbucks.barista;
|
||||
|
||||
import geektime.spring.springbucks.barista.integration.Waiter;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
|
||||
@EnableJpaRepositories
|
||||
@SpringBootApplication
|
||||
@EnableBinding(Waiter.class)
|
||||
public class BaristaServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BaristaServiceApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package geektime.spring.springbucks.barista.integration;
|
||||
|
||||
import geektime.spring.springbucks.barista.model.CoffeeOrder;
|
||||
import geektime.spring.springbucks.barista.model.OrderState;
|
||||
import geektime.spring.springbucks.barista.repository.CoffeeOrderRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
@Transactional
|
||||
public class OrderListener {
|
||||
@Autowired
|
||||
private CoffeeOrderRepository orderRepository;
|
||||
@Autowired
|
||||
@Qualifier(Waiter.FINISHED_ORDERS)
|
||||
private MessageChannel finishedOrdersMessageChannel;
|
||||
@Value("${order.barista-prefix}${random.uuid}")
|
||||
private String barista;
|
||||
|
||||
@StreamListener(Waiter.NEW_ORDERS)
|
||||
public void processNewOrder(Long id) {
|
||||
CoffeeOrder o = orderRepository.getOne(id);
|
||||
if (o == null) {
|
||||
log.warn("Order id {} is NOT valid.", id);
|
||||
return;
|
||||
}
|
||||
log.info("Receive a new Order {}. Waiter: {}. Customer: {}",
|
||||
id, o.getWaiter(), o.getCustomer());
|
||||
o.setState(OrderState.BREWED);
|
||||
o.setBarista(barista);
|
||||
orderRepository.save(o);
|
||||
log.info("Order {} is READY.", id);
|
||||
finishedOrdersMessageChannel.send(MessageBuilder.withPayload(id).build());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package geektime.spring.springbucks.barista.integration;
|
||||
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
public interface Waiter {
|
||||
String NEW_ORDERS = "newOrders";
|
||||
String FINISHED_ORDERS = "finishedOrders";
|
||||
|
||||
@Input(NEW_ORDERS)
|
||||
SubscribableChannel newOrders();
|
||||
|
||||
@Output(FINISHED_ORDERS)
|
||||
MessageChannel finishedOrders();
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package geektime.spring.springbucks.barista.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
@Entity
|
||||
@Table(name = "T_ORDER")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CoffeeOrder {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
private String customer;
|
||||
private String waiter;
|
||||
private String barista;
|
||||
@Enumerated
|
||||
@Column(nullable = false)
|
||||
private OrderState state;
|
||||
@Column(updatable = false)
|
||||
@CreationTimestamp
|
||||
private Date createTime;
|
||||
@UpdateTimestamp
|
||||
private Date updateTime;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue