├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── uc ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── src │ ├── main │ │ ├── java │ │ │ └── club │ │ │ │ └── newtech │ │ │ │ └── qbike │ │ │ │ └── uc │ │ │ │ ├── domain │ │ │ │ ├── Type.java │ │ │ │ ├── repository │ │ │ │ │ ├── PoiRepository.java │ │ │ │ │ └── UserRepository.java │ │ │ │ └── root │ │ │ │ │ ├── User.java │ │ │ │ │ └── Poi.java │ │ │ │ └── UcApplication.java │ │ └── resources │ │ │ ├── application-docker.yml │ │ │ └── application.yml │ └── test │ │ └── java │ │ └── club │ │ └── newtech │ │ └── qbike │ │ └── uc │ │ ├── UcApplicationTests.java │ │ └── TableTransfer.java ├── .gitignore ├── pom.xml ├── mvnw.cmd └── mvnw ├── order ├── src │ ├── main │ │ ├── resources │ │ │ ├── messages.properties │ │ │ ├── application.yml │ │ │ └── application-docker.yml │ │ └── java │ │ │ └── club │ │ │ └── newtech │ │ │ └── qbike │ │ │ └── order │ │ │ ├── domain │ │ │ ├── core │ │ │ │ ├── vo │ │ │ │ │ ├── Events.java │ │ │ │ │ ├── DriverVo.java │ │ │ │ │ ├── CustomerVo.java │ │ │ │ │ ├── IntentionVo.java │ │ │ │ │ ├── StateRequest.java │ │ │ │ │ └── FlowState.java │ │ │ │ └── root │ │ │ │ │ └── Order.java │ │ │ ├── service │ │ │ │ ├── InnerService.java │ │ │ │ ├── ErrorAction.java │ │ │ │ ├── StateMachineBuilderFactory.java │ │ │ │ ├── Receiver.java │ │ │ │ ├── FsmService.java │ │ │ │ ├── OrderService.java │ │ │ │ └── OrderStateMachineBuilder.java │ │ │ ├── repository │ │ │ │ └── OrderRepository.java │ │ │ └── exception │ │ │ │ └── OrderRuntimeException.java │ │ │ ├── RabbitConfig.java │ │ │ ├── util │ │ │ ├── AsyncTaskInitializer.java │ │ │ ├── SpringContextHolder.java │ │ │ └── SequenceFactory.java │ │ │ ├── SpringAsyncConfig.java │ │ │ ├── controller │ │ │ └── OrderController.java │ │ │ ├── infrastructure │ │ │ └── UserRibbonHystrixApi.java │ │ │ └── OrderApplication.java │ └── test │ │ └── java │ │ └── club │ │ └── newtech │ │ └── qbike │ │ └── order │ │ └── OrderTest.java └── pom.xml ├── position ├── src │ └── main │ │ ├── java │ │ └── club │ │ │ └── newtech │ │ │ └── qbike │ │ │ └── trip │ │ │ ├── domain │ │ │ ├── core │ │ │ │ ├── Status.java │ │ │ │ ├── vo │ │ │ │ │ ├── Driver.java │ │ │ │ │ └── Position.java │ │ │ │ └── root │ │ │ │ │ └── DriverStatus.java │ │ │ ├── repository │ │ │ │ ├── PositionRepository.java │ │ │ │ └── DriverStatusRepo.java │ │ │ └── service │ │ │ │ └── PositionService.java │ │ │ ├── PositionApplication.java │ │ │ ├── controller │ │ │ └── PositionController.java │ │ │ └── infrastructure │ │ │ └── UserRibbonHystrixApi.java │ │ └── resources │ │ ├── application.yml │ │ └── application-docker.yml └── pom.xml ├── zipkin-server └── run-zipkin.sh ├── intention ├── src │ ├── main │ │ ├── java │ │ │ └── club │ │ │ │ └── newtech │ │ │ │ └── qbike │ │ │ │ └── intention │ │ │ │ ├── domain │ │ │ │ ├── core │ │ │ │ │ ├── vo │ │ │ │ │ │ ├── Status.java │ │ │ │ │ │ ├── Customer.java │ │ │ │ │ │ ├── DriverVo.java │ │ │ │ │ │ ├── DriverStatusVo.java │ │ │ │ │ │ ├── Candidate.java │ │ │ │ │ │ ├── Lock.java │ │ │ │ │ │ └── IntentionTask.java │ │ │ │ │ └── root │ │ │ │ │ │ └── Intention.java │ │ │ │ ├── exception │ │ │ │ │ ├── LockException.java │ │ │ │ │ ├── LockExistsException.java │ │ │ │ │ ├── LockNotHeldException.java │ │ │ │ │ └── NoSuchLockException.java │ │ │ │ ├── repository │ │ │ │ │ ├── CandidateRepository.java │ │ │ │ │ └── IntentionRepository.java │ │ │ │ └── service │ │ │ │ │ ├── AsyncTaskInitializer.java │ │ │ │ │ ├── LockService.java │ │ │ │ │ ├── RedisLockService.java │ │ │ │ │ └── IntentionService.java │ │ │ │ ├── controller │ │ │ │ ├── bean │ │ │ │ │ ├── MyIntention.java │ │ │ │ │ └── IntentionVo.java │ │ │ │ └── RibbonHystrixController.java │ │ │ │ ├── RabbitConfig.java │ │ │ │ ├── SpringAsyncConfig.java │ │ │ │ ├── IntentionApplication.java │ │ │ │ └── infrastructure │ │ │ │ ├── PositionApi.java │ │ │ │ └── UserRibbonHystrixApi.java │ │ └── resources │ │ │ ├── application.yml │ │ │ └── application-docker.yml │ └── test │ │ └── java │ │ └── club │ │ └── newtech │ │ └── qbike │ │ └── intention │ │ └── IntentionTest.java └── pom.xml ├── microservice-discovery-eureka ├── Dockerfile ├── src │ └── main │ │ ├── docker │ │ └── Dockerfile │ │ ├── resources │ │ ├── application.yml │ │ └── application-docker.yml │ │ └── java │ │ └── club │ │ └── newtech │ │ └── qbike │ │ └── EurekaApplication.java └── pom.xml ├── testclient ├── src │ ├── test │ │ ├── resources │ │ │ └── application.yml │ │ └── java │ │ │ └── club │ │ │ └── newtech │ │ │ └── qbike │ │ │ └── client │ │ │ ├── GeoPositionUpdateTest.java │ │ │ └── UserRequestIntention.java │ └── main │ │ └── java │ │ └── club │ │ └── newtech │ │ └── qbike │ │ └── client │ │ ├── bean │ │ ├── MyIntention.java │ │ └── User.java │ │ ├── api │ │ ├── PositionApi.java │ │ └── UserApi.java │ │ └── RestClientApplication.java └── pom.xml ├── .gitignore ├── api-gateway ├── src │ └── main │ │ ├── java │ │ └── club │ │ │ └── newtech │ │ │ └── qbick │ │ │ └── gateway │ │ │ └── ZuulGateway.java │ │ └── resources │ │ ├── application.yml │ │ └── application-docker.yml └── pom.xml ├── mysql └── config.cnf ├── README.md ├── qbike.postman_collection.json ├── docker-compose.yml ├── pom.xml └── wait-for-it.sh /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoeCao/qbike/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /uc/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoeCao/qbike/HEAD/uc/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /order/src/main/resources/messages.properties: -------------------------------------------------------------------------------- 1 | 010003=状态迁移失败 2 | 030001=订单不存在 3 | 040002=只有乘客本人才能操作 4 | 040003=只有司机本人才能操作 5 | 040001=订单超过3分钟无法取消 -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip 2 | -------------------------------------------------------------------------------- /uc/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip 2 | -------------------------------------------------------------------------------- /uc/src/main/java/club/newtech/qbike/uc/domain/Type.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.uc.domain; 2 | 3 | public enum Type { 4 | Customer, Driver 5 | } 6 | -------------------------------------------------------------------------------- /position/src/main/java/club/newtech/qbike/trip/domain/core/Status.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.trip.domain.core; 2 | 3 | public enum Status { 4 | BUSY, OFFLINE, ONLINE 5 | } 6 | -------------------------------------------------------------------------------- /zipkin-server/run-zipkin.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [ ! -f "zipkin.jar" ]; then 3 | curl -sSL https://zipkin.io/quickstart.sh | bash -s 4 | fi 5 | RABBIT_ADDRESSES=localhost java -jar zipkin.jar -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/core/vo/Status.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.core.vo; 2 | 3 | public enum Status { 4 | Inited, UnConfirmed, Confirmed, Failed 5 | } 6 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/core/vo/Events.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.core.vo; 2 | 3 | public enum Events { 4 | ABOARD, ARRIVE, PAY, PAY_CALLBACK, COMMENT, CANCEL 5 | } 6 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/service/InnerService.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.service; 2 | 3 | import javax.transaction.Transactional; 4 | 5 | @Transactional 6 | public class InnerService { 7 | } 8 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/exception/LockException.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.exception; 2 | 3 | @SuppressWarnings("serial") 4 | public class LockException extends RuntimeException { 5 | 6 | } -------------------------------------------------------------------------------- /microservice-discovery-eureka/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jdk-alpine 2 | VOLUME /tmp 3 | ARG JAR_FILE 4 | COPY ${JAR_FILE} app.jar 5 | ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar", "--spring.profiles.active=docker"] 6 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/exception/LockExistsException.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.exception; 2 | 3 | @SuppressWarnings("serial") 4 | public class LockExistsException extends LockException { 5 | 6 | } -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/exception/LockNotHeldException.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.exception; 2 | 3 | @SuppressWarnings("serial") 4 | public class LockNotHeldException extends LockException { 5 | 6 | } -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/exception/NoSuchLockException.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.exception; 2 | 3 | @SuppressWarnings("serial") 4 | public class NoSuchLockException extends LockException { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /testclient/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: qbike-testclient 4 | rabbitmq: 5 | host: localhost 6 | port: 5672 7 | username: guest 8 | password: guest 9 | zipkin: 10 | rabbitmq: 11 | queue: zipkin -------------------------------------------------------------------------------- /uc/src/main/java/club/newtech/qbike/uc/domain/repository/PoiRepository.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.uc.domain.repository; 2 | 3 | import club.newtech.qbike.uc.domain.root.Poi; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface PoiRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/repository/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.repository; 2 | 3 | import club.newtech.qbike.order.domain.core.root.Order; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface OrderRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /position/src/main/java/club/newtech/qbike/trip/domain/repository/PositionRepository.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.trip.domain.repository; 2 | 3 | import club.newtech.qbike.trip.domain.core.vo.Position; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface PositionRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /uc/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/core/vo/DriverVo.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.core.vo; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.Embeddable; 6 | 7 | @Embeddable 8 | @Data 9 | public class DriverVo { 10 | private int driverId; 11 | private String driverName; 12 | private String driverMobile; 13 | } 14 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/repository/CandidateRepository.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.repository; 2 | 3 | import club.newtech.qbike.intention.domain.core.vo.Candidate; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface CandidateRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/repository/IntentionRepository.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.repository; 2 | 3 | import club.newtech.qbike.intention.domain.core.root.Intention; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface IntentionRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/core/vo/CustomerVo.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.core.vo; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.Embeddable; 6 | 7 | @Embeddable 8 | @Data 9 | public class CustomerVo { 10 | private int customerId; 11 | private String customerName; 12 | private String customerMobile; 13 | } 14 | -------------------------------------------------------------------------------- /testclient/src/main/java/club/newtech/qbike/client/bean/MyIntention.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.client.bean; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class MyIntention { 7 | private int userId; 8 | private Double startLongitude; 9 | private Double startLatitude; 10 | private Double destLongitude; 11 | private Double destLatitude; 12 | 13 | } -------------------------------------------------------------------------------- /position/src/main/java/club/newtech/qbike/trip/domain/core/vo/Driver.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.trip.domain.core.vo; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.Embeddable; 6 | 7 | @Embeddable 8 | @Data 9 | public class Driver { 10 | private int id; 11 | private String userName; 12 | private String mobile; 13 | private String type; 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | data/* 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ 25 | zipkin.jar 26 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/controller/bean/MyIntention.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.controller.bean; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class MyIntention { 7 | private int userId; 8 | private Double startLongitude; 9 | private Double startLatitude; 10 | private Double destLongitude; 11 | private Double destLatitude; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /position/src/main/java/club/newtech/qbike/trip/domain/repository/DriverStatusRepo.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.trip.domain.repository; 2 | 3 | import club.newtech.qbike.trip.domain.core.root.DriverStatus; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface DriverStatusRepo extends CrudRepository { 7 | DriverStatus findByDriver_Id(Integer driverId); 8 | } 9 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/core/vo/Customer.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.core.vo; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.Embeddable; 6 | 7 | @Embeddable 8 | @Data 9 | public class Customer { 10 | private int customerId; 11 | private String customerName; 12 | private String customerMobile; 13 | private String userType; 14 | } 15 | -------------------------------------------------------------------------------- /microservice-discovery-eureka/src/main/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # 基于哪个镜像 2 | FROM java:8 3 | 4 | # 将本地文件夹挂载到当前容器 5 | VOLUME /tmp 6 | 7 | # 拷贝文件到容器,也可以直接写成ADD microservice-discovery-eureka-0.0.1-SNAPSHOT.jar /app.jar 8 | ADD microservice-discovery-eureka-0.0.1-SNAPSHOT.jar app.jar 9 | RUN bash -c 'touch /app.jar' 10 | 11 | # 开放8761端口 12 | EXPOSE 8761 13 | 14 | # 配置容器启动后执行的命令 15 | ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] -------------------------------------------------------------------------------- /uc/src/test/java/club/newtech/qbike/uc/UcApplicationTests.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.uc; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class UcApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /testclient/src/main/java/club/newtech/qbike/client/bean/User.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.client.bean; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class User { 7 | 8 | private int id; 9 | private String userName; 10 | private String mobile; 11 | private String province; 12 | private String city; 13 | private String district; 14 | private String street; 15 | private String originAddress; 16 | private String type; 17 | 18 | } -------------------------------------------------------------------------------- /uc/src/main/java/club/newtech/qbike/uc/domain/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.uc.domain.repository; 2 | 3 | import club.newtech.qbike.uc.domain.root.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.rest.core.annotation.RepositoryRestResource; 6 | 7 | @RepositoryRestResource(collectionResourceRel = "users", path = "users") 8 | public interface UserRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/core/vo/IntentionVo.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.core.vo; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | 7 | 8 | @ToString 9 | @Data 10 | public class IntentionVo { 11 | private int customerId; 12 | private double startLong; 13 | private double startLat; 14 | private double destLong; 15 | private double destLat; 16 | private int intentionId; 17 | private int driverId; 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/club/newtech/qbick/gateway/ZuulGateway.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbick.gateway; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.zuul.EnableZuulProxy; 6 | 7 | @SpringBootApplication 8 | @EnableZuulProxy 9 | public class ZuulGateway { 10 | public static void main(String[] args) { 11 | SpringApplication.run(ZuulGateway.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /microservice-discovery-eureka/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8761 # 指定该Eureka实例的端口 3 | 4 | eureka: 5 | instance: 6 | hostname: discovery # 指定该Eureka实例的主机名 7 | client: 8 | registerWithEureka: false 9 | fetchRegistry: false 10 | serviceUrl: 11 | defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ 12 | 13 | # 参考文档:http://projects.spring.io/spring-cloud/docs/1.0.3/spring-cloud.html#_standalone_mode 14 | # 参考文档:http://my.oschina.net/buwei/blog/618756 -------------------------------------------------------------------------------- /microservice-discovery-eureka/src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8761 # 指定该Eureka实例的端口 3 | 4 | eureka: 5 | instance: 6 | hostname: discovery # 指定该Eureka实例的主机名 7 | client: 8 | registerWithEureka: false 9 | fetchRegistry: false 10 | serviceUrl: 11 | defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ 12 | 13 | # 参考文档:http://projects.spring.io/spring-cloud/docs/1.0.3/spring-cloud.html#_standalone_mode 14 | # 参考文档:http://my.oschina.net/buwei/blog/618756 -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/RabbitConfig.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order; 2 | 3 | import org.springframework.amqp.core.Queue; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * @author 曹祖鹏 OF506 9 | * company qianmi.com 10 | * Date 2018-06-22 11 | */ 12 | @Configuration 13 | public class RabbitConfig { 14 | @Bean 15 | public Queue intentionQueue() { 16 | return new Queue("intention"); 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/util/AsyncTaskInitializer.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.util; 2 | 3 | import club.newtech.qbike.order.domain.service.OrderService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.annotation.PostConstruct; 8 | 9 | @Component 10 | public class AsyncTaskInitializer { 11 | @Autowired 12 | OrderService orderService; 13 | 14 | @PostConstruct 15 | public void initialize() { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/RabbitConfig.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention; 2 | 3 | import org.springframework.amqp.core.Queue; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * @author 曹祖鹏 OF506 9 | * company qianmi.com 10 | * Date 2018-06-22 11 | */ 12 | @Configuration 13 | public class RabbitConfig { 14 | @Bean 15 | public Queue intentionQueue() { 16 | return new Queue("intention"); 17 | } 18 | 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/service/AsyncTaskInitializer.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.service; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | 6 | import javax.annotation.PostConstruct; 7 | 8 | @Component 9 | public class AsyncTaskInitializer { 10 | @Autowired 11 | IntentionService intentionService; 12 | 13 | @PostConstruct 14 | public void initialize() { 15 | intentionService.handleTask(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/controller/bean/IntentionVo.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.controller.bean; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | import lombok.experimental.Accessors; 6 | 7 | 8 | @ToString 9 | @Data 10 | @Accessors(fluent = false, chain = true) 11 | public class IntentionVo { 12 | private int customerId; 13 | private double startLong; 14 | private double startLat; 15 | private double destLong; 16 | private double destLat; 17 | private int intentionId; 18 | private int driverId; 19 | } 20 | -------------------------------------------------------------------------------- /microservice-discovery-eureka/src/main/java/club/newtech/qbike/EurekaApplication.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | /** 8 | * 使用Eureka做服务发现. 9 | * @author eacdy 10 | */ 11 | @SpringBootApplication 12 | @EnableEurekaServer 13 | public class EurekaApplication { 14 | public static void main(String[] args) { 15 | SpringApplication.run(EurekaApplication.class, args); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /uc/src/main/java/club/newtech/qbike/uc/UcApplication.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.uc; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.domain.EntityScan; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | 8 | @SpringBootApplication 9 | @EnableDiscoveryClient 10 | @EntityScan("club.newtech.qbike.uc.domain.root") 11 | public class UcApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(UcApplication.class, args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/core/vo/DriverVo.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.core.vo; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.Data; 5 | 6 | import javax.persistence.Column; 7 | import javax.persistence.Embeddable; 8 | 9 | @JsonIgnoreProperties(ignoreUnknown = true) 10 | @Data 11 | @Embeddable 12 | public class DriverVo { 13 | @Column(nullable = true) 14 | private int id; 15 | @Column(nullable = true) 16 | private String userName; 17 | @Column(nullable = true) 18 | private String mobile; 19 | @Column(nullable = true) 20 | private String type; 21 | } 22 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/core/vo/DriverStatusVo.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.core.vo; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.Data; 5 | 6 | import javax.persistence.Embeddable; 7 | import java.util.Date; 8 | 9 | /** 10 | * 和Position服务中的DriverStatus内容一样 11 | * 这里的值对象的目的是避免共享带来的耦合 12 | * 让intention成为自治的服务 13 | */ 14 | @JsonIgnoreProperties(ignoreUnknown = true) 15 | @Data 16 | public class DriverStatusVo { 17 | private int dId; 18 | private DriverVo driver; 19 | private Double currentLongitude; 20 | private Double currentLatitude; 21 | private Date updateTime; 22 | } 23 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/SpringAsyncConfig.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.core.task.AsyncTaskExecutor; 6 | import org.springframework.scheduling.annotation.EnableAsync; 7 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 8 | 9 | @Configuration 10 | @EnableAsync 11 | public class SpringAsyncConfig { 12 | @Bean 13 | public AsyncTaskExecutor taskExecutor() { 14 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 15 | executor.setMaxPoolSize(10); 16 | return executor; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/SpringAsyncConfig.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.core.task.AsyncTaskExecutor; 6 | import org.springframework.scheduling.annotation.EnableAsync; 7 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 8 | 9 | @Configuration 10 | @EnableAsync 11 | public class SpringAsyncConfig { 12 | @Bean 13 | public AsyncTaskExecutor taskExecutor() { 14 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 15 | executor.setMaxPoolSize(10); 16 | return executor; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /testclient/src/main/java/club/newtech/qbike/client/api/PositionApi.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.client.api; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.web.client.RestTemplate; 6 | 7 | @Component 8 | public class PositionApi { 9 | @Autowired 10 | RestTemplate restTemplate; 11 | 12 | public void positionUpdate(String driverId, String longitude, String latitude) { 13 | String url = String.format( 14 | "http://localhost:8050/qbike-trip/trips/updatePosition?driverId=%s&longitude=%s&latitude=%s", 15 | driverId, longitude, latitude); 16 | restTemplate.getForObject( 17 | url, Object.class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/core/vo/StateRequest.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.core.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * 状态扭转 请求参数 10 | * Created by jinwei on 29/3/2017. 11 | */ 12 | @Builder 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class StateRequest { 17 | 18 | /** 19 | * 订单编号 20 | */ 21 | private String orderId; 22 | 23 | /** 24 | * 事件操作 25 | */ 26 | private Events event; 27 | 28 | private Object data; 29 | /** 30 | * 用户信息 31 | */ 32 | private int userId; 33 | 34 | /** 35 | * 追踪调用链 36 | */ 37 | private String uId; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/service/ErrorAction.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.service; 2 | 3 | import club.newtech.qbike.order.domain.core.vo.Events; 4 | import club.newtech.qbike.order.domain.core.vo.FlowState; 5 | import org.springframework.statemachine.StateContext; 6 | import org.springframework.statemachine.action.Action; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | * 状态机异常Action 11 | * 12 | * @author wumeng[OF2627] 13 | * company qianmi.com 14 | * Date 2017-04-26 15 | */ 16 | @Service 17 | public class ErrorAction implements Action { 18 | 19 | @Override 20 | public void execute(StateContext context) { 21 | context.getExtendedState().getVariables().put(Exception.class, context.getException()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /testclient/src/main/java/club/newtech/qbike/client/api/UserApi.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.client.api; 2 | 3 | import club.newtech.qbike.client.bean.MyIntention; 4 | import club.newtech.qbike.client.bean.User; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.client.RestTemplate; 8 | 9 | @Component 10 | public class UserApi { 11 | @Autowired 12 | RestTemplate restTemplate; 13 | 14 | public void newUser(User user) { 15 | restTemplate.postForObject("http://qbike-uc/users", user, User.class); 16 | } 17 | 18 | public void newIntention(MyIntention myIntention) { 19 | restTemplate.postForObject("http://qbike-intention/intentions/place", myIntention, MyIntention.class); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /position/src/main/java/club/newtech/qbike/trip/domain/core/root/DriverStatus.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.trip.domain.core.root; 2 | 3 | import club.newtech.qbike.trip.domain.core.Status; 4 | import club.newtech.qbike.trip.domain.core.vo.Driver; 5 | import lombok.Data; 6 | 7 | import javax.persistence.*; 8 | import java.util.Date; 9 | 10 | import static javax.persistence.EnumType.STRING; 11 | 12 | @Data 13 | @Entity 14 | @Table(name = "t_driver_status") 15 | public class DriverStatus { 16 | @Id 17 | private int dId; 18 | @Embedded 19 | private Driver driver; 20 | private Double currentLongitude; 21 | private Double currentLatitude; 22 | @Enumerated(value = STRING) 23 | @Column(length = 32, nullable = false) 24 | private Status status; 25 | @Temporal(TemporalType.TIMESTAMP) 26 | private Date updateTime; 27 | } 28 | -------------------------------------------------------------------------------- /api-gateway/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: microservice-api-gateway 4 | rabbitmq: 5 | host: localhost 6 | port: 5672 7 | username: guest 8 | password: guest 9 | sleuth: 10 | sampler: 11 | probability: 1.0 12 | zipkin: 13 | rabbitmq: 14 | queue: zipkin 15 | 16 | server: 17 | port: 8050 18 | eureka: 19 | instance: 20 | hostname: gateway 21 | client: 22 | serviceUrl: 23 | defaultZone: http://127.0.0.1:8761/eureka/ 24 | 25 | #zuul: 26 | # ignored-services: microservice-provider-user # 需要忽视的服务(配置后将不会被路由) 27 | # routes: 28 | # movie: # 可以随便写,在zuul上面唯一即可;当这里的值 = service-id时,service-id可以不写。 29 | # path: /movie/** # 想要映射到的路径 30 | # service-customerId: microservice-consumer-movie-ribbon-with-hystrix # Eureka中的serviceId 31 | -------------------------------------------------------------------------------- /position/src/main/java/club/newtech/qbike/trip/domain/core/vo/Position.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.trip.domain.core.vo; 2 | 3 | import club.newtech.qbike.trip.domain.core.Status; 4 | import lombok.Data; 5 | import lombok.ToString; 6 | import lombok.experimental.Accessors; 7 | 8 | import javax.persistence.*; 9 | import java.util.Date; 10 | 11 | import static javax.persistence.EnumType.STRING; 12 | 13 | @Data 14 | @ToString 15 | @Accessors(fluent = false, chain = true) 16 | @Entity 17 | @Table(name = "t_position") 18 | public class Position { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private int tid; 22 | private Double positionLongitude; 23 | private Double positionLatitude; 24 | @Enumerated(value = STRING) 25 | @Column(length = 32, nullable = false) 26 | private Status status; 27 | private String driverId; 28 | @Temporal(TemporalType.TIMESTAMP) 29 | private Date uploadTime; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/core/vo/Candidate.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.core.vo; 2 | 3 | import club.newtech.qbike.intention.domain.core.root.Intention; 4 | import lombok.Data; 5 | 6 | import javax.persistence.*; 7 | import java.util.Date; 8 | 9 | @Data 10 | @Entity 11 | @Table(name = "t_intention_candidate") 12 | public class Candidate { 13 | /** 14 | * 按照DDD的理论,值对象是没有自己的主键的,应该用intentionId和driverId组成复合主键 15 | * 但是考虑JPA的实现难度和数据库管理的难度,所以这里做了一个反模式 16 | */ 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | private int cid; 20 | @ManyToOne 21 | @JoinColumn(name = "intention_id") 22 | private Intention intention; 23 | private int driverId; 24 | private String driverName; 25 | private String driverMobile; 26 | private Double longitude; 27 | private Double latitude; 28 | @Temporal(TemporalType.TIMESTAMP) 29 | private Date created; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /uc/src/main/java/club/newtech/qbike/uc/domain/root/User.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.uc.domain.root; 2 | 3 | import club.newtech.qbike.uc.domain.Type; 4 | import lombok.Data; 5 | 6 | import javax.persistence.*; 7 | 8 | import static javax.persistence.EnumType.STRING; 9 | 10 | @Entity 11 | @Table(name = "T_USER") 12 | @Data 13 | public class User { 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.IDENTITY) 16 | private int id; 17 | @Column(length = 64) 18 | private String userName; 19 | @Column(length = 64, nullable = false) 20 | private String mobile; 21 | @Column(length = 64) 22 | private String province; 23 | @Column(length = 64) 24 | private String city; 25 | @Column(length = 64) 26 | private String district; 27 | private String street; 28 | private String originAddress; 29 | 30 | 31 | @Enumerated(value = STRING) 32 | @Column(length = 32, nullable = false) 33 | private Type type; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /api-gateway/src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: microservice-api-gateway 4 | rabbitmq: 5 | host: ${RABBIT} 6 | port: 5672 7 | username: guest 8 | password: guest 9 | zipkin: 10 | rabbitmq: 11 | queue: zipkin 12 | sleuth: 13 | sampler: 14 | probability: 1.0f 15 | server: 16 | port: 8050 17 | eureka: 18 | instance: 19 | hostname: gateway 20 | client: 21 | serviceUrl: 22 | defaultZone: http://${EUREKA_HOST}:8761/eureka/ 23 | zuul: 24 | host: 25 | connect-timeout-millis: 30000 26 | #zuul: 27 | # ignored-services: microservice-provider-user # 需要忽视的服务(配置后将不会被路由) 28 | # routes: 29 | # movie: # 可以随便写,在zuul上面唯一即可;当这里的值 = service-id时,service-id可以不写。 30 | # path: /movie/** # 想要映射到的路径 31 | # service-customerId: microservice-consumer-movie-ribbon-with-hystrix # Eureka中的serviceId 32 | -------------------------------------------------------------------------------- /uc/src/main/java/club/newtech/qbike/uc/domain/root/Poi.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.uc.domain.root; 2 | 3 | import club.newtech.qbike.uc.domain.Type; 4 | import lombok.Data; 5 | 6 | import javax.persistence.*; 7 | 8 | import static javax.persistence.EnumType.STRING; 9 | 10 | @Entity 11 | @Table(name = "tb_poi") 12 | @Data 13 | public class Poi { 14 | @Id 15 | private int id; 16 | @Column(length = 64) 17 | private String linkMan; 18 | @Column(length = 64) 19 | private String shopName; 20 | @Column(length = 64, nullable = false) 21 | private String cellPhone; 22 | private Double longitude; 23 | private Double latitude; 24 | @Column(length = 64) 25 | private String province; 26 | @Column(length = 64) 27 | private String city; 28 | @Column(length = 64) 29 | private String district; 30 | private String street; 31 | private String streetNumber; 32 | private int shopType; 33 | private String userCode; 34 | private String originAddress; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/core/root/Order.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.core.root; 2 | 3 | import club.newtech.qbike.order.domain.core.vo.CustomerVo; 4 | import club.newtech.qbike.order.domain.core.vo.DriverVo; 5 | import lombok.Data; 6 | import lombok.ToString; 7 | import lombok.experimental.Accessors; 8 | 9 | import javax.persistence.*; 10 | import java.util.Date; 11 | 12 | @Data 13 | @ToString 14 | @Accessors(fluent = false, chain = true) 15 | @Entity 16 | @Table(name = "t_qbike_order") 17 | public class Order { 18 | @Id 19 | private String oid; 20 | @Embedded 21 | private CustomerVo customer; 22 | @Embedded 23 | private DriverVo driver; 24 | private Double startLong; 25 | private Double startLat; 26 | private Double destLong; 27 | private Double destLat; 28 | @Temporal(TemporalType.TIMESTAMP) 29 | private Date opened; 30 | @Column(length = 32, nullable = false) 31 | private String orderStatus; 32 | private String intentionId; 33 | } 34 | -------------------------------------------------------------------------------- /testclient/src/main/java/club/newtech/qbike/client/RestClientApplication.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.client; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.web.client.RestTemplate; 10 | 11 | @SpringBootApplication 12 | //@EnableDiscoveryClient 13 | public class RestClientApplication { 14 | public static void main(String[] args) { 15 | SpringApplication.run(RestClientApplication.class, args); 16 | } 17 | 18 | /** 19 | * 实例化RestTemplate,通过@LoadBalanced注解开启均衡负载能力. 20 | * 21 | * @return restTemplate 22 | */ 23 | @Bean 24 | // @LoadBalanced 25 | public RestTemplate restTemplate() { 26 | return new RestTemplate(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /position/src/main/java/club/newtech/qbike/trip/PositionApplication.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.trip; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.web.client.RestTemplate; 10 | 11 | @SpringBootApplication 12 | @EnableDiscoveryClient 13 | @EnableCircuitBreaker 14 | public class PositionApplication { 15 | public static void main(String[] args) { 16 | SpringApplication.run(PositionApplication.class, args); 17 | } 18 | 19 | /** 20 | * 实例化RestTemplate,通过@LoadBalanced注解开启均衡负载能力. 21 | * 22 | * @return restTemplate 23 | */ 24 | @Bean 25 | @LoadBalanced 26 | public RestTemplate restTemplate() { 27 | return new RestTemplate(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/core/vo/Lock.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.core.vo; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | import java.util.Date; 7 | 8 | /** 9 | * A value object representing a named lock, with a globally unique value and an expiry. 10 | * @author Joe 11 | * 12 | */ 13 | @Data 14 | @ToString 15 | public class Lock implements Comparable { 16 | /** 17 | * The customerName of the lock. 18 | */ 19 | private final String name; 20 | /** 21 | * The value of the lock (globally unique, or at least different for locks with the 22 | * same customerName and different expiry). 23 | */ 24 | private final String value; 25 | /** 26 | * The expiry of the lock expressed as a point in time. 27 | */ 28 | private final Date expires; 29 | 30 | public boolean isExpired() { 31 | return expires.before(new Date()); 32 | } 33 | 34 | @Override 35 | public int compareTo(Lock other) { 36 | return expires.compareTo(other.expires); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /uc/src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8000 3 | spring: 4 | application: 5 | name: qbike-uc # 项目名称尽量用小写 6 | jpa: 7 | generate-ddl: false 8 | show-sql: true 9 | hibernate: 10 | ddl-auto: validate 11 | datasource: 12 | driver-class-name: com.mysql.jdbc.Driver 13 | url: jdbc:mysql://${DB_HOST}:3306/${DB_SCHEMA}?useUnicode=true&characterEncoding=utf8 14 | username: root 15 | password: root 16 | tomcat: 17 | max-active: 5 18 | min-idle: 1 19 | rabbitmq: 20 | host: ${RABBIT} 21 | port: 5672 22 | username: guest 23 | password: guest 24 | zipkin: 25 | rabbitmq: 26 | queue: zipkin 27 | sleuth: 28 | sampler: 29 | probability: 1.0f 30 | logging: 31 | level: 32 | root: INFO 33 | org.hibernate: INFO 34 | org.hibernate.type.descriptor.sql.BasicBinder: TRACE 35 | org.hibernate.type.descriptor.sql.BasicExtractor: TRACE 36 | eureka: 37 | client: 38 | serviceUrl: 39 | defaultZone: http://${EUREKA_HOST}:8761/eureka/ # 指定注册中心的地址 40 | instance: 41 | preferIpAddress: true -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/IntentionApplication.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 7 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 8 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | @SpringBootApplication 13 | @EnableDiscoveryClient 14 | @EnableCircuitBreaker 15 | public class IntentionApplication { 16 | public static void main(String[] args) { 17 | SpringApplication.run(IntentionApplication.class, args); 18 | } 19 | 20 | /** 21 | * 实例化RestTemplate,通过@LoadBalanced注解开启均衡负载能力. 22 | * 23 | * @return restTemplate 24 | */ 25 | @Bean 26 | @LoadBalanced 27 | public RestTemplate restTemplate() { 28 | return new RestTemplate(); 29 | } 30 | 31 | @Bean 32 | public ObjectMapper objectMapper() { 33 | return new ObjectMapper(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /uc/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8000 3 | spring: 4 | application: 5 | name: qbike-uc # 项目名称尽量用小写 6 | jpa: 7 | generate-ddl: false 8 | show-sql: true 9 | hibernate: 10 | ddl-auto: update 11 | datasource: 12 | driver-class-name: com.mysql.jdbc.Driver 13 | url: jdbc:mysql://127.0.0.1:3306/qbike?useUnicode=true&characterEncoding=utf8 14 | username: root 15 | password: 16 | tomcat: 17 | max-active: 5 18 | min-idle: 1 19 | # 指定数据源类型 20 | # schema: classpath:schema.sql # 指定h2数据库的建表脚本 21 | # data: classpath:data.sql # 指定h2数据库的insert脚本 22 | h2: 23 | console: 24 | enabled: true 25 | rabbitmq: 26 | host: localhost 27 | port: 5672 28 | username: guest 29 | password: guest 30 | zipkin: 31 | rabbitmq: 32 | queue: zipkin 33 | sleuth: 34 | sampler: 35 | probability: 1.0f 36 | logging: 37 | level: 38 | root: INFO 39 | org.hibernate: INFO 40 | org.hibernate.type.descriptor.sql.BasicBinder: TRACE 41 | org.hibernate.type.descriptor.sql.BasicExtractor: TRACE 42 | eureka: 43 | client: 44 | serviceUrl: 45 | defaultZone: http://127.0.0.1:8761/eureka/ # 指定注册中心的地址 46 | instance: 47 | preferIpAddress: true -------------------------------------------------------------------------------- /position/src/main/java/club/newtech/qbike/trip/controller/PositionController.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.trip.controller; 2 | 3 | import club.newtech.qbike.trip.domain.core.root.DriverStatus; 4 | import club.newtech.qbike.trip.domain.service.PositionService; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.util.Collection; 12 | 13 | @RestController 14 | public class PositionController { 15 | private static final Logger LOGGER = LoggerFactory.getLogger(PositionController.class); 16 | @Autowired 17 | PositionService positionService; 18 | 19 | @GetMapping("/trips/updatePosition") 20 | public void positionUpdate(Integer driverId, Double longitude, Double latitude) { 21 | LOGGER.info(String.format("update position %s %s %s", driverId, longitude, latitude)); 22 | positionService.updatePosition(driverId, longitude, latitude); 23 | } 24 | 25 | @GetMapping("/trips/match") 26 | public Collection match(Double longitude, Double latitude) { 27 | return positionService.matchDriver(longitude, latitude); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /order/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8002 3 | spring: 4 | application: 5 | name: qbike-order # 项目名称尽量用小写 6 | jpa: 7 | generate-ddl: false 8 | show-sql: true 9 | hibernate: 10 | ddl-auto: update 11 | datasource: 12 | driver-class-name: com.mysql.jdbc.Driver 13 | url: jdbc:mysql://127.0.0.1:3306/qbike?useUnicode=true&characterEncoding=utf8 14 | username: root 15 | password: 16 | tomcat: 17 | max-active: 5 18 | min-idle: 1 19 | redis: 20 | host: localhost 21 | timeout: 2000ms 22 | database: 2 23 | jedis: 24 | pool: 25 | #最大连接数 26 | max-active: 8 27 | #最大阻塞等待时间(负数表示没限制) 28 | max-wait: 1000ms 29 | #最大空闲 30 | max-idle: 8 31 | #最小空闲 32 | min-idle: 0 33 | rabbitmq: 34 | host: localhost 35 | port: 5672 36 | username: guest 37 | password: guest 38 | zipkin: 39 | rabbitmq: 40 | queue: zipkin 41 | sleuth: 42 | sampler: 43 | probability: 1.0f 44 | logging: 45 | level: 46 | root: INFO 47 | org.hibernate: INFO 48 | org.hibernate.type.descriptor.sql.BasicBinder: TRACE 49 | org.hibernate.type.descriptor.sql.BasicExtractor: TRACE 50 | eureka: 51 | client: 52 | serviceUrl: 53 | defaultZone: http://127.0.0.1:8761/eureka/ # 指定注册中心的地址 54 | instance: 55 | preferIpAddress: true -------------------------------------------------------------------------------- /position/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8003 3 | spring: 4 | application: 5 | name: qbike-trip # 项目名称尽量用小写 6 | jpa: 7 | generate-ddl: false 8 | show-sql: true 9 | hibernate: 10 | ddl-auto: update 11 | datasource: 12 | driver-class-name: com.mysql.jdbc.Driver 13 | url: jdbc:mysql://127.0.0.1:3306/qbike?useUnicode=true&characterEncoding=utf8 14 | username: root 15 | password: 16 | tomcat: 17 | max-active: 3 18 | min-idle: 1 19 | redis: 20 | host: localhost 21 | timeout: 2000ms 22 | database: 2 23 | jedis: 24 | pool: 25 | #最大连接数 26 | max-active: 8 27 | #最大阻塞等待时间(负数表示没限制) 28 | max-wait: 1000ms 29 | #最大空闲 30 | max-idle: 8 31 | #最小空闲 32 | min-idle: 0 33 | rabbitmq: 34 | host: localhost 35 | port: 5672 36 | username: guest 37 | password: guest 38 | zipkin: 39 | rabbitmq: 40 | queue: zipkin 41 | sleuth: 42 | sampler: 43 | probability: 1.0f 44 | logging: 45 | level: 46 | root: INFO 47 | org.hibernate: INFO 48 | org.hibernate.type.descriptor.sql.BasicBinder: TRACE 49 | org.hibernate.type.descriptor.sql.BasicExtractor: TRACE 50 | eureka: 51 | client: 52 | serviceUrl: 53 | defaultZone: http://127.0.0.1:8761/eureka/ # 指定注册中心的地址 54 | instance: 55 | preferIpAddress: true -------------------------------------------------------------------------------- /intention/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8001 3 | spring: 4 | application: 5 | name: qbike-intention # 项目名称尽量用小写 6 | jpa: 7 | generate-ddl: false 8 | show-sql: true 9 | hibernate: 10 | ddl-auto: update 11 | datasource: 12 | driver-class-name: com.mysql.jdbc.Driver 13 | url: jdbc:mysql://127.0.0.1:3306/qbike?useUnicode=true&characterEncoding=utf8 14 | username: root 15 | password: 16 | tomcat: 17 | max-active: 3 18 | min-idle: 1 19 | redis: 20 | host: localhost 21 | timeout: 2000ms 22 | database: 2 23 | jedis: 24 | pool: 25 | #最大连接数 26 | max-active: 8 27 | #最大阻塞等待时间(负数表示没限制) 28 | max-wait: 1000ms 29 | #最大空闲 30 | max-idle: 8 31 | #最小空闲 32 | min-idle: 0 33 | 34 | rabbitmq: 35 | host: localhost 36 | port: 5672 37 | username: guest 38 | password: guest 39 | zipkin: 40 | rabbitmq: 41 | queue: zipkin 42 | sleuth: 43 | sampler: 44 | probability: 1.0f 45 | logging: 46 | level: 47 | root: INFO 48 | org.hibernate: INFO 49 | # org.hibernate.type.descriptor.sql.BasicBinder: TRACE 50 | # org.hibernate.type.descriptor.sql.BasicExtractor: TRACE 51 | eureka: 52 | client: 53 | serviceUrl: 54 | defaultZone: http://127.0.0.1:8761/eureka/ # 指定注册中心的地址 55 | instance: 56 | preferIpAddress: true -------------------------------------------------------------------------------- /testclient/src/test/java/club/newtech/qbike/client/GeoPositionUpdateTest.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.client; 2 | 3 | import club.newtech.qbike.client.api.PositionApi; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | import java.io.IOException; 12 | import java.nio.file.Files; 13 | 14 | 15 | @RunWith(SpringRunner.class) 16 | @SpringBootTest 17 | public class GeoPositionUpdateTest { 18 | @Value("classpath:/idposition.csv") 19 | private org.springframework.core.io.Resource resource; 20 | @Autowired 21 | private PositionApi positionApi; 22 | 23 | @Test 24 | public void positionUpdateClient() { 25 | try { 26 | Files.lines(resource.getFile().toPath()).forEach(line -> { 27 | try { 28 | String[] values = line.split(","); 29 | positionApi.positionUpdate(values[0], values[1], values[2]); 30 | } catch (Exception e) { 31 | e.printStackTrace(); 32 | } 33 | 34 | }); 35 | } catch (IOException e) { 36 | e.printStackTrace(); 37 | } 38 | 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /order/src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8002 3 | spring: 4 | application: 5 | name: qbike-order # 项目名称尽量用小写 6 | jpa: 7 | generate-ddl: false 8 | show-sql: true 9 | hibernate: 10 | ddl-auto: validate 11 | datasource: 12 | driver-class-name: com.mysql.jdbc.Driver 13 | url: jdbc:mysql://${DB_HOST}:3306/${DB_SCHEMA}?useUnicode=true&characterEncoding=utf8 14 | username: root 15 | password: root 16 | tomcat: 17 | max-active: 5 18 | min-idle: 1 19 | redis: 20 | host: ${REDIS_HOST} 21 | timeout: 2000ms 22 | database: ${REDIS_DB} 23 | jedis: 24 | pool: 25 | #最大连接数 26 | max-active: 8 27 | #最大阻塞等待时间(负数表示没限制) 28 | max-wait: 1000ms 29 | #最大空闲 30 | max-idle: 8 31 | #最小空闲 32 | min-idle: 0 33 | rabbitmq: 34 | host: ${RABBIT} 35 | port: 5672 36 | username: guest 37 | password: guest 38 | zipkin: 39 | rabbitmq: 40 | queue: zipkin 41 | sleuth: 42 | sampler: 43 | probability: 1.0f 44 | logging: 45 | level: 46 | root: INFO 47 | org.hibernate: INFO 48 | org.hibernate.type.descriptor.sql.BasicBinder: TRACE 49 | org.hibernate.type.descriptor.sql.BasicExtractor: TRACE 50 | eureka: 51 | client: 52 | serviceUrl: 53 | defaultZone: http://${EUREKA_HOST}:8761/eureka/ # 指定注册中心的地址 54 | instance: 55 | preferIpAddress: true -------------------------------------------------------------------------------- /position/src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8003 3 | spring: 4 | application: 5 | name: qbike-trip # 项目名称尽量用小写 6 | jpa: 7 | generate-ddl: false 8 | show-sql: true 9 | hibernate: 10 | ddl-auto: validate 11 | datasource: 12 | driver-class-name: com.mysql.jdbc.Driver 13 | url: jdbc:mysql://${DB_HOST}:3306/${DB_SCHEMA}?useUnicode=true&characterEncoding=utf8 14 | username: root 15 | password: root 16 | tomcat: 17 | max-active: 3 18 | min-idle: 1 19 | redis: 20 | host: ${REDIS_HOST} 21 | timeout: 2000ms 22 | database: ${REDIS_DB} 23 | jedis: 24 | pool: 25 | #最大连接数 26 | max-active: 8 27 | #最大阻塞等待时间(负数表示没限制) 28 | max-wait: 1000ms 29 | #最大空闲 30 | max-idle: 8 31 | #最小空闲 32 | min-idle: 0 33 | rabbitmq: 34 | host: ${RABBIT} 35 | port: 5672 36 | username: guest 37 | password: guest 38 | zipkin: 39 | rabbitmq: 40 | queue: zipkin 41 | sleuth: 42 | sampler: 43 | probability: 1.0f 44 | logging: 45 | level: 46 | root: INFO 47 | org.hibernate: INFO 48 | org.hibernate.type.descriptor.sql.BasicBinder: TRACE 49 | org.hibernate.type.descriptor.sql.BasicExtractor: TRACE 50 | eureka: 51 | client: 52 | serviceUrl: 53 | defaultZone: http://${EUREKA_HOST}:8761/eureka/ # 指定注册中心的地址 54 | instance: 55 | preferIpAddress: true -------------------------------------------------------------------------------- /intention/src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8001 3 | spring: 4 | application: 5 | name: qbike-intention # 项目名称尽量用小写 6 | jpa: 7 | generate-ddl: false 8 | show-sql: false 9 | hibernate: 10 | ddl-auto: validate 11 | datasource: 12 | driver-class-name: com.mysql.jdbc.Driver 13 | url: jdbc:mysql://${DB_HOST}:3306/${DB_SCHEMA}?useUnicode=true&characterEncoding=utf8 14 | username: root 15 | password: root 16 | tomcat: 17 | max-active: 10 18 | min-idle: 3 19 | redis: 20 | host: ${REDIS_HOST} 21 | timeout: 2000ms 22 | database: ${REDIS_DB} 23 | jedis: 24 | pool: 25 | #最大连接数 26 | max-active: 8 27 | #最大阻塞等待时间(负数表示没限制) 28 | max-wait: 1000ms 29 | #最大空闲 30 | max-idle: 8 31 | #最小空闲 32 | min-idle: 0 33 | rabbitmq: 34 | host: ${RABBIT} 35 | port: 5672 36 | username: guest 37 | password: guest 38 | zipkin: 39 | rabbitmq: 40 | queue: zipkin 41 | sleuth: 42 | sampler: 43 | probability: 1.0f 44 | logging: 45 | level: 46 | root: INFO 47 | org.hibernate: INFO 48 | # org.hibernate.type.descriptor.sql.BasicBinder: TRACE 49 | # org.hibernate.type.descriptor.sql.BasicExtractor: TRACE 50 | eureka: 51 | client: 52 | serviceUrl: 53 | defaultZone: http://${EUREKA_HOST}:8761/eureka/ # 指定注册中心的地址 54 | instance: 55 | preferIpAddress: true -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/service/StateMachineBuilderFactory.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.service; 2 | 3 | import club.newtech.qbike.order.domain.core.root.Order; 4 | import club.newtech.qbike.order.domain.core.vo.Events; 5 | import club.newtech.qbike.order.domain.core.vo.FlowState; 6 | import club.newtech.qbike.order.domain.exception.OrderRuntimeException; 7 | import org.springframework.beans.factory.BeanFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.statemachine.StateMachine; 10 | import org.springframework.stereotype.Component; 11 | 12 | @Component 13 | public class StateMachineBuilderFactory { 14 | @Autowired 15 | private OrderStateMachineBuilder orderStateMachineBuilder; 16 | @Autowired 17 | private BeanFactory beanFactory; 18 | 19 | public StateMachine create(Order order) { 20 | FlowState flowState = FlowState.forValue(order.getOrderStatus()); 21 | 22 | try { 23 | StateMachine sm = orderStateMachineBuilder.build(flowState, beanFactory); 24 | // sm.getStateMachineAccessor().withRegion().addStateMachineInterceptor(tradeCommonOptInterceptor); 25 | sm.start(); 26 | sm.getExtendedState().getVariables().put(Order.class, order); 27 | return sm; 28 | } catch (Exception e) { 29 | throw new OrderRuntimeException(String.format("创建状态[%s]失败 => [%s]", flowState, e.getCause())); 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /position/src/main/java/club/newtech/qbike/trip/infrastructure/UserRibbonHystrixApi.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.trip.infrastructure; 2 | 3 | import club.newtech.qbike.trip.domain.core.vo.Driver; 4 | import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.web.client.RestTemplate; 10 | 11 | @Service 12 | public class UserRibbonHystrixApi { 13 | private static final Logger LOGGER = LoggerFactory.getLogger(UserRibbonHystrixApi.class); 14 | @Autowired 15 | private RestTemplate restTemplate; 16 | 17 | /** 18 | * 使用@HystrixCommand注解指定当该方法发生异常时调用的方法 19 | * 20 | * @param id customerId 21 | * @return 通过id查询到的用户 22 | */ 23 | @HystrixCommand 24 | public Driver findById(Integer id) { 25 | Driver driver = this.restTemplate.getForObject("http://QBIKE-UC/users/" + id, Driver.class); 26 | driver.setId(id); 27 | return driver; 28 | } 29 | 30 | /** 31 | * hystrix fallback方法 32 | * 33 | * @param id customerId 34 | * @return 默认的用户 35 | */ 36 | public Driver fallback(Integer id) { 37 | UserRibbonHystrixApi.LOGGER.info("异常发生,进入fallback方法,接收的参数:customerId = {}", id); 38 | Driver driver = new Driver(); 39 | driver.setId(-1); 40 | driver.setUserName("default driver"); 41 | driver.setMobile("0000"); 42 | return driver; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /testclient/src/test/java/club/newtech/qbike/client/UserRequestIntention.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.client; 2 | 3 | import club.newtech.qbike.client.api.UserApi; 4 | import club.newtech.qbike.client.bean.MyIntention; 5 | import club.newtech.qbike.client.bean.User; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | import org.springframework.web.client.RestTemplate; 12 | 13 | @RunWith(SpringRunner.class) 14 | @SpringBootTest 15 | public class UserRequestIntention { 16 | @Autowired 17 | private RestTemplate restTemplate; 18 | @Autowired 19 | private UserApi userApi; 20 | 21 | @Test 22 | public void myNewIntention() { 23 | MyIntention myIntention = new MyIntention(); 24 | myIntention.setUserId(1270); 25 | myIntention.setStartLongitude(118.81722069709); 26 | myIntention.setStartLatitude(32.007969136143); 27 | myIntention.setDestLongitude(118.71334176065); 28 | myIntention.setDestLatitude(32.012518207527); 29 | // restTemplate.postForObject("http://qbike-intention/intentions/place", myIntention, MyIntention.class); 30 | userApi.newIntention(myIntention); 31 | } 32 | 33 | 34 | @Test 35 | public void testNewUser() { 36 | User user = new User(); 37 | user.setUserName("Joe"); 38 | user.setMobile("13900001111"); 39 | user.setType("Customer"); 40 | userApi.newUser(user); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/core/vo/FlowState.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.core.vo; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public enum FlowState { 7 | WAITING_ABOARD("WAITING_ABOARD", "等待上车"), 8 | WAITING_ARRIVE("WAITING_ARRIVE", "等待到达目的地"), 9 | UNPAY("UNPAY", "等待支付"), 10 | PAYING("PAYING", "已支付待确认"), 11 | WAITING_COMMENT("WAITING_COMMENT", "等待评论"), 12 | CLOSED("CLOSED", "订单关闭"), 13 | CANCELED("CANCELED", "订单取消"); 14 | private static Map flowStateMap = new HashMap<>(); 15 | 16 | static { 17 | flowStateMap.put(WAITING_ABOARD.getStateId(), WAITING_ABOARD); 18 | flowStateMap.put(WAITING_ARRIVE.getStateId(), WAITING_ARRIVE); 19 | flowStateMap.put(UNPAY.getStateId(), UNPAY); 20 | flowStateMap.put(PAYING.getStateId(), PAYING); 21 | flowStateMap.put(WAITING_COMMENT.getStateId(), WAITING_COMMENT); 22 | flowStateMap.put(CLOSED.getStateId(), CLOSED); 23 | flowStateMap.put(CANCELED.getStateId(), CANCELED); 24 | } 25 | 26 | private String stateId; 27 | private String description; 28 | 29 | FlowState(String stateId, String description) { 30 | this.stateId = stateId; 31 | this.description = description; 32 | } 33 | 34 | public static FlowState forValue(String stateId) { 35 | return flowStateMap.get(stateId); 36 | } 37 | 38 | public String toValue() { 39 | return this.getStateId(); 40 | } 41 | 42 | public String getStateId() { 43 | return stateId; 44 | } 45 | 46 | public String getDescription() { 47 | return description; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/infrastructure/PositionApi.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.infrastructure; 2 | 3 | import club.newtech.qbike.intention.domain.core.vo.DriverStatusVo; 4 | import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.core.ParameterizedTypeReference; 9 | import org.springframework.http.HttpMethod; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.web.client.RestTemplate; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Collection; 16 | 17 | @Service 18 | public class PositionApi { 19 | private static final Logger LOGGER = LoggerFactory.getLogger(PositionApi.class); 20 | @Autowired 21 | private RestTemplate restTemplate; 22 | 23 | @HystrixCommand(fallbackMethod = "defaultMatch") 24 | public Collection match(double longitude, double latitude) { 25 | ResponseEntity> matchReponse = 26 | restTemplate.exchange( 27 | String.format("http://qbike-trip/trips/match?longitude=%s&latitude=%s", longitude, latitude), 28 | HttpMethod.GET, null, 29 | new ParameterizedTypeReference>() { 30 | } 31 | ); 32 | return matchReponse.getBody(); 33 | } 34 | 35 | public Collection defaultMatch(double longitude, double latitude) { 36 | return new ArrayList<>(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/util/SpringContextHolder.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.util; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.context.annotation.Lazy; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | * 获取spring上下文工具类 11 | * Created by aqlu on 15/12/2. 12 | */ 13 | @Component 14 | @Lazy(false) 15 | public class SpringContextHolder implements ApplicationContextAware { 16 | private static ApplicationContext applicationContext; 17 | 18 | @Override 19 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 20 | setApplicationContextWithStatic(applicationContext); 21 | } 22 | 23 | private static void setApplicationContextWithStatic(ApplicationContext applicationContext){ 24 | SpringContextHolder.applicationContext = applicationContext; 25 | 26 | } 27 | 28 | public static ApplicationContext getApplicationContext() { 29 | return applicationContext; 30 | } 31 | 32 | /** 33 | * 根据Bean名称获取实例 34 | * 35 | * @return bean实例 36 | * @throws BeansException 37 | */ 38 | @SuppressWarnings("unchecked") 39 | public static T getBean(String name) throws BeansException { 40 | return (T)applicationContext.getBean(name); 41 | } 42 | 43 | /** 44 | * 根据类型获取实例 45 | * 46 | * @param type 类型 47 | * @return bean实例 48 | * @throws BeansException 49 | */ 50 | public static T getBean(Class type) throws BeansException { 51 | return applicationContext.getBean(type); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/core/vo/IntentionTask.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.core.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.ToString; 5 | 6 | import java.util.concurrent.Delayed; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | @Getter 10 | @ToString 11 | /** 12 | * 用延时队列来实现定时的轮询任务,如果找不到匹配intention的司机,将重新压回队列 13 | * 分布式下可以使用redis来实现延时任务队列 14 | */ 15 | public class IntentionTask implements Delayed { 16 | /** 17 | * Base of nanosecond timings, to avoid wrapping 18 | */ 19 | private final long NANO_ORIGIN = System.nanoTime(); 20 | private final long executionTime; 21 | private int intenionId; 22 | private int repeatTimes; 23 | 24 | public IntentionTask(int intenionId, long time, TimeUnit unit, int repeatTimes) { 25 | this.intenionId = intenionId; 26 | this.repeatTimes = repeatTimes; 27 | this.executionTime = TimeUnit.NANOSECONDS.convert(time, unit); 28 | } 29 | 30 | 31 | final long now() { 32 | return System.nanoTime() - NANO_ORIGIN; 33 | } 34 | 35 | @Override 36 | public long getDelay(TimeUnit unit) { 37 | long d = unit.convert(executionTime - now(), TimeUnit.NANOSECONDS); 38 | return d; 39 | } 40 | 41 | @Override 42 | public int compareTo(Delayed other) { 43 | if (other == this) // compare zero ONLY if same object 44 | return 0; 45 | if (other instanceof IntentionTask) { 46 | IntentionTask x = (IntentionTask) other; 47 | long diff = executionTime - x.executionTime; 48 | if (diff < 0) 49 | return -1; 50 | else if (diff > 0) 51 | return 1; 52 | } 53 | long d = (getDelay(TimeUnit.NANOSECONDS) - other.getDelay(TimeUnit.NANOSECONDS)); 54 | return (d == 0) ? 0 : ((d < 0) ? -1 : 1); 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/controller/RibbonHystrixController.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.controller; 2 | 3 | import club.newtech.qbike.intention.controller.bean.MyIntention; 4 | import club.newtech.qbike.intention.domain.core.vo.Customer; 5 | import club.newtech.qbike.intention.domain.core.vo.DriverStatusVo; 6 | import club.newtech.qbike.intention.domain.service.IntentionService; 7 | import club.newtech.qbike.intention.infrastructure.PositionApi; 8 | import club.newtech.qbike.intention.infrastructure.UserRibbonHystrixApi; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import java.util.Collection; 13 | 14 | @RestController 15 | public class RibbonHystrixController { 16 | @Autowired 17 | private UserRibbonHystrixApi userRibbonHystrixApi; 18 | @Autowired 19 | private IntentionService intentionService; 20 | @Autowired 21 | private PositionApi positionApi; 22 | 23 | @GetMapping("/ribbon/{id}") 24 | public Customer findById(@PathVariable Integer id) { 25 | return this.userRibbonHystrixApi.findById(id); 26 | } 27 | 28 | @GetMapping("/ribbon/match") 29 | public Collection match(double longitude, double latitude) { 30 | return this.positionApi.match(longitude, latitude); 31 | } 32 | 33 | @PostMapping("/intentions/place") 34 | public void place(@RequestBody 35 | MyIntention myIntention) { 36 | intentionService.placeIntention(myIntention.getUserId(), 37 | myIntention.getStartLongitude(), myIntention.getStartLatitude(), 38 | myIntention.getDestLongitude(), myIntention.getDestLatitude()); 39 | } 40 | 41 | @PostMapping("/intention/confirm") 42 | public boolean confirm(int driverId, int intentionId) { 43 | return intentionService.confirmIntention(driverId, intentionId); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /uc/src/test/java/club/newtech/qbike/uc/TableTransfer.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.uc; 2 | 3 | import club.newtech.qbike.uc.domain.Type; 4 | import club.newtech.qbike.uc.domain.repository.PoiRepository; 5 | import club.newtech.qbike.uc.domain.repository.UserRepository; 6 | import club.newtech.qbike.uc.domain.root.Poi; 7 | import club.newtech.qbike.uc.domain.root.User; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.annotation.Rollback; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | import org.springframework.transaction.annotation.Transactional; 15 | 16 | import java.util.List; 17 | 18 | @RunWith(SpringRunner.class) 19 | @SpringBootTest 20 | @Transactional 21 | public class TableTransfer { 22 | @Autowired 23 | PoiRepository poiRepository; 24 | @Autowired 25 | UserRepository userRepository; 26 | 27 | @Test 28 | @Rollback(false) 29 | public void transfer() { 30 | List pois = poiRepository.findAll(); 31 | pois.forEach(poi -> { 32 | User user = new User(); 33 | user.setId(poi.getId()); 34 | String phone = poi.getCellPhone(); 35 | if (phone.contains(".")) { 36 | phone = phone.substring(0, phone.indexOf(".")+1); 37 | } 38 | user.setMobile(poi.getCellPhone()); 39 | user.setType(Type.Driver); 40 | user.setUserName(poi.getLinkMan()); 41 | user.setCity(poi.getCity()); 42 | user.setDistrict(poi.getDistrict()); 43 | user.setOriginAddress(poi.getOriginAddress()); 44 | user.setProvince(poi.getProvince()); 45 | user.setStreet(poi.getStreet()); 46 | System.out.println(user); 47 | userRepository.save(user); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /intention/src/test/java/club/newtech/qbike/intention/IntentionTest.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention; 2 | 3 | import club.newtech.qbike.intention.domain.core.vo.Status; 4 | import club.newtech.qbike.intention.domain.core.root.Intention; 5 | import club.newtech.qbike.intention.domain.core.vo.Candidate; 6 | import club.newtech.qbike.intention.domain.core.vo.Customer; 7 | import club.newtech.qbike.intention.domain.repository.CandidateRepository; 8 | import club.newtech.qbike.intention.domain.repository.IntentionRepository; 9 | import org.junit.Assert; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.test.context.junit4.SpringRunner; 15 | 16 | import java.util.Date; 17 | 18 | @RunWith(SpringRunner.class) 19 | @SpringBootTest 20 | public class IntentionTest { 21 | @Autowired 22 | CandidateRepository candidateRepository; 23 | @Autowired 24 | IntentionRepository intentionRepository; 25 | 26 | @Test 27 | public void saveIntention() { 28 | Intention intention = new Intention(); 29 | intention.setStatus(Status.Inited); 30 | Customer customer = new Customer(); 31 | customer.setCustomerId(1032); 32 | customer.setCustomerName("hello"); 33 | customer.setCustomerMobile("13900001111"); 34 | intention.setCustomer(customer); 35 | intentionRepository.save(intention); 36 | Candidate candidate = new Candidate(); 37 | candidate.setIntention(intention); 38 | candidate.setDriverId(100); 39 | candidate.setCreated(new Date()); 40 | 41 | candidateRepository.save(candidate); 42 | 43 | int id = intention.getMid(); 44 | Intention another = intentionRepository.findById(id).orElse(null); 45 | Assert.assertEquals(1, another.getCandidates().size()); 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /mysql/config.cnf: -------------------------------------------------------------------------------- 1 | # 2 | # This program is free software; you can redistribute it and/or modify 3 | # it under the terms of the GNU General Public License as published by 4 | # the Free Software Foundation; version 2 of the License. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program; if not, write to the Free Software 13 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 14 | 15 | # 16 | # The MySQL Community Server configuration file. 17 | # 18 | # For explanations see 19 | # http://dev.mysql.com/doc/mysql/en/server-system-variables.html 20 | 21 | #[client] 22 | #port = 3306 23 | #socket = /var/run/mysqld/mysqld.sock 24 | 25 | #[mysqld_safe] 26 | #pid-file = /var/run/mysqld/mysqld.pid 27 | #socket = /var/run/mysqld/mysqld.sock 28 | #nice = 0 29 | 30 | [mysqld] 31 | #skip-host-cache 32 | #skip-customerName-resolve 33 | #user = mysql 34 | #pid-file = /var/run/mysqld/mysqld.pid 35 | #socket = /var/run/mysqld/mysqld.sock 36 | #port = 3306 37 | #basedir = /usr 38 | #datadir = /var/lib/mysql 39 | #tmpdir = /tmp 40 | #lc-messages-dir = /usr/share/mysql 41 | #explicit_defaults_for_timestamp 42 | lower_case_table_names = 1 43 | # Instead of skip-networking the default is now to listen only on 44 | # localhost which is more compatible and is not less secure. 45 | #bind-address = 127.0.0.1 46 | 47 | #log-error = /var/log/mysql/error.log 48 | 49 | # Recommended in standard MySQL setup 50 | #sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES 51 | 52 | # Disabling symbolic-links is recommended to prevent assorted security risks 53 | #symbolic-links=0 54 | 55 | # * IMPORTANT: Additional settings that can override those from this file! 56 | # The files must end with '.cnf', otherwise they'll be ignored. 57 | # 58 | #includedir /etc/mysql/conf.d/ 59 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/service/Receiver.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.service; 2 | 3 | import club.newtech.qbike.order.domain.core.vo.IntentionVo; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.amqp.rabbit.annotation.RabbitHandler; 8 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Component; 11 | 12 | @Component 13 | @RabbitListener(queues = "intention") 14 | public class Receiver { 15 | private static final Logger LOGGER = LoggerFactory.getLogger(Receiver.class); 16 | ObjectMapper mapper = new ObjectMapper(); 17 | @Autowired 18 | private OrderService orderService; 19 | 20 | @RabbitHandler 21 | public void receiveMessage(String message) { 22 | LOGGER.info("Received new intention <" + message + ">"); 23 | try { 24 | // String[] values = message.split("\\|"); 25 | // if (values.length == 6) { 26 | // IntentionVo intentionVo = 27 | // new IntentionVo(values[0], 28 | // Double.parseDouble(values[1]), 29 | // Double.parseDouble(values[2]), 30 | // Double.parseDouble(values[3]), 31 | // Double.parseDouble(values[4]), 32 | // values[5], 33 | // 2000L); 34 | // 35 | // orderService.put(intentionVo); 36 | // } 37 | IntentionVo vo = mapper.readValue(message, IntentionVo.class); 38 | orderService.createOrder(vo); 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | } 42 | } 43 | 44 | public void receivePositionUpdate(String message) { 45 | LOGGER.info("Received position update " + message); 46 | try { 47 | String[] values = message.split("\\|"); 48 | } catch (Exception e) { 49 | e.printStackTrace(); 50 | } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/controller/OrderController.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.controller; 2 | 3 | import club.newtech.qbike.order.domain.core.vo.Events; 4 | import club.newtech.qbike.order.domain.core.vo.StateRequest; 5 | import club.newtech.qbike.order.domain.exception.OrderRuntimeException; 6 | import club.newtech.qbike.order.domain.repository.OrderRepository; 7 | import club.newtech.qbike.order.domain.service.FsmService; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.util.Arrays; 13 | import java.util.List; 14 | import java.util.UUID; 15 | 16 | @RestController 17 | public class OrderController { 18 | @Autowired 19 | FsmService fsmService; 20 | @Autowired 21 | OrderRepository orderRepository; 22 | 23 | @PostMapping("/order/cancel") 24 | public List cancelOrder(int driverId, String orderId) { 25 | StateRequest stateRequest = new StateRequest(); 26 | stateRequest.setEvent(Events.CANCEL); 27 | stateRequest.setData(orderRepository.findById(orderId).get()); 28 | stateRequest.setUId(UUID.randomUUID().toString()); 29 | stateRequest.setOrderId(orderId); 30 | stateRequest.setUserId(driverId); 31 | try { 32 | fsmService.changeState(stateRequest); 33 | return Arrays.asList("success", ""); 34 | } catch (OrderRuntimeException oe) { 35 | return Arrays.asList(oe.getErrorCode(), oe.getErrorMessage()); 36 | } 37 | } 38 | 39 | @PostMapping("/order/aboard") 40 | public List aboard(int driverId, String orderId) { 41 | StateRequest stateRequest = new StateRequest(); 42 | stateRequest.setEvent(Events.ABOARD); 43 | stateRequest.setData(orderRepository.findById(orderId).get()); 44 | stateRequest.setUId(UUID.randomUUID().toString()); 45 | stateRequest.setOrderId(orderId); 46 | stateRequest.setUserId(driverId); 47 | try { 48 | fsmService.changeState(stateRequest); 49 | return Arrays.asList("success", ""); 50 | } catch (OrderRuntimeException oe) { 51 | return Arrays.asList(oe.getErrorCode(), oe.getErrorMessage()); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /testclient/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | testclient 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | testclient 11 | Demo project for Spring Boot 12 | 13 | 14 | club.newtech.qbike 15 | demo 16 | 0.0.1-SNAPSHOT 17 | 18 | 19 | 20 | UTF-8 21 | UTF-8 22 | 1.8 23 | Edgware.SR3 24 | 25 | 26 | 27 | 28 | org.springframework.cloud 29 | spring-cloud-starter-netflix-eureka-client 30 | 31 | 32 | 33 | org.springframework.cloud 34 | spring-cloud-starter-netflix-ribbon 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-test 39 | test 40 | 41 | 42 | org.projectlombok 43 | lombok 44 | 1.16.16 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-data-rest 49 | 50 | 51 | org.springframework.cloud 52 | spring-cloud-starter-zipkin 53 | 54 | 55 | org.springframework.amqp 56 | spring-rabbit 57 | 58 | 59 | -------------------------------------------------------------------------------- /order/src/test/java/club/newtech/qbike/order/OrderTest.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order; 2 | 3 | import club.newtech.qbike.order.domain.core.root.Order; 4 | import club.newtech.qbike.order.domain.core.vo.Events; 5 | import club.newtech.qbike.order.domain.core.vo.IntentionVo; 6 | import club.newtech.qbike.order.domain.core.vo.StateRequest; 7 | import club.newtech.qbike.order.domain.repository.OrderRepository; 8 | import club.newtech.qbike.order.domain.service.FsmService; 9 | import club.newtech.qbike.order.domain.service.OrderService; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.test.context.junit4.SpringRunner; 15 | 16 | import java.util.UUID; 17 | 18 | @RunWith(SpringRunner.class) 19 | @SpringBootTest 20 | public class OrderTest { 21 | @Autowired 22 | FsmService fsmService; 23 | @Autowired 24 | OrderService orderService; 25 | @Autowired 26 | OrderRepository orderRepository; 27 | 28 | @Test 29 | public void changeState() { 30 | 31 | Order order = orderRepository.findById("T0000000005").orElse(null); 32 | StateRequest stateRequest = new StateRequest(); 33 | stateRequest.setOrderId(order.getOid()); 34 | stateRequest.setUId(UUID.randomUUID().toString()); 35 | stateRequest.setUserId(96); 36 | stateRequest.setData(order); 37 | stateRequest.setEvent(Events.ABOARD); 38 | fsmService.changeState(stateRequest); 39 | 40 | } 41 | @Test 42 | public void createAndChange() { 43 | IntentionVo intentionVo = new IntentionVo(); 44 | intentionVo.setCustomerId(84); 45 | intentionVo.setDriverId(96); 46 | intentionVo.setIntentionId(1); 47 | intentionVo.setStartLong(118.71334176065); 48 | intentionVo.setStartLat(32.012518207527); 49 | intentionVo.setDestLong(118.81722069709); 50 | intentionVo.setDestLat(32.007969136143); 51 | Order order = orderService.createOrder(intentionVo); 52 | StateRequest stateRequest = new StateRequest(); 53 | stateRequest.setOrderId(order.getOid()); 54 | stateRequest.setUId(UUID.randomUUID().toString()); 55 | stateRequest.setUserId(96); 56 | stateRequest.setData(order); 57 | stateRequest.setEvent(Events.ABOARD); 58 | fsmService.changeState(stateRequest); 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /microservice-discovery-eureka/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | club.newtech.qbike 6 | microservice-discovery-eureka 7 | jar 8 | 9 | 10 | club.newtech.qbike 11 | demo 12 | 0.0.1-SNAPSHOT 13 | 14 | 15 | 16 | 17 | org.springframework.cloud 18 | spring-cloud-starter-config 19 | 20 | 21 | org.springframework.cloud 22 | spring-cloud-starter-netflix-eureka-server 23 | 24 | 25 | 26 | 27 | 28 | 29 | com.spotify 30 | docker-maven-plugin 31 | 32 | 33 | build-image 34 | package 35 | 36 | build 37 | 38 | 39 | 40 | 41 | ${docker.image.prefix}/${project.artifactId} 42 | ./${project.artifactId}/src/main/docker 43 | java 44 | ["java", "-jar", "/${project.build.finalName}.jar", 45 | "--spring.profiles.active=docker"]] 46 | 47 | 48 | 49 | / 50 | ${project.build.directory} 51 | ${project.build.finalName}.jar 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/infrastructure/UserRibbonHystrixApi.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.infrastructure; 2 | 3 | import club.newtech.qbike.intention.domain.core.vo.Customer; 4 | import club.newtech.qbike.intention.domain.core.vo.DriverVo; 5 | import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | import java.util.Map; 13 | 14 | @Service 15 | public class UserRibbonHystrixApi { 16 | private static final Logger LOGGER = LoggerFactory.getLogger(UserRibbonHystrixApi.class); 17 | @Autowired 18 | private RestTemplate restTemplate; 19 | 20 | /** 21 | * 使用@HystrixCommand注解指定当该方法发生异常时调用的方法 22 | * 23 | * @param id customerId 24 | * @return 通过id查询到的用户 25 | */ 26 | @HystrixCommand 27 | public Customer findById(Integer id) { 28 | // return this.restTemplate.getForObject("http://QBIKE-UC/users/" + customerId, Customer.class); 29 | Map ret = restTemplate.getForObject("http://QBIKE-UC/users/" + id, Map.class); 30 | Customer customerVo = new Customer(); 31 | customerVo.setCustomerId(id); 32 | customerVo.setCustomerMobile(String.valueOf(ret.get("mobile"))); 33 | customerVo.setCustomerName(String.valueOf(ret.get("userName"))); 34 | customerVo.setUserType(String.valueOf(ret.get("type"))); 35 | return customerVo; 36 | } 37 | 38 | @HystrixCommand 39 | public DriverVo findDriverById(Integer id) { 40 | Map ret = restTemplate.getForObject("http://QBIKE-UC/users/" + id, Map.class); 41 | DriverVo driverVo = new DriverVo(); 42 | driverVo.setId(id); 43 | driverVo.setMobile(String.valueOf(ret.get("mobile"))); 44 | driverVo.setUserName(String.valueOf(ret.get("userName"))); 45 | driverVo.setType(String.valueOf(ret.get("type"))); 46 | return driverVo; 47 | } 48 | 49 | /** 50 | * hystrix fallback方法 51 | * 52 | * @param id customerId 53 | * @return 默认的用户 54 | */ 55 | public Customer fallback(Integer id) { 56 | UserRibbonHystrixApi.LOGGER.info("异常发生,进入fallback方法,接收的参数:customerId = {}", id); 57 | Customer customer = new Customer(); 58 | customer.setCustomerId(-1); 59 | customer.setCustomerName("default username"); 60 | customer.setCustomerMobile("0000"); 61 | return customer; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 滴滴电动车 2 | ---- 3 | 本项目纯属虚构,如有雷同实属巧合 4 | 5 | ### 项目背景 6 | 7 | 在嘀嘀打车的创业成功经验引领下你的创业团队发现了一个拥挤的大城市的电动车机会。 8 | 你们发现电动车由于其灵活性能够在拥挤的路面上穿行,从而能够帮助很多上班族避免早晚高峰,按时到达目的地,比滴滴打车更方便。 9 | 你们决定设计一个电动车的打车平台,能够在早晚高峰时匹配电动车司机和有需要的乘客。 10 | 11 | ### Quick Start 12 | 启动docker daemon进程 13 | mvn clean install -DskipTests=true 14 | docker-compose up mysql 15 | ctrl-c退出 16 | docker-compose up 17 | 18 | http://localhost:8050 19 | 20 | ### 测试接口 21 | 安装postman,导入qbkie.postman_collection.json 22 | 调用fetchUser接口,注意,spring cloud第一次调用可能会失败,需要预热一段时间。 23 | 出现结果后,调用updateDriverPostion接口,将司机的坐标导入到redis中 24 | 然后就可以使用placeIntention接口发起一个请求 25 | 然后再用confirmIntention接口,返回true就是成功匹配了,Order就会生成 26 | 注意:confirm的接口中需要输入intentionId,如果是第一次,就是1,不清楚,可以到docker的MySQL的表t_intention中找 27 | 28 | 在http://127.0.0.1:9411上可以找到zipkin对这几次调用的结果的追踪 29 | ![zipkin](http://os8wjvykw.bkt.clouddn.com/2018-07-05-035127.png) 30 | 31 | ### Dev 32 | 请安装Lombok插件,Java8 Stream + Lombok + Spring boot, 你会拥有一个全新的Java开发体验。 33 | 34 | ### TODO 35 | * [x] 完全基于领域驱动设计 36 | * [x] 聚合根、领域对象、API的提炼按照`Event Storming`过程分析 37 | * [x] 升级到`Spring Boot 2.0` 和`Spring Cloud Finchley` 38 | * [x] `Eureka`服务注册与发现 39 | * [ ] `Swagger2`管理`REST API` 40 | * [x] `Hystrix`服务的保护与容错 41 | * [x] `Zuul`网关应用 42 | * [x] `Zipkin`分布式追踪 43 | 44 | 45 | ### 业务场景 46 | 47 | 初步设计的场景比较简单,主要是匹配有需求的乘客附近的电动车司机。经过前期的需求梳理,产出了下列的基本功能 48 | 49 | - 司机发布自己的位置和状态(默认一辆电动车只能搭载一位乘客) 50 | - 乘客提交行程需求(行程需要包含起点和终点,只支持实时提交,不支持预约) 51 | - 系统自动为乘客匹配合适的司机,将需求发送至司机手中 52 | - 司机确认是否接受行程 53 | - 司机和乘客按订单完成行程 54 | - 乘客行程途中要求改变行程 55 | - 系统计费及完成订单 56 | - 乘客对司机的评价 57 | 58 | 59 | ![主要场景.005](https://user-images.githubusercontent.com/3287183/50633723-54d8b280-0f87-11e9-84a9-c8727872a443.png) 60 | 61 | 62 | ### 业务流程 63 | 64 | ![业务流程.006](https://user-images.githubusercontent.com/3287183/50633730-5c985700-0f87-11e9-8232-7bf638bf70fc.png) 65 | 66 | 67 | ### 事件 68 | 69 | ![事件.008](https://user-images.githubusercontent.com/3287183/50633743-6a4ddc80-0f87-11e9-8c8b-f011c84d0314.png) 70 | 71 | 72 | ### 命令 73 | 74 | ![滴滴电动车需求.009](https://user-images.githubusercontent.com/3287183/50633750-70dc5400-0f87-11e9-9296-a731b7f3f899.png) 75 | 76 | 77 | ### 聚合识别 78 | 79 | 80 | ![滴滴电动车需求.010](https://user-images.githubusercontent.com/3287183/50633761-7afe5280-0f87-11e9-8156-bdd898c1e106.png) 81 | 82 | ![滴滴电动车需求.011](https://user-images.githubusercontent.com/3287183/50633776-881b4180-0f87-11e9-95ef-7286280e5af1.png) 83 | 84 | 85 | 86 | 87 | ### 系统架构 88 | 89 | 90 | 91 | ![image](https://user-images.githubusercontent.com/3287183/50633782-8e112280-0f87-11e9-9ac3-0f50941a2ae7.png) 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/service/LockService.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.service; 2 | 3 | 4 | import club.newtech.qbike.intention.domain.core.vo.Lock; 5 | import club.newtech.qbike.intention.domain.exception.LockExistsException; 6 | import club.newtech.qbike.intention.domain.exception.LockNotHeldException; 7 | 8 | import java.util.Set; 9 | 10 | public interface LockService { 11 | /** 12 | * Iterate the existing locks. 13 | * 14 | * @return an iterable of all locks 15 | */ 16 | Iterable findAll(); 17 | 18 | /** 19 | * Acquire a lock by customerName. Only one process (globally) should be able to obtain and 20 | * hold the lock with this customerName at any given time. Locks expire and can also be 21 | * released by the owner, so after either of those events the lock can be acquired by 22 | * the same or a different process. 23 | * 24 | * @param name the customerName identifying the lock 25 | * @return a Lock containing a value that can be used to release or refresh the lock 26 | * @throws LockExistsException 27 | */ 28 | Lock create(String name) throws LockExistsException; 29 | 30 | Set createMultiLock(Set names) throws LockExistsException; 31 | 32 | /** 33 | * Release a lock before it expires. Only the holder of a lock can release it, and the 34 | * holder must have the correct unique value to prove that he holds it. 35 | * 36 | * @param name the customerName of the lock 37 | * @param value the value of the lock (which has to match the value when it was 38 | * acquired) 39 | * @return true if successful 40 | * @throws LockNotHeldException 41 | */ 42 | boolean release(String name, String value) throws LockNotHeldException; 43 | 44 | 45 | /** 46 | * The holder of a lock can refresh it, extending its expiry. If the caller does not 47 | * hold the lock there will be an exception, but the implementation may not be able to 48 | * tell if it was because he formerly held the lock and it expired, or if it simply 49 | * was never held. 50 | * 51 | * @param name the customerName of the lock 52 | * @param value the value of the lock (which has to match the value when it was 53 | * acquired) 54 | * @return a new lock with a new value and a new expiry 55 | * @throws LockNotHeldException if the value does not match the current value or if 56 | * the lock doesn't exist (e.g. if it expired) 57 | */ 58 | Lock refresh(String name, String value) throws LockNotHeldException; 59 | } 60 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/infrastructure/UserRibbonHystrixApi.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.infrastructure; 2 | 3 | import club.newtech.qbike.order.domain.core.vo.CustomerVo; 4 | import club.newtech.qbike.order.domain.core.vo.DriverVo; 5 | import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | import java.util.Map; 13 | 14 | @Service 15 | public class UserRibbonHystrixApi { 16 | private static final Logger LOGGER = LoggerFactory.getLogger(UserRibbonHystrixApi.class); 17 | @Autowired 18 | private RestTemplate restTemplate; 19 | 20 | /** 21 | * 使用@HystrixCommand注解指定当该方法发生异常时调用的方法 22 | * 23 | * @param id customerId 24 | * @return 通过id查询到的用户 25 | */ 26 | @HystrixCommand() 27 | public CustomerVo findCustomerById(Integer id) { 28 | Map ret = restTemplate.getForObject("http://QBIKE-UC/users/" + id, Map.class); 29 | CustomerVo customerVo = new CustomerVo(); 30 | customerVo.setCustomerId(id); 31 | customerVo.setCustomerMobile(String.valueOf(ret.get("mobile"))); 32 | customerVo.setCustomerName(String.valueOf(ret.get("userName"))); 33 | return customerVo; 34 | } 35 | 36 | @HystrixCommand() 37 | public DriverVo findDriverById(Integer id) { 38 | Map ret = restTemplate.getForObject("http://QBIKE-UC/users/" + id, Map.class); 39 | DriverVo driverVo = new DriverVo(); 40 | driverVo.setDriverId(id); 41 | driverVo.setDriverMobile(String.valueOf(ret.get("mobile"))); 42 | driverVo.setDriverName(String.valueOf(ret.get("userName"))); 43 | return driverVo; 44 | } 45 | 46 | /** 47 | * hystrix fallback方法 48 | * 49 | * @param id customerId 50 | * @return 默认的用户 51 | */ 52 | public CustomerVo fallback(Integer id) { 53 | LOGGER.info("异常发生,进入fallback方法,接收的参数:customerId = {}", id); 54 | CustomerVo customer = new CustomerVo(); 55 | customer.setCustomerId(-1); 56 | customer.setCustomerName("default username"); 57 | customer.setCustomerMobile("0000"); 58 | return customer; 59 | } 60 | 61 | public DriverVo fallbackDriver(Integer id) { 62 | LOGGER.info("异常发生,进入fallback方法,接收的参数:customerId = {}", id); 63 | DriverVo driverVo = new DriverVo(); 64 | driverVo.setDriverId(-1); 65 | driverVo.setDriverName("default"); 66 | driverVo.setDriverMobile("0000"); 67 | return driverVo; 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/service/FsmService.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.service; 2 | 3 | import club.newtech.qbike.order.domain.core.root.Order; 4 | import club.newtech.qbike.order.domain.core.vo.Events; 5 | import club.newtech.qbike.order.domain.core.vo.FlowState; 6 | import club.newtech.qbike.order.domain.core.vo.StateRequest; 7 | import club.newtech.qbike.order.domain.exception.OrderRuntimeException; 8 | import club.newtech.qbike.order.domain.repository.OrderRepository; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.statemachine.StateMachine; 13 | import org.springframework.stereotype.Service; 14 | 15 | @Service 16 | public class FsmService { 17 | 18 | public static final String UUID_KEY = "UUID_KEY"; 19 | private static final Logger LOGGER = LoggerFactory.getLogger(FsmService.class); 20 | @Autowired 21 | private OrderRepository orderRepository; 22 | @Autowired 23 | private StateMachineBuilderFactory builderFactory; 24 | 25 | /** 26 | * 修改订单状态 27 | * extendedState中存放有两个对象,key为StateRequest.class为最新获得的request数据,key为Order.class的是已有订单数据 28 | * 29 | * @param request 状态迁移请求 30 | * @return 31 | */ 32 | public void changeState(StateRequest request) { 33 | //1、查找订单信息 34 | Order order = orderRepository.findById(request.getOrderId()).orElse(null); 35 | if (order == null) { 36 | throw new OrderRuntimeException("030001", new Object[]{request.getOrderId()}); 37 | } 38 | 39 | 40 | //2. 根据订单创建状态机 41 | StateMachine stateMachine = builderFactory.create(order); 42 | if (request.getData() != null) { 43 | stateMachine.getExtendedState().getVariables().put(StateRequest.class, request.getData()); 44 | } 45 | //上下文 调用链UUID 46 | stateMachine.getExtendedState().getVariables().put(UUID_KEY, request.getUId()); 47 | 48 | //3. 发送当前请求的状态 49 | boolean isSend = stateMachine.sendEvent(request.getEvent()); 50 | 51 | if (!isSend) { 52 | LOGGER.error(String.format("无法从状态[%s]转向 => [%s]", FlowState.forValue(order.getOrderStatus()), request.getEvent())); 53 | throw new OrderRuntimeException("010003"); 54 | } 55 | 56 | Exception error = stateMachine.getExtendedState().get(Exception.class, Exception.class); 57 | if (error != null) { 58 | if (error.getClass().equals(OrderRuntimeException.class)) { 59 | throw (OrderRuntimeException) error; 60 | } else { 61 | throw new OrderRuntimeException("000000", error); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /qbike.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "9755bb7f-6e86-46a9-84a0-b472b827c3fc", 4 | "name": "qbike", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "fetchUser", 10 | "request": { 11 | "method": "GET", 12 | "header": [], 13 | "body": {}, 14 | "url": { 15 | "raw": "http://127.0.0.1:8050/qbike-intention/ribbon/1271", 16 | "protocol": "http", 17 | "host": [ 18 | "127", 19 | "0", 20 | "0", 21 | "1" 22 | ], 23 | "port": "8050", 24 | "path": [ 25 | "qbike-intention", 26 | "ribbon", 27 | "1271" 28 | ] 29 | } 30 | }, 31 | "response": [] 32 | }, 33 | { 34 | "name": "placeIntention", 35 | "request": { 36 | "method": "POST", 37 | "header": [ 38 | { 39 | "key": "Content-Type", 40 | "value": "application/json" 41 | } 42 | ], 43 | "body": { 44 | "mode": "raw", 45 | "raw": "{\n \"destLatitude\": 32.012518207527,\n \"destLongitude\": 118.71334176065,\n \"startLatitude\": 32.007969136143,\n \"startLongitude\": 118.81722069709,\n \"userId\": 1270\n}" 46 | }, 47 | "url": { 48 | "raw": "http://127.0.0.1:8050/qbike-intention/intentions/place", 49 | "protocol": "http", 50 | "host": [ 51 | "127", 52 | "0", 53 | "0", 54 | "1" 55 | ], 56 | "port": "8050", 57 | "path": [ 58 | "qbike-intention", 59 | "intentions", 60 | "place" 61 | ] 62 | } 63 | }, 64 | "response": [] 65 | }, 66 | { 67 | "name": "confirmIntention", 68 | "request": { 69 | "method": "GET", 70 | "header": [ 71 | { 72 | "key": "Content-Type", 73 | "value": "application/json" 74 | } 75 | ], 76 | "body": { 77 | "mode": "raw", 78 | "raw": "" 79 | }, 80 | "url": { 81 | "raw": "http://localhost:8050/qbike-intention/intention/confirm?driverId=50&intentionId=44", 82 | "protocol": "http", 83 | "host": [ 84 | "localhost" 85 | ], 86 | "port": "8050", 87 | "path": [ 88 | "qbike-intention", 89 | "intention", 90 | "confirm" 91 | ], 92 | "query": [ 93 | { 94 | "key": "driverId", 95 | "value": "50" 96 | }, 97 | { 98 | "key": "intentionId", 99 | "value": "44" 100 | } 101 | ] 102 | } 103 | }, 104 | "response": [] 105 | }, 106 | { 107 | "name": "updateDriverPositon", 108 | "request": { 109 | "method": "GET", 110 | "header": [], 111 | "body": {}, 112 | "url": { 113 | "raw": "" 114 | }, 115 | "description": "更新司机的坐标" 116 | }, 117 | "response": [] 118 | } 119 | ] 120 | } -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/OrderApplication.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order; 2 | 3 | import club.newtech.qbike.order.domain.service.OrderService; 4 | import club.newtech.qbike.order.domain.service.Receiver; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 9 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 10 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.data.redis.connection.RedisConnectionFactory; 13 | import org.springframework.data.redis.listener.PatternTopic; 14 | import org.springframework.data.redis.listener.RedisMessageListenerContainer; 15 | import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; 16 | import org.springframework.web.client.RestTemplate; 17 | 18 | @SpringBootApplication 19 | @EnableDiscoveryClient 20 | @EnableCircuitBreaker 21 | public class OrderApplication { 22 | public static void main(String[] args) { 23 | SpringApplication.run(OrderApplication.class, args); 24 | } 25 | // 26 | // @Bean 27 | // RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, 28 | // MessageListenerAdapter intentionListener, 29 | // MessageListenerAdapter positionListener) { 30 | // 31 | // RedisMessageListenerContainer container = new RedisMessageListenerContainer(); 32 | // container.setConnectionFactory(connectionFactory); 33 | // container.addMessageListener(intentionListener, new PatternTopic("intention")); 34 | // container.addMessageListener(positionListener, new PatternTopic("position")); 35 | // return container; 36 | // } 37 | 38 | // @Bean(name = "intentionListener") 39 | // MessageListenerAdapter intentionListener(Receiver receiver) { 40 | // return new MessageListenerAdapter(receiver, "receiveMessage"); 41 | // } 42 | // 43 | // @Bean(name = "positionListener") 44 | // MessageListenerAdapter positionListener(Receiver receiver) { 45 | // return new MessageListenerAdapter(receiver, "receivePositionUpdate"); 46 | // } 47 | 48 | // @Bean 49 | // Receiver receiver(OrderService service, ObjectMapper objectMapper) { 50 | // return new Receiver(service, objectMapper); 51 | // } 52 | // 53 | // @Bean 54 | // public ObjectMapper objectMapper() { 55 | // return new ObjectMapper(); 56 | // } 57 | 58 | /** 59 | * 实例化RestTemplate,通过@LoadBalanced注解开启均衡负载能力. 60 | * 61 | * @return restTemplate 62 | */ 63 | @Bean 64 | @LoadBalanced 65 | public RestTemplate restTemplate() { 66 | return new RestTemplate(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /api-gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | demo 7 | club.newtech.qbike 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | api-gateway 13 | jar 14 | 15 | 16 | UTF-8 17 | UTF-8 18 | 1.8 19 | 2.7.0 20 | 21 | 22 | 23 | 24 | org.springframework.cloud 25 | spring-cloud-starter-netflix-eureka-client 26 | 27 | 28 | org.springframework.cloud 29 | spring-cloud-starter-zipkin 30 | 31 | 32 | org.springframework.amqp 33 | spring-rabbit 34 | 35 | 36 | org.springframework.cloud 37 | spring-cloud-starter-netflix-zuul 38 | 39 | 40 | 41 | 42 | 43 | com.spotify 44 | docker-maven-plugin 45 | 46 | 47 | build-image 48 | package 49 | 50 | build 51 | 52 | 53 | 54 | 55 | ${docker.image.prefix}/${project.artifactId} 56 | java 57 | ["java", "-jar", "/${project.build.finalName}.jar", "--spring.profiles.active=docker"]] 58 | 59 | 60 | / 61 | ${project.build.directory} 62 | ${project.build.finalName}.jar 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | rabbit: 4 | image: "rabbitmq:3-management" 5 | hostname: "rabbit" 6 | environment: 7 | RABBITMQ_ERLANG_COOKIE: "SWQOKODSQALRPCLNMEQG" 8 | RABBITMQ_DEFAULT_USER: "guest" 9 | RABBITMQ_DEFAULT_PASS: "guest" 10 | RABBITMQ_DEFAULT_VHOST: "/" 11 | ports: 12 | - "15672" 13 | - "5672" 14 | labels: 15 | NAME: "rabbitmq" 16 | redis: 17 | image: redis:latest 18 | ports: 19 | - "16379:6379" 20 | volumes: 21 | - ./data/redisdata:/data 22 | mysql: 23 | image : mysql:5.7 24 | ports: 25 | - "13306:3306" 26 | restart: always 27 | environment: 28 | - MYSQL_ROOT_PASSWORD=root 29 | volumes: 30 | - ./mysql:/docker-entrypoint-initdb.d 31 | - ./mysql:/etc/mysql/conf.d 32 | - ./data/mysqldata:/var/lib/mysql 33 | eureka: 34 | image: club.newtech/microservice-discovery-eureka 35 | ports: 36 | - "8761:8761" 37 | uc: 38 | image: club.newtech/uc 39 | ports: 40 | - "8000" 41 | environment: 42 | - REDIS_HOST=redis 43 | - REDIS_DB=0 44 | - DB_HOST=mysql 45 | - DB_SCHEMA=qbike 46 | - EUREKA_HOST=eureka 47 | - RABBIT=rabbit 48 | links: 49 | - eureka 50 | - mysql 51 | - rabbit 52 | intention: 53 | image: club.newtech/intention 54 | ports: 55 | - "8001" 56 | environment: 57 | - REDIS_HOST=redis 58 | - REDIS_DB=0 59 | - DB_HOST=mysql 60 | - DB_SCHEMA=qbike 61 | - EUREKA_HOST=eureka 62 | - RABBIT=rabbit 63 | links: 64 | - eureka 65 | - mysql 66 | - rabbit 67 | position: 68 | image: club.newtech/position 69 | ports: 70 | - "8003" 71 | environment: 72 | - REDIS_HOST=redis 73 | - REDIS_DB=0 74 | - DB_HOST=mysql 75 | - DB_SCHEMA=qbike 76 | - EUREKA_HOST=eureka 77 | - RABBIT=rabbit 78 | links: 79 | - eureka 80 | - mysql 81 | - rabbit 82 | order: 83 | image: club.newtech/order 84 | ports: 85 | - "8002" 86 | environment: 87 | - REDIS_HOST=redis 88 | - REDIS_DB=0 89 | - DB_HOST=mysql 90 | - DB_SCHEMA=qbike 91 | - EUREKA_HOST=eureka 92 | - RABBIT=rabbit 93 | links: 94 | - eureka 95 | - mysql 96 | - rabbit 97 | api-gateway: 98 | image: club.newtech/api-gateway 99 | ports: 100 | - "8050:8050" 101 | environment: 102 | - REDIS_HOST=redis 103 | - REDIS_DB=0 104 | - DB_HOST=mysql 105 | - DB_SCHEMA=qbike 106 | - EUREKA_HOST=eureka 107 | - RABBIT=rabbit 108 | links: 109 | - eureka 110 | - rabbit 111 | zipkin: 112 | image: openzipkin/zipkin 113 | ports: 114 | - "9411:9411" 115 | environment: 116 | - RABBIT_ADDRESSES=rabbit 117 | # volumes: 118 | # - ./wait-for-it.sh:/wait-for-it.sh 119 | depends_on: 120 | - rabbit 121 | links: 122 | - rabbit 123 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/exception/OrderRuntimeException.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.exception; 2 | 3 | import club.newtech.qbike.order.util.SpringContextHolder; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.springframework.context.MessageSource; 8 | import org.springframework.context.NoSuchMessageException; 9 | 10 | import java.util.Locale; 11 | 12 | /** 13 | * 运行时异常 14 | * Created by jinwei on 12/1/2017. 15 | */ 16 | @Data 17 | @EqualsAndHashCode(callSuper = true) 18 | public class OrderRuntimeException extends RuntimeException { 19 | 20 | private String errorCode = ""; 21 | 22 | private String errorMessage = ""; 23 | 24 | private Object[] params; 25 | 26 | private MessageSource messageSource = SpringContextHolder.getBean(MessageSource.class); 27 | 28 | public OrderRuntimeException() { 29 | super(); 30 | this.errorCode = "000000"; 31 | } 32 | 33 | public OrderRuntimeException(String errorCode) { 34 | super(); 35 | this.errorCode = errorCode; 36 | this.params = null; 37 | } 38 | 39 | public OrderRuntimeException(String errorCode, Object[] params) { 40 | super(); 41 | this.errorCode = errorCode; 42 | this.params = params; 43 | } 44 | 45 | public OrderRuntimeException(String errorCode, Throwable cause) { 46 | super(cause); 47 | this.errorCode = errorCode; 48 | this.params = null; 49 | } 50 | 51 | public OrderRuntimeException(String errorCode, Object[] params, Throwable cause) { 52 | super(cause); 53 | this.errorCode = errorCode; 54 | this.params = params; 55 | } 56 | 57 | public OrderRuntimeException(Throwable cause) { 58 | super(cause); 59 | 60 | } 61 | 62 | 63 | /** 64 | * 生成异常信息 65 | * 66 | * @return 67 | */ 68 | public String getErrorMessage() { 69 | String msg = ""; 70 | Throwable cause = this.getCause(); 71 | 72 | String errorCode = this.getErrorCode(); 73 | 74 | if (StringUtils.isNotEmpty(errorCode) && StringUtils.isEmpty(msg)) { 75 | //2、如果有异常码,以异常码对应的提示信息为准 76 | msg = this.getMessage(errorCode, this.getParams()); 77 | } 78 | if (StringUtils.isEmpty(msg) && cause != null) { 79 | msg = cause.getMessage(); 80 | } 81 | if (StringUtils.isEmpty(msg)) { 82 | //3、异常码为空 & msg为空,提示系统异常 83 | msg = this.getMessage("000000", this.getParams()); 84 | } 85 | return msg; 86 | } 87 | 88 | /** 89 | * 获取错误码描述 90 | * 91 | * @param code 92 | * @return 93 | */ 94 | private String getMessage(String code, Object[] params) { 95 | try { 96 | return messageSource.getMessage(code, params, Locale.CHINA); 97 | } catch (NoSuchMessageException e) { 98 | return code; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/util/SequenceFactory.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.util; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.data.redis.core.RedisTemplate; 5 | import org.springframework.data.redis.support.atomic.RedisAtomicLong; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.Date; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | @Service 12 | public class SequenceFactory { 13 | @Autowired 14 | RedisTemplate redisTemplate; 15 | 16 | /** 17 | * @param key 18 | * @param value 19 | * @param expireTime 20 | * @Title: set 21 | * @Description: set cache. 22 | */ 23 | public void set(String key, int value, Date expireTime) { 24 | RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory()); 25 | counter.set(value); 26 | counter.expireAt(expireTime); 27 | } 28 | 29 | /** 30 | * @param key 31 | * @param value 32 | * @param timeout 33 | * @param unit 34 | * @Title: set 35 | * @Description: set cache. 36 | */ 37 | public void set(String key, int value, long timeout, TimeUnit unit) { 38 | RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory()); 39 | counter.set(value); 40 | counter.expire(timeout, unit); 41 | } 42 | 43 | /** 44 | * @param key 45 | * @return 46 | * @Title: generate 47 | * @Description: Atomically increments by one the current value. 48 | */ 49 | public long generate(String key) { 50 | RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory()); 51 | return counter.incrementAndGet(); 52 | } 53 | 54 | /** 55 | * @param key 56 | * @return 57 | * @Title: generate 58 | * @Description: Atomically increments by one the current value. 59 | */ 60 | public long generate(String key, Date expireTime) { 61 | RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory()); 62 | counter.expireAt(expireTime); 63 | return counter.incrementAndGet(); 64 | } 65 | 66 | /** 67 | * @param key 68 | * @param increment 69 | * @return 70 | * @Title: generate 71 | * @Description: Atomically adds the given value to the current value. 72 | */ 73 | public long generate(String key, int increment) { 74 | RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory()); 75 | return counter.addAndGet(increment); 76 | } 77 | 78 | /** 79 | * @param key 80 | * @param increment 81 | * @param expireTime 82 | * @return 83 | * @Title: generate 84 | * @Description: Atomically adds the given value to the current value. 85 | */ 86 | public long generate(String key, int increment, Date expireTime) { 87 | RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory()); 88 | counter.expireAt(expireTime); 89 | return counter.addAndGet(increment); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.service; 2 | 3 | import club.newtech.qbike.order.domain.core.root.Order; 4 | import club.newtech.qbike.order.domain.core.vo.CustomerVo; 5 | import club.newtech.qbike.order.domain.core.vo.DriverVo; 6 | import club.newtech.qbike.order.domain.core.vo.FlowState; 7 | import club.newtech.qbike.order.domain.core.vo.IntentionVo; 8 | import club.newtech.qbike.order.domain.exception.OrderRuntimeException; 9 | import club.newtech.qbike.order.domain.repository.OrderRepository; 10 | import club.newtech.qbike.order.infrastructure.UserRibbonHystrixApi; 11 | import club.newtech.qbike.order.util.SequenceFactory; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.data.redis.core.RedisTemplate; 16 | import org.springframework.stereotype.Service; 17 | import org.springframework.transaction.annotation.Transactional; 18 | 19 | import java.util.Date; 20 | 21 | @Service 22 | public class OrderService { 23 | public static final String UUID_KEY = "UUID_KEY"; 24 | private static final Logger LOGGER = LoggerFactory.getLogger(OrderService.class); 25 | @Autowired 26 | UserRibbonHystrixApi userService; 27 | @Autowired 28 | private RedisTemplate redisTemplate; 29 | @Autowired 30 | private OrderRepository orderRepository; 31 | @Autowired 32 | private SequenceFactory sequenceFactory; 33 | 34 | 35 | private String generateOrderId() { 36 | return "T" + String.format("%010d", sequenceFactory.generate("order")); 37 | } 38 | 39 | 40 | @Transactional 41 | public Order createOrder(IntentionVo intention) { 42 | //在调用远程user服务获取用户信息的时候,必须有熔断,否则在事务中很危险 43 | Order order = new Order(); 44 | order.setOid(generateOrderId()); 45 | CustomerVo customerVo = userService.findCustomerById(intention.getCustomerId()); 46 | DriverVo driverVo = userService.findDriverById(intention.getDriverId()); 47 | order.setCustomer(customerVo); 48 | order.setDriver(driverVo); 49 | order.setOrderStatus(FlowState.WAITING_ABOARD.toValue()); 50 | order.setOpened(new Date()); 51 | order.setStartLong(intention.getStartLong()); 52 | order.setStartLat(intention.getStartLat()); 53 | order.setDestLong(intention.getDestLong()); 54 | order.setDestLat(intention.getDestLat()); 55 | order.setIntentionId(String.valueOf(intention.getIntentionId())); 56 | orderRepository.save(order); 57 | return order; 58 | } 59 | 60 | @Transactional 61 | public void aboard(Order order) { 62 | order.setOrderStatus(FlowState.WAITING_ARRIVE.toValue()); 63 | order.setOpened(new Date()); 64 | orderRepository.save(order); 65 | } 66 | 67 | @Transactional 68 | public void cancel(Order order) { 69 | Date currentTime = new Date(); 70 | if ((currentTime.getTime() - order.getOpened().getTime()) <= 3 * 60 * 1000L) { 71 | order.setOrderStatus(FlowState.CANCELED.toValue()); 72 | orderRepository.save(order); 73 | } else { 74 | throw new OrderRuntimeException("040001"); 75 | } 76 | } 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/core/root/Intention.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.core.root; 2 | 3 | import club.newtech.qbike.intention.domain.core.vo.Candidate; 4 | import club.newtech.qbike.intention.domain.core.vo.Customer; 5 | import club.newtech.qbike.intention.domain.core.vo.DriverVo; 6 | import club.newtech.qbike.intention.domain.core.vo.Status; 7 | import lombok.Data; 8 | import lombok.ToString; 9 | import lombok.experimental.Accessors; 10 | 11 | import javax.persistence.*; 12 | import java.util.ArrayList; 13 | import java.util.Date; 14 | import java.util.List; 15 | 16 | import static javax.persistence.EnumType.STRING; 17 | 18 | @Data 19 | @ToString 20 | @Accessors(fluent = false, chain = true) 21 | @Entity 22 | @Table(name = "t_intention") 23 | public class Intention { 24 | @Id 25 | @GeneratedValue(strategy = GenerationType.IDENTITY) 26 | private int mid; 27 | private Double startLongitude; 28 | private Double startLatitude; 29 | private Double destLongitude; 30 | private Double destLatitude; 31 | @Embedded 32 | private Customer customer; 33 | @Enumerated(value = STRING) 34 | @Column(length = 32, nullable = false) 35 | private Status status; 36 | @Embedded 37 | private DriverVo selectedDriver; 38 | @Temporal(TemporalType.TIMESTAMP) 39 | private Date updated = new Date(); 40 | 41 | @OneToMany(mappedBy = "intention", fetch = FetchType.EAGER, cascade = CascadeType.ALL) 42 | private List candidates = new ArrayList<>(); 43 | 44 | public boolean canMatchDriver() { 45 | if (status.equals(Status.Inited)) { 46 | return true; 47 | } else { 48 | return false; 49 | } 50 | } 51 | 52 | private boolean canConfirm() { 53 | if (status.equals(Status.UnConfirmed)) { 54 | return true; 55 | } else { 56 | return false; 57 | } 58 | } 59 | 60 | public boolean waitingConfirm() { 61 | if (canMatchDriver()) { 62 | this.status = Status.UnConfirmed; 63 | this.updated = new Date(); 64 | return true; 65 | } else { 66 | return false; 67 | } 68 | } 69 | 70 | public boolean fail() { 71 | if (this.status == Status.Inited) { 72 | this.status = Status.Failed; 73 | this.updated = new Date(); 74 | return true; 75 | } else { 76 | return false; 77 | } 78 | } 79 | 80 | /** 81 | * 抢单,先应答的司机抢单成功 82 | * 该方法线程不安全,请使用锁保证没有并发 83 | * 84 | * @param driverVo 85 | * @return 0 成功, -1 状态不对, -2 不是候选司机,-3 已被抢走 86 | */ 87 | public int confirmIntention(DriverVo driverVo) { 88 | //判断状态 89 | if (!canConfirm()) { 90 | //状态不对 91 | return -1; 92 | } 93 | //判断是否是候选司机,不能随便什么司机都来抢单 94 | if (candidates.stream().map(Candidate::getDriverId).noneMatch(id -> id == driverVo.getId())) { 95 | return -2; 96 | } 97 | //将候选司机加入到列表中 98 | if (this.selectedDriver == null) { 99 | this.selectedDriver = driverVo; 100 | this.status = Status.Confirmed; 101 | this.updated = new Date(); 102 | return 0; 103 | } else { 104 | return -3; 105 | } 106 | 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | club.newtech.qbike 7 | demo 8 | 0.0.1-SNAPSHOT 9 | pom 10 | 11 | 12 | microservice-discovery-eureka 13 | uc 14 | intention 15 | position 16 | api-gateway 17 | order 18 | testclient 19 | 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-parent 26 | 2.0.1.RELEASE 27 | 28 | 29 | 30 | UTF-8 31 | 1.8 32 | club.newtech 33 | 2.9.2 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | org.springframework.cloud 42 | spring-cloud-dependencies 43 | Finchley.RELEASE 44 | pom 45 | import 46 | 47 | 48 | io.springfox 49 | springfox-swagger2 50 | ${springfox.version} 51 | 52 | 53 | io.springfox 54 | springfox-swagger-ui 55 | ${springfox.version} 56 | 57 | 58 | io.springfox 59 | springfox-data-rest 60 | ${springfox.version} 61 | 62 | 63 | io.swagger 64 | swagger-annotations 65 | 1.5.10 66 | 67 | 68 | org.springframework.statemachine 69 | spring-statemachine-core 70 | 1.2.3.RELEASE 71 | 72 | 73 | org.apache.commons 74 | commons-lang3 75 | 3.5 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | org.springframework.boot 84 | spring-boot-maven-plugin 85 | 86 | 87 | 88 | 89 | 90 | 91 | com.spotify 92 | docker-maven-plugin 93 | 0.4.12 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /order/src/main/java/club/newtech/qbike/order/domain/service/OrderStateMachineBuilder.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.order.domain.service; 2 | 3 | import club.newtech.qbike.order.domain.core.root.Order; 4 | import club.newtech.qbike.order.domain.core.vo.Events; 5 | import club.newtech.qbike.order.domain.core.vo.FlowState; 6 | import club.newtech.qbike.order.domain.core.vo.StateRequest; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.BeanFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.statemachine.StateMachine; 12 | import org.springframework.statemachine.action.Action; 13 | import org.springframework.statemachine.config.StateMachineBuilder; 14 | import org.springframework.stereotype.Component; 15 | 16 | import java.util.EnumSet; 17 | 18 | @Component 19 | public class OrderStateMachineBuilder { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(OrderStateMachineBuilder.class); 22 | @Autowired 23 | OrderService orderService; 24 | @Autowired 25 | ErrorAction errorAction; 26 | 27 | public StateMachine build(FlowState initState, BeanFactory beanFactory) throws Exception { 28 | StateMachineBuilder.Builder builder = StateMachineBuilder.builder(); 29 | builder.configureConfiguration() 30 | .withConfiguration() 31 | .machineId("orderFSM") 32 | .beanFactory(beanFactory); 33 | builder.configureStates() 34 | .withStates() 35 | .initial(initState) 36 | .end(FlowState.CLOSED) 37 | .end(FlowState.CANCELED) 38 | .states(EnumSet.allOf(FlowState.class)); 39 | builder.configureTransitions() 40 | .withExternal() 41 | .source(FlowState.WAITING_ABOARD).target(FlowState.WAITING_ARRIVE) 42 | .event(Events.ABOARD) 43 | .action(aboard(), errorAction) 44 | //司机接单后,三分钟内可以无条件取消 45 | .and() 46 | .withExternal() 47 | .source(FlowState.WAITING_ABOARD).target(FlowState.CANCELED) 48 | .event(Events.CANCEL) 49 | .action(cancel(), errorAction) 50 | // 上车后,等待结束 51 | .and() 52 | .withExternal() 53 | .source(FlowState.WAITING_ARRIVE).target(FlowState.UNPAY) 54 | .event(Events.ARRIVE) 55 | .and() 56 | .withExternal() 57 | .source(FlowState.UNPAY).target(FlowState.PAYING) 58 | .event(Events.PAY) 59 | .and() 60 | .withExternal() 61 | .source(FlowState.PAYING).target(FlowState.WAITING_COMMENT) 62 | .event(Events.PAY_CALLBACK) 63 | .and() 64 | .withExternal() 65 | .source(FlowState.WAITING_COMMENT).target(FlowState.CLOSED) 66 | .event(Events.COMMENT); 67 | 68 | 69 | return builder.build(); 70 | } 71 | 72 | public Action aboard() { 73 | return stateContext -> { 74 | Order order = stateContext.getExtendedState().get(StateRequest.class, Order.class); 75 | String uuId = stateContext.getExtendedState().get(OrderService.UUID_KEY, String.class); 76 | order.setOrderStatus(FlowState.WAITING_ARRIVE.toValue()); 77 | orderService.aboard(order); 78 | }; 79 | } 80 | 81 | public Action cancel() { 82 | return stateContext -> { 83 | Order order = stateContext.getExtendedState().get(StateRequest.class, Order.class); 84 | String uuId = stateContext.getExtendedState().get(OrderService.UUID_KEY, String.class); 85 | order.setOrderStatus(FlowState.CANCELED.toValue()); 86 | orderService.cancel(order); 87 | }; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /intention/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | demo 7 | club.newtech.qbike 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | intention 13 | 14 | 15 | 16 | org.springframework.cloud 17 | spring-cloud-starter-netflix-eureka-client 18 | 19 | 20 | org.springframework.cloud 21 | spring-cloud-starter-zipkin 22 | 23 | 24 | org.springframework.amqp 25 | spring-rabbit 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-data-jpa 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-data-redis 35 | 36 | 37 | 38 | org.springframework.cloud 39 | spring-cloud-starter-netflix-ribbon 40 | 41 | 42 | 43 | org.springframework.cloud 44 | spring-cloud-starter-netflix-hystrix 45 | 46 | 47 | mysql 48 | mysql-connector-java 49 | runtime 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-test 54 | test 55 | 56 | 57 | org.projectlombok 58 | lombok 59 | 1.16.16 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-data-rest 64 | 65 | 66 | 67 | 68 | 69 | 70 | com.spotify 71 | docker-maven-plugin 72 | 73 | 74 | build-image 75 | package 76 | 77 | build 78 | 79 | 80 | 81 | 82 | ${docker.image.prefix}/${project.artifactId} 83 | java 84 | ["java", "-jar", "/${project.build.finalName}.jar", "--spring.profiles.active=docker"]] 85 | 86 | 87 | / 88 | ${project.build.directory} 89 | ${project.build.finalName}.jar 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /uc/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | uc 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | uc 11 | Demo project for Spring Boot 12 | 13 | 14 | club.newtech.qbike 15 | demo 16 | 0.0.1-SNAPSHOT 17 | 18 | 19 | 20 | UTF-8 21 | UTF-8 22 | 1.8 23 | Edgware.SR3 24 | 25 | 26 | 27 | 28 | 29 | org.springframework.cloud 30 | spring-cloud-starter-netflix-eureka-client 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-data-jpa 36 | 37 | 38 | 39 | mysql 40 | mysql-connector-java 41 | runtime 42 | 43 | 44 | 45 | org.springframework.cloud 46 | spring-cloud-starter-netflix-eureka-client 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-test 51 | test 52 | 53 | 54 | org.projectlombok 55 | lombok 56 | 1.16.16 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-data-rest 61 | 62 | 63 | 64 | org.springframework.cloud 65 | spring-cloud-starter-zipkin 66 | 67 | 68 | org.springframework.amqp 69 | spring-rabbit 70 | 71 | 72 | 73 | 74 | 75 | com.spotify 76 | docker-maven-plugin 77 | 78 | 79 | build-image 80 | package 81 | 82 | build 83 | 84 | 85 | 86 | 87 | ${docker.image.prefix}/${project.artifactId} 88 | java 89 | ["java", "-jar", "/${project.build.finalName}.jar", "--spring.profiles.active=docker"]] 90 | 91 | 92 | / 93 | ${project.build.directory} 94 | ${project.build.finalName}.jar 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /position/src/main/java/club/newtech/qbike/trip/domain/service/PositionService.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.trip.domain.service; 2 | 3 | import club.newtech.qbike.trip.domain.core.Status; 4 | import club.newtech.qbike.trip.domain.core.root.DriverStatus; 5 | import club.newtech.qbike.trip.domain.core.vo.Driver; 6 | import club.newtech.qbike.trip.domain.core.vo.Position; 7 | import club.newtech.qbike.trip.domain.repository.DriverStatusRepo; 8 | import club.newtech.qbike.trip.domain.repository.PositionRepository; 9 | import club.newtech.qbike.trip.infrastructure.UserRibbonHystrixApi; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.data.geo.Circle; 14 | import org.springframework.data.geo.Distance; 15 | import org.springframework.data.geo.GeoResults; 16 | import org.springframework.data.geo.Point; 17 | import org.springframework.data.redis.connection.RedisGeoCommands; 18 | import org.springframework.data.redis.core.RedisTemplate; 19 | import org.springframework.stereotype.Service; 20 | import org.springframework.transaction.annotation.Transactional; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Collection; 24 | import java.util.Date; 25 | import java.util.List; 26 | 27 | import static java.util.stream.Collectors.toList; 28 | 29 | @Service 30 | @Transactional 31 | public class PositionService { 32 | private static final Logger LOGGER = LoggerFactory.getLogger(PositionService.class); 33 | @Autowired 34 | DriverStatusRepo driverStatusRepo; 35 | @Autowired 36 | UserRibbonHystrixApi userService; 37 | @Autowired 38 | RedisTemplate redisTemplate; 39 | @Autowired 40 | PositionRepository positionRepository; 41 | 42 | public void updatePosition(Integer driverId, Double longitude, Double latitude) { 43 | //先记录轨迹 44 | Date current = new Date(); 45 | Position position = new Position(); 46 | position.setDriverId(String.valueOf(driverId)); 47 | position.setPositionLongitude(longitude); 48 | position.setPositionLatitude(latitude); 49 | //TODO 目前没有上传上下线状态 50 | position.setStatus(Status.ONLINE); 51 | position.setUploadTime(current); 52 | positionRepository.save(position); 53 | //更新状态表 54 | DriverStatus driverStatus = driverStatusRepo.findByDriver_Id(driverId); 55 | if (driverStatus != null) { 56 | driverStatus.setCurrentLongitude(longitude); 57 | driverStatus.setCurrentLatitude(latitude); 58 | driverStatus.setUpdateTime(current); 59 | driverStatusRepo.save(driverStatus); 60 | } else { 61 | Driver driver = userService.findById(driverId); 62 | driverStatus = new DriverStatus(); 63 | driverStatus.setDriver(driver); 64 | driverStatus.setCurrentLongitude(longitude); 65 | driverStatus.setCurrentLatitude(latitude); 66 | driverStatus.setUpdateTime(current); 67 | driverStatus.setStatus(Status.ONLINE); 68 | driverStatus.setDId(driverId); 69 | driverStatusRepo.save(driverStatus); 70 | } 71 | //更新Redis中的坐标数据,GeoHash 72 | redisTemplate.opsForGeo().geoAdd("Drivers", new Point(longitude, latitude), String.valueOf(driverId)); 73 | LOGGER.info("position update " + driverStatus); 74 | 75 | } 76 | 77 | public Collection matchDriver(double longitude, double latitude) { 78 | Circle circle = new Circle(new Point(longitude, latitude), // 79 | new Distance(500, RedisGeoCommands.DistanceUnit.METERS)); 80 | GeoResults> result = 81 | redisTemplate.opsForGeo().geoRadius("Drivers", circle); 82 | if (result.getContent().size() == 0) { 83 | LOGGER.info("没找到匹配司机"); 84 | return new ArrayList<>(); 85 | 86 | } else { 87 | List drivers = result.getContent() 88 | .stream() 89 | .map(x -> x.getContent().getName()) 90 | .collect(toList()); 91 | LOGGER.info("获取附近司机为{}", drivers); 92 | return drivers.stream().map(Integer::parseInt) 93 | .map(id -> driverStatusRepo.findByDriver_Id(id)).collect(toList()); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /position/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | demo 7 | club.newtech.qbike 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | position 13 | 14 | 15 | org.springframework.cloud 16 | spring-cloud-starter-config 17 | 18 | 19 | 20 | org.springframework.cloud 21 | spring-cloud-starter-netflix-eureka-client 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-data-jpa 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-data-redis 32 | 33 | 34 | 35 | org.springframework.cloud 36 | spring-cloud-starter-netflix-ribbon 37 | 38 | 39 | 40 | org.springframework.cloud 41 | spring-cloud-starter-netflix-hystrix 42 | 43 | 44 | mysql 45 | mysql-connector-java 46 | runtime 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-test 55 | test 56 | 57 | 58 | org.projectlombok 59 | lombok 60 | 1.16.16 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-starter-data-rest 65 | 66 | 67 | 68 | org.springframework.cloud 69 | spring-cloud-starter-zipkin 70 | 71 | 72 | org.springframework.amqp 73 | spring-rabbit 74 | 75 | 76 | 77 | 78 | 79 | com.spotify 80 | docker-maven-plugin 81 | 82 | 83 | build-image 84 | package 85 | 86 | build 87 | 88 | 89 | 90 | 91 | ${docker.image.prefix}/${project.artifactId} 92 | java 93 | ["java", "-jar", "/${project.build.finalName}.jar", "--spring.profiles.active=docker"]] 94 | 95 | 96 | / 97 | ${project.build.directory} 98 | ${project.build.finalName}.jar 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /order/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | demo 7 | club.newtech.qbike 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | order 13 | 14 | 15 | org.springframework.cloud 16 | spring-cloud-starter-config 17 | 18 | 19 | 20 | org.springframework.cloud 21 | spring-cloud-starter-netflix-eureka-client 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-data-jpa 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-redis 31 | 32 | 33 | 34 | org.springframework.cloud 35 | spring-cloud-starter-netflix-ribbon 36 | 37 | 38 | 39 | org.springframework.cloud 40 | spring-cloud-starter-netflix-hystrix 41 | 42 | 43 | mysql 44 | mysql-connector-java 45 | runtime 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-test 50 | test 51 | 52 | 53 | org.projectlombok 54 | lombok 55 | 1.16.16 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-data-rest 60 | 61 | 62 | org.springframework.statemachine 63 | spring-statemachine-core 64 | 65 | 66 | org.apache.commons 67 | commons-lang3 68 | 69 | 70 | org.springframework.cloud 71 | spring-cloud-starter-zipkin 72 | 73 | 74 | org.springframework.amqp 75 | spring-rabbit 76 | 77 | 78 | 79 | 80 | 81 | com.spotify 82 | docker-maven-plugin 83 | 84 | 85 | build-image 86 | package 87 | 88 | build 89 | 90 | 91 | 92 | 93 | ${docker.image.prefix}/${project.artifactId} 94 | java 95 | ["java", "-jar", "/${project.build.finalName}.jar", "--spring.profiles.active=docker"]] 96 | 97 | 98 | / 99 | ${project.build.directory} 100 | ${project.build.finalName}.jar 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /wait-for-it.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Use this script to test if a given TCP host/port are available 3 | 4 | cmdname=$(basename $0) 5 | 6 | echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } 7 | 8 | usage() 9 | { 10 | cat << USAGE >&2 11 | Usage: 12 | $cmdname host:port [-s] [-t timeout] [-- command args] 13 | -h HOST | --host=HOST Host or IP under test 14 | -p PORT | --port=PORT TCP port under test 15 | Alternatively, you specify the host and port as host:port 16 | -s | --strict Only execute subcommand if the test succeeds 17 | -q | --quiet Don't output any status messages 18 | -t TIMEOUT | --timeout=TIMEOUT 19 | Timeout in seconds, zero for no timeout 20 | -- COMMAND ARGS Execute command with args after the test finishes 21 | USAGE 22 | exit 1 23 | } 24 | 25 | wait_for() 26 | { 27 | if [[ $TIMEOUT -gt 0 ]]; then 28 | echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT" 29 | else 30 | echoerr "$cmdname: waiting for $HOST:$PORT without a timeout" 31 | fi 32 | start_ts=$(date +%s) 33 | while : 34 | do 35 | if [[ $ISBUSY -eq 1 ]]; then 36 | nc -z $HOST $PORT 37 | result=$? 38 | else 39 | (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1 40 | result=$? 41 | fi 42 | if [[ $result -eq 0 ]]; then 43 | end_ts=$(date +%s) 44 | echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds" 45 | break 46 | fi 47 | sleep 1 48 | done 49 | return $result 50 | } 51 | 52 | wait_for_wrapper() 53 | { 54 | # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 55 | if [[ $QUIET -eq 1 ]]; then 56 | timeout $BUSYTIMEFLAG $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & 57 | else 58 | timeout $BUSYTIMEFLAG $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & 59 | fi 60 | PID=$! 61 | trap "kill -INT -$PID" INT 62 | wait $PID 63 | RESULT=$? 64 | if [[ $RESULT -ne 0 ]]; then 65 | echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT" 66 | fi 67 | return $RESULT 68 | } 69 | 70 | # process arguments 71 | while [[ $# -gt 0 ]] 72 | do 73 | case "$1" in 74 | *:* ) 75 | hostport=(${1//:/ }) 76 | HOST=${hostport[0]} 77 | PORT=${hostport[1]} 78 | shift 1 79 | ;; 80 | --child) 81 | CHILD=1 82 | shift 1 83 | ;; 84 | -q | --quiet) 85 | QUIET=1 86 | shift 1 87 | ;; 88 | -s | --strict) 89 | STRICT=1 90 | shift 1 91 | ;; 92 | -h) 93 | HOST="$2" 94 | if [[ $HOST == "" ]]; then break; fi 95 | shift 2 96 | ;; 97 | --host=*) 98 | HOST="${1#*=}" 99 | shift 1 100 | ;; 101 | -p) 102 | PORT="$2" 103 | if [[ $PORT == "" ]]; then break; fi 104 | shift 2 105 | ;; 106 | --port=*) 107 | PORT="${1#*=}" 108 | shift 1 109 | ;; 110 | -t) 111 | TIMEOUT="$2" 112 | if [[ $TIMEOUT == "" ]]; then break; fi 113 | shift 2 114 | ;; 115 | --timeout=*) 116 | TIMEOUT="${1#*=}" 117 | shift 1 118 | ;; 119 | --) 120 | shift 121 | CLI=("$@") 122 | break 123 | ;; 124 | --help) 125 | usage 126 | ;; 127 | *) 128 | echoerr "Unknown argument: $1" 129 | usage 130 | ;; 131 | esac 132 | done 133 | 134 | if [[ "$HOST" == "" || "$PORT" == "" ]]; then 135 | echoerr "Error: you need to provide a host and port to test." 136 | usage 137 | fi 138 | 139 | TIMEOUT=${TIMEOUT:-15} 140 | STRICT=${STRICT:-0} 141 | CHILD=${CHILD:-0} 142 | QUIET=${QUIET:-0} 143 | 144 | # check to see if timeout is from busybox? 145 | # check to see if timeout is from busybox? 146 | TIMEOUT_PATH=$(realpath $(which timeout)) 147 | if [[ $TIMEOUT_PATH =~ "busybox" ]]; then 148 | ISBUSY=1 149 | BUSYTIMEFLAG="-t" 150 | else 151 | ISBUSY=0 152 | BUSYTIMEFLAG="" 153 | fi 154 | 155 | if [[ $CHILD -gt 0 ]]; then 156 | wait_for 157 | RESULT=$? 158 | exit $RESULT 159 | else 160 | if [[ $TIMEOUT -gt 0 ]]; then 161 | wait_for_wrapper 162 | RESULT=$? 163 | else 164 | wait_for 165 | RESULT=$? 166 | fi 167 | fi 168 | 169 | if [[ $CLI != "" ]]; then 170 | if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then 171 | echoerr "$cmdname: strict mode, refusing to execute subprocess" 172 | exit $RESULT 173 | fi 174 | exec "${CLI[@]}" 175 | else 176 | exit $RESULT 177 | fi 178 | -------------------------------------------------------------------------------- /uc/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/service/RedisLockService.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.service; 2 | 3 | 4 | import club.newtech.qbike.intention.domain.core.vo.Lock; 5 | import club.newtech.qbike.intention.domain.exception.LockExistsException; 6 | import club.newtech.qbike.intention.domain.exception.LockNotHeldException; 7 | import lombok.Setter; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.dao.DataAccessException; 10 | import org.springframework.data.redis.core.RedisOperations; 11 | import org.springframework.data.redis.core.SessionCallback; 12 | import org.springframework.data.redis.core.StringRedisTemplate; 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.util.*; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | import static java.util.stream.Collectors.toMap; 19 | 20 | @Service 21 | public class RedisLockService implements LockService { 22 | private static final String DEFAULT_LOCK_PREFIX = "qbike.lock."; 23 | @Autowired 24 | StringRedisTemplate redisOperations; 25 | private String prefix = DEFAULT_LOCK_PREFIX; 26 | @Setter 27 | private long expiry = 30000; // 30 seconds 28 | 29 | /** 30 | * The prefix for all lock keys. 31 | * 32 | * @param prefix the prefix to set for all lock keys 33 | */ 34 | public void setPrefix(String prefix) { 35 | if (!prefix.endsWith(".")) { 36 | prefix = prefix + "."; 37 | } 38 | this.prefix = prefix; 39 | } 40 | 41 | @Override 42 | public Iterable findAll() { 43 | Set keys = redisOperations.keys(prefix + "*"); 44 | Set locks = new LinkedHashSet(); 45 | for (String key : keys) { 46 | Date expires = new Date(System.currentTimeMillis() + redisOperations.getExpire(key, TimeUnit.MILLISECONDS)); 47 | locks.add(new Lock(nameForKey(key), redisOperations.opsForValue().get(key), expires)); 48 | } 49 | return locks; 50 | } 51 | 52 | @Override 53 | public Lock create(String name) { 54 | String stored = getValue(name); 55 | if (stored != null) { 56 | throw new LockExistsException(); 57 | } 58 | String value = UUID.randomUUID().toString(); 59 | String key = keyForName(name); 60 | if (!redisOperations.opsForValue().setIfAbsent(key, value)) { 61 | throw new LockExistsException(); 62 | } 63 | redisOperations.expire(key, expiry, TimeUnit.MILLISECONDS); 64 | Date expires = new Date(System.currentTimeMillis() + expiry); 65 | return new Lock(name, value, expires); 66 | } 67 | 68 | @Override 69 | public Set createMultiLock(Set names) { 70 | Set locks = redisOperations.execute(new SessionCallback>() { 71 | @Override 72 | public Set execute(RedisOperations operations) throws DataAccessException { 73 | Set locks = new HashSet<>(); 74 | Map mset = 75 | names.stream() 76 | .collect(toMap(name -> keyForName(name), key -> UUID.randomUUID().toString())); 77 | //一次性获取所有的锁,如果失败直接退出,说明已经有被占用了 78 | boolean b = operations.opsForValue() 79 | .multiSetIfAbsent(mset); 80 | //占领锁成功了,再给锁添加过期时间 TODO 这里有可能失败,如果用Watch也许会更有效果一些 81 | if (b) { 82 | operations.multi(); 83 | for (String name : names) { 84 | String key = keyForName(name); 85 | operations.expire(key, expiry, TimeUnit.MILLISECONDS); 86 | Date expires = new Date(System.currentTimeMillis() + expiry); 87 | Lock lock = new Lock(name, mset.get(key), expires); 88 | locks.add(lock); 89 | } 90 | operations.exec(); 91 | return locks; 92 | } else { 93 | throw new LockExistsException(); 94 | } 95 | } 96 | 97 | }); 98 | return locks; 99 | } 100 | 101 | @Override 102 | public boolean release(String name, String value) { 103 | String stored = getValue(name); 104 | if (stored != null && value.equals(stored)) { 105 | String key = keyForName(name); 106 | redisOperations.delete(key); 107 | return true; 108 | } 109 | if (stored != null) { 110 | throw new LockNotHeldException(); 111 | } 112 | return false; 113 | } 114 | 115 | @Override 116 | public Lock refresh(String name, String value) { 117 | String key = keyForName(name); 118 | String stored = getValue(name); 119 | if (stored != null && value.equals(stored)) { 120 | Date expires = new Date(System.currentTimeMillis() + expiry); 121 | redisOperations.expire(key, expiry, TimeUnit.MILLISECONDS); 122 | return new Lock(name, value, expires); 123 | } 124 | throw new LockNotHeldException(); 125 | } 126 | 127 | private String getValue(String name) { 128 | String key = keyForName(name); 129 | String stored = redisOperations.opsForValue().get(key); 130 | return stored; 131 | } 132 | 133 | private String nameForKey(String key) { 134 | if (!key.startsWith(prefix)) { 135 | throw new IllegalStateException("Key (" + key + ") does not start with prefix (" + prefix + ")"); 136 | } 137 | return key.substring(prefix.length()); 138 | } 139 | 140 | private String keyForName(String name) { 141 | return prefix + name; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /uc/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /intention/src/main/java/club/newtech/qbike/intention/domain/service/IntentionService.java: -------------------------------------------------------------------------------- 1 | package club.newtech.qbike.intention.domain.service; 2 | 3 | import club.newtech.qbike.intention.controller.bean.IntentionVo; 4 | import club.newtech.qbike.intention.domain.core.root.Intention; 5 | import club.newtech.qbike.intention.domain.core.vo.*; 6 | import club.newtech.qbike.intention.domain.exception.LockException; 7 | import club.newtech.qbike.intention.domain.repository.CandidateRepository; 8 | import club.newtech.qbike.intention.domain.repository.IntentionRepository; 9 | import club.newtech.qbike.intention.infrastructure.PositionApi; 10 | import club.newtech.qbike.intention.infrastructure.UserRibbonHystrixApi; 11 | import com.fasterxml.jackson.core.JsonProcessingException; 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.data.redis.core.RedisTemplate; 18 | import org.springframework.scheduling.annotation.Async; 19 | import org.springframework.stereotype.Service; 20 | import org.springframework.transaction.annotation.Transactional; 21 | 22 | import java.util.Collection; 23 | import java.util.Date; 24 | import java.util.List; 25 | import java.util.concurrent.DelayQueue; 26 | import java.util.concurrent.TimeUnit; 27 | 28 | import static java.util.stream.Collectors.toList; 29 | 30 | @Service 31 | public class IntentionService { 32 | private static final Logger LOGGER = LoggerFactory.getLogger(IntentionService.class); 33 | @Autowired 34 | IntentionRepository intentionRepository; 35 | @Autowired 36 | UserRibbonHystrixApi userApi; 37 | @Autowired 38 | RedisTemplate redisTemplate; 39 | @Autowired 40 | PositionApi positionApi; 41 | @Autowired 42 | CandidateRepository candidateRepository; 43 | @Autowired 44 | LockService lockService; 45 | @Autowired 46 | ObjectMapper objectMapper; 47 | @Autowired 48 | RabbitTemplate rabbitTemplate; 49 | 50 | 51 | private DelayQueue intentions = new DelayQueue<>(); 52 | 53 | private static Candidate fromDriverStatus(DriverStatusVo driverStatusVo, Intention intention) { 54 | Candidate candidate = new Candidate(); 55 | candidate.setDriverId(driverStatusVo.getDId()); 56 | candidate.setCreated(new Date()); 57 | candidate.setDriverMobile(driverStatusVo.getDriver().getMobile()); 58 | candidate.setDriverName(driverStatusVo.getDriver().getUserName()); 59 | candidate.setLongitude(driverStatusVo.getCurrentLongitude()); 60 | candidate.setLatitude(driverStatusVo.getCurrentLatitude()); 61 | candidate.setIntention(intention); 62 | return candidate; 63 | } 64 | 65 | @Transactional 66 | public void placeIntention(int userId, Double startLongitude, Double startLatitude, 67 | Double destLongitude, Double destLatitude) { 68 | Customer customer = userApi.findById(userId); 69 | Intention intention = new Intention() 70 | .setStartLongitude(startLongitude) 71 | .setStartLatitude(startLatitude) 72 | .setDestLongitude(destLongitude) 73 | .setDestLatitude(destLatitude) 74 | .setCustomer(customer) 75 | .setStatus(Status.Inited); 76 | intentionRepository.save(intention); 77 | IntentionTask task = new IntentionTask(intention.getMid(), 2L, TimeUnit.SECONDS, 0); 78 | 79 | intentions.put(task); 80 | } 81 | 82 | @Transactional 83 | public void sendNotification(Collection result, Intention intention) { 84 | result.stream() 85 | .map(vo -> fromDriverStatus(vo, intention)) 86 | .forEach(candidate -> candidateRepository.save(candidate)); 87 | intention.waitingConfirm(); 88 | intentionRepository.save(intention); 89 | } 90 | 91 | @Transactional 92 | public boolean confirmIntention(int driverId, int intentionId) { 93 | String lockName = "intention" + intentionId; 94 | Lock lock = null; 95 | try { 96 | lock = lockService.create(lockName); 97 | Intention intention = intentionRepository.findById(intentionId).orElse(null); 98 | DriverVo driverVo = userApi.findDriverById(driverId); 99 | int ret = intention.confirmIntention(driverVo); 100 | LOGGER.info("{}司机抢单{}结果为{}", driverId, intentionId, ret); 101 | if (ret == 0) { 102 | intentionRepository.save(intention); 103 | IntentionVo intentionVo = new IntentionVo().setIntentionId(intention.getMid()) 104 | .setCustomerId(intention.getCustomer().getCustomerId()) 105 | .setDestLat(intention.getDestLatitude()) 106 | .setDestLong(intention.getDestLongitude()) 107 | .setStartLong(intention.getStartLongitude()) 108 | .setStartLat(intention.getStartLatitude()) 109 | .setDriverId(intention.getSelectedDriver().getId()); 110 | try { 111 | rabbitTemplate.convertAndSend("intention", objectMapper.writeValueAsString(intentionVo)); 112 | } catch (JsonProcessingException e) { 113 | LOGGER.error("convert message fail" + intentionVo, e); 114 | } 115 | return true; 116 | } else { 117 | return false; 118 | } 119 | } catch (LockException e) { 120 | LOGGER.error("try lock error ", e); 121 | return false; 122 | } finally { 123 | if (lock != null) { 124 | lockService.release(lockName, lock.getValue()); 125 | } 126 | } 127 | } 128 | 129 | @Transactional 130 | public void matchFail(Intention intention) { 131 | intention.fail(); 132 | intentionRepository.save(intention); 133 | } 134 | 135 | @Async 136 | public void handleTask() { 137 | LOGGER.info("start handling intention task loop"); 138 | for (; ; ) { 139 | try { 140 | IntentionTask task = intentions.take(); 141 | if (task != null) { 142 | LOGGER.info("got a task {}", task.getIntenionId()); 143 | Intention intention = intentionRepository.findById(task.getIntenionId()).orElse(null); 144 | if (intention.canMatchDriver()) { 145 | //调用position服务匹配司机 146 | Collection result = positionApi.match(intention.getStartLongitude(), intention.getStartLatitude()); 147 | if (result.size() > 0) { 148 | List names = result.stream().map(s -> s.getDriver().getUserName()).collect(toList()); 149 | LOGGER.info("匹配司机{},将向他们发送抢单请求", names); 150 | sendNotification(result, intention); 151 | } else { 152 | LOGGER.info("没有匹配到司机,放入队列继续等待"); 153 | retryMatch(task, intention); 154 | } 155 | } else { 156 | // 忽略 157 | } 158 | 159 | } 160 | } catch (Exception e) { 161 | LOGGER.error("error happened", e); 162 | } 163 | } 164 | } 165 | 166 | private boolean retryMatch(IntentionTask task, Intention intention) { 167 | int times = task.getRepeatTimes() + 1; 168 | LOGGER.info("task 已经被循环{} 次", times); 169 | if (times > 5) { 170 | //超过n次匹配无法匹配,就失败 171 | matchFail(intention); 172 | return true; 173 | } 174 | IntentionTask newTask = new IntentionTask(intention.getMid(), 2 * times, TimeUnit.SECONDS, times); 175 | this.intentions.put(newTask); 176 | return false; 177 | } 178 | } 179 | --------------------------------------------------------------------------------