Рубрики
Без рубрики

Простая реализация электронной коммерции с помощью Spring

Узнайте, как создать простое приложение для электронной коммерции, используя Spring Boot и угловые фреймворки.

Автор оригинала: baeldung.

1. Обзор нашего приложения для электронной коммерции

В этом уроке мы реализуем простое приложение для электронной коммерции. Мы разработаем API с использованием Spring Boot и клиентское приложение, которое будет использовать API с помощью Angular .

В принципе, пользователь сможет добавлять/удалять товары из списка товаров в/из корзины покупок и размещать заказ.

2. Внутренняя часть

Для разработки API мы будем использовать последнюю версию Spring Boot. Мы также используем базу данных JPA и H2 для обеспечения персистентности.

Чтобы узнать больше о Spring Boot, вы можете ознакомиться с нашей серией статей Spring Boot | и если вы хотите ознакомиться с созданием REST API, пожалуйста, ознакомьтесь с другой серией .

2.1. Зависимости Maven

Давайте подготовим наш проект и импортируем необходимые зависимости в ваш pom.xml .

Нам понадобятся некоторые зависимости от ядра Spring Boot :


    org.springframework.boot
    spring-boot-starter-data-jpa
    2.2.2.RELEASE


    org.springframework.boot
    spring-boot-starter-web
    2.2.2.RELEASE

Затем база данных H2 :


    com.h2database
    h2
    1.4.197
    runtime

И, наконец, – библиотека Джексона :


    com.fasterxml.jackson.datatype
    jackson-datatype-jsr310
    2.9.6

Мы использовали Spring Initializr для быстрой настройки проекта с необходимыми зависимостями.

2.2. Настройка базы данных

Хотя мы могли бы использовать базу данных H2 в памяти из коробки с Spring Boot, мы все равно внесем некоторые коррективы, прежде чем начнем разрабатывать наш API.

Мы включим консоль H2 в нашем application.properties файле , чтобы мы могли проверить состояние нашей базы данных и посмотреть, все ли идет так, как мы ожидали .

Кроме того, может быть полезно регистрировать SQL-запросы в консоли во время разработки:

spring.datasource.name=ecommercedb
spring.jpa.show-sql=true

#H2 settings
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

После добавления этих настроек мы сможем получить доступ к базе данных по адресу http://localhost:8080/h2-console использование jdbc:h2:mem:ecommercedb в качестве URL-адреса JDBC и пользователя удовлетворение без пароля.

2.3. Структура Проекта

Проект будет организован в несколько стандартных пакетов, с приложением Angular, помещенным в папку frontend:

├───pom.xml            
├───src
    ├───main
    │   ├───frontend
    │   ├───java
    │   │   └───com
    │   │       └───baeldung
    │   │           └───ecommerce
    │   │               │   EcommerceApplication.java
    │   │               ├───controller 
    │   │               ├───dto  
    │   │               ├───exception
    │   │               ├───model
    │   │               ├───repository
    │   │               └───service
    │   │                       
    │   └───resources
    │       │   application.properties
    │       ├───static
    │       └───templates
    └───test
        └───java
            └───com
                └───baeldung
                    └───ecommerce
                            EcommerceApplicationIntegrationTest.java

Следует отметить , что все интерфейсы в пакете репозитория просты и расширяют CrudRepository Spring Data, поэтому мы не будем отображать их здесь.

2.4. Обработка исключений

Нам понадобится обработчик исключений для нашего API, чтобы правильно обрабатывать возможные исключения.

Вы можете найти более подробную информацию по этой теме в наших статьях Обработка ошибок для REST с помощью Spring и Пользовательская обработка сообщений об ошибках для REST API статьи .

Здесь мы сосредоточимся на ConstraintViolationException и нашем пользовательском ResourceNotFoundException :

@RestControllerAdvice
public class ApiExceptionHandler {

    @SuppressWarnings("rawtypes")
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity handle(ConstraintViolationException e) {
        ErrorResponse errors = new ErrorResponse();
        for (ConstraintViolation violation : e.getConstraintViolations()) {
            ErrorItem error = new ErrorItem();
            error.setCode(violation.getMessageTemplate());
            error.setMessage(violation.getMessage());
            errors.addError(error);
        }
        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }

    @SuppressWarnings("rawtypes")
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity handle(ResourceNotFoundException e) {
        ErrorItem error = new ErrorItem();
        error.setMessage(e.getMessage());

        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }
}

2.5. Продукты

Если вам нужно больше знаний о персистентности весной, есть много полезных статей в серии Spring Persistence .

Наше приложение будет поддерживать только чтение продуктов из базы данных , поэтому сначала нам нужно добавить некоторые из них.

Давайте создадим простой Продукт класс:

@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull(message = "Product name is required.")
    @Basic(optional = false)
    private String name;

    private Double price;

    private String pictureUrl;

    // all arguments contructor 
    // standard getters and setters
}

Хотя у пользователя не будет возможности добавлять продукты через приложение, мы поддержим сохранение продукта в базе данных, чтобы предварительно заполнить список продуктов.

Простой услуги будет достаточно для наших нужд:

@Service
@Transactional
public class ProductServiceImpl implements ProductService {

    // productRepository constructor injection

    @Override
    public Iterable getAllProducts() {
        return productRepository.findAll();
    }

    @Override
    public Product getProduct(long id) {
        return productRepository
          .findById(id)
          .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
    }

    @Override
    public Product save(Product product) {
        return productRepository.save(product);
    }
}

Простой контроллер будет обрабатывать запросы на получение списка продуктов:

@RestController
@RequestMapping("/api/products")
public class ProductController {

    // productService constructor injection

    @GetMapping(value = { "", "/" })
    public @NotNull Iterable getProducts() {
        return productService.getAllProducts();
    }
}

Все, что нам сейчас нужно для того, чтобы предоставить список продуктов пользователю, – это фактически поместить некоторые продукты в базу данных. Поэтому мы будем использовать класс CommandLineRunner для создания Bean в нашем основном классе приложений.

Таким образом, мы будем вставлять продукты в базу данных во время запуска приложения:

@Bean
CommandLineRunner runner(ProductService productService) {
    return args -> {
        productService.save(...);
        // more products
}

Если мы сейчас запустим ваше приложение, мы сможем получить список продуктов через http://localhost:8080/api/products . Кроме того, если мы перейдем к http://localhost:8080/h2-console и войдите в систему, мы увидим, что есть таблица с именем PRODUCT с продуктами, которые мы только что добавили.

2.6. Заказы

На стороне API нам нужно включить запросы POST для сохранения заказов, которые будет делать конечный пользователь.

Давайте сначала создадим модель:

@Entity
@Table(name = "orders")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JsonFormat(pattern = "dd/MM/yyyy")
    private LocalDate dateCreated;

    private String status;

    @JsonManagedReference
    @OneToMany(mappedBy = "pk.order")
    @Valid
    private List orderProducts = new ArrayList<>();

    @Transient
    public Double getTotalOrderPrice() {
        double sum = 0D;
        List orderProducts = getOrderProducts();
        for (OrderProduct op : orderProducts) {
            sum += op.getTotalPrice();
        }
        return sum;
    }

    @Transient
    public int getNumberOfProducts() {
        return this.orderProducts.size();
    }

    // standard getters and setters
}

Здесь мы должны отметить несколько вещей. Конечно, одна из самых примечательных вещей-это не забудьте изменить имя нашей таблицы по умолчанию . Поскольку мы назвали класс Order , по умолчанию должна быть создана таблица с именем ORDER . Но поскольку это зарезервированное SQL-слово, мы добавили @Table(name) , чтобы избежать конфликтов.

Кроме того, у нас есть два метода @Transient , которые вернут общую сумму для этого заказа и количество продуктов в нем . Оба представляют собой вычисленные данные, поэтому нет необходимости хранить их в базе данных.

Наконец, у нас есть отношение @OneToMany , представляющее детали заказа . Для этого нам нужен другой класс сущностей:

@Entity
public class OrderProduct {

    @EmbeddedId
    @JsonIgnore
    private OrderProductPK pk;

    @Column(nullable = false)
	private Integer quantity;

    // default constructor

    public OrderProduct(Order order, Product product, Integer quantity) {
        pk = new OrderProductPK();
        pk.setOrder(order);
        pk.setProduct(product);
        this.quantity = quantity;
    }

    @Transient
    public Product getProduct() {
        return this.pk.getProduct();
    }

    @Transient
    public Double getTotalPrice() {
        return getProduct().getPrice() * getQuantity();
    }

    // standard getters and setters

    // hashcode() and equals() methods
}

У нас есть составной первичный ключ здесь :

@Embeddable
public class OrderProductPK implements Serializable {

    @JsonBackReference
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Order order;

    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id")
    private Product product;

    // standard getters and setters

    // hashcode() and equals() methods
}

Эти классы не слишком сложны, но мы должны отметить, что в Order Product class мы помещаем @JsonIgnore на первичный ключ. Это потому, что мы не хотим сериализовать Order часть первичного ключа, так как это было бы избыточно.

Нам нужно только, чтобы Product отображался пользователю, поэтому у нас есть метод/| getProduct () .

Далее нам нужна простая реализация сервиса:

@Service
@Transactional
public class OrderServiceImpl implements OrderService {

    // orderRepository constructor injection

    @Override
    public Iterable getAllOrders() {
        return this.orderRepository.findAll();
    }
	
    @Override
    public Order create(Order order) {
        order.setDateCreated(LocalDate.now());
        return this.orderRepository.save(order);
    }

    @Override
    public void update(Order order) {
        this.orderRepository.save(order);
    }
}

И контроллер, сопоставленный с /api/orders для обработки Order запросов.

Наиболее важным является метод create ():

@PostMapping
public ResponseEntity create(@RequestBody OrderForm form) {
    List formDtos = form.getProductOrders();
    validateProductsExistence(formDtos);
    // create order logic
    // populate order with products

    order.setOrderProducts(orderProducts);
    this.orderService.update(order);

    String uri = ServletUriComponentsBuilder
      .fromCurrentServletMapping()
      .path("/orders/{id}")
      .buildAndExpand(order.getId())
      .toString();
    HttpHeaders headers = new HttpHeaders();
    headers.add("Location", uri);

    return new ResponseEntity<>(order, headers, HttpStatus.CREATED);
}

Прежде всего, мы принимаем список продуктов с их соответствующими количествами . После этого мы проверяем, существуют ли все продукты в базе данных, а затем создаем и сохраняем новый заказ . Мы сохраняем ссылку на вновь созданный объект, чтобы добавить к нему детали заказа.

Наконец, мы создаем заголовок “Местоположение” .

Подробная реализация находится в репозитории – ссылка на нее приведена в конце этой статьи.

3. Передний конец

Теперь, когда у нас есть приложение Spring Boot, пришло время переместить угловую часть проекта . Для этого нам сначала нужно установить Node.js с NPM и, после этого, Angular CLI , интерфейс командной строки для Angular.

Это действительно легко установить оба из них, как мы могли видеть в официальной документации.

3.1. Настройка Углового проекта

Как мы уже упоминали, мы будем использовать Angular CLI для создания нашего приложения. Чтобы все было просто и все было в одном месте, мы будем хранить наше угловое приложение в папке /src/main/frontend .

Чтобы создать его, нам нужно открыть терминал (или командную строку) в папке /src/main и запустить:

ng new frontend

Это создаст все файлы и папки, необходимые для нашего углового приложения. В файле package.json мы можем проверить , какие версии наших зависимостей установлены. Этот учебник основан на Angular v6.0.3, но более старые версии должны выполнять эту работу, по крайней мере версии 4.3 и более новые ( HttpClient , который мы используем здесь, был представлен в Angular 4.3).

Мы должны отметить, что мы будем запускать все наши команды из папки /frontend , если не указано иное.

Этой настройки достаточно, чтобы запустить приложение Angular, выполнив команду ng server . По умолчанию он работает на http://localhost:4200 и если мы сейчас отправимся туда, мы увидим, что загружено базовое угловое приложение.

3.2. Добавление Начальной загрузки

Прежде чем мы приступим к созданию наших собственных компонентов, давайте сначала добавим Bootstrap в наш проект, чтобы мы могли сделать ваши страницы красивыми.

Для этого нам нужно всего несколько вещей. Во-первых, нам нужно выполнить команду для его установки :

npm install --save bootstrap

и затем сказать Угловому, чтобы он действительно использовал его . Для этого нам нужно открыть файл src/main/frontend/angular.json и добавить node_modules/bootstrap/dist/css/bootstrap.min.css в разделе “стили” свойство. И это все.

3.3. Компоненты и модели

Прежде чем мы начнем создавать компоненты для нашего приложения, давайте сначала проверим, как на самом деле будет выглядеть наше приложение:

Теперь мы создадим базовый компонент с именем электронная коммерция :

ng g c ecommerce

Это создаст наш компонент внутри папки /frontend/src/app . Чтобы загрузить его при запуске приложения, мы включим его | в app.component.html :

Затем мы создадим другие компоненты внутри этого базового компонента:

ng g c /ecommerce/products
ng g c /ecommerce/orders
ng g c /ecommerce/shopping-cart

Конечно, мы могли бы создать все эти папки и файлы вручную, если бы захотели, но в этом случае нам нужно было бы не забыть зарегистрировать эти компоненты в нашем AppModule .

Нам также понадобятся некоторые модели, чтобы легко манипулировать нашими данными:

export class Product {
    id: number;
    name: string;
    price: number;
    pictureUrl: string;

    // all arguments constructor
}
export class ProductOrder {
    product: Product;
    quantity: number;

    // all arguments constructor
}
export class ProductOrders {
    productOrders: ProductOrder[] = [];
}

Последняя упомянутая модель соответствует нашей Форме заказа на бэкэнде.

3.4. Базовый компонент

В верхней части нашего компонента e commerce мы поместим навигационную панель с домашней ссылкой справа:

Мы также загрузим другие компоненты отсюда:

Мы должны иметь в виду, что для того, чтобы увидеть содержимое наших компонентов, поскольку мы используем класс navbar , нам нужно добавить некоторые CSS в app.component.css :

.container {
    padding-top: 65px;
}

Давайте проверим файл .ts , прежде чем комментировать наиболее важные части:

@Component({
    selector: 'app-ecommerce',
    templateUrl: './ecommerce.component.html',
    styleUrls: ['./ecommerce.component.css']
})
export class EcommerceComponent implements OnInit {
    private collapsed = true;
    orderFinished = false;

    @ViewChild('productsC')
    productsC: ProductsComponent;

    @ViewChild('shoppingCartC')
    shoppingCartC: ShoppingCartComponent;

    @ViewChild('ordersC')
    ordersC: OrdersComponent;

    toggleCollapsed(): void {
        this.collapsed = !this.collapsed;
    }

    finishOrder(orderFinished: boolean) {
        this.orderFinished = orderFinished;
    }

    reset() {
        this.orderFinished = false;
        this.productsC.reset();
        this.shoppingCartC.reset();
        this.ordersC.paid = false;
    }
}

Как мы видим, нажатие на ссылку Home приведет к сбросу дочерних компонентов. Нам нужно получить доступ к методам и полю внутри дочерних компонентов от родителя, поэтому мы сохраняем ссылки на дочерние компоненты и используем их внутри метода reset () .

3.5. Услуга

Для того , чтобы компоненты братьев и сестер могли взаимодействовать друг с другом | и получать| отправлять данные из/в наш API/|, нам нужно будет создать службу:

@Injectable()
export class EcommerceService {
    private productsUrl = "/api/products";
    private ordersUrl = "/api/orders";

    private productOrder: ProductOrder;
    private orders: ProductOrders = new ProductOrders();

    private productOrderSubject = new Subject();
    private ordersSubject = new Subject();
    private totalSubject = new Subject();

    private total: number;

    ProductOrderChanged = this.productOrderSubject.asObservable();
    OrdersChanged = this.ordersSubject.asObservable();
    TotalChanged = this.totalSubject.asObservable();

    constructor(private http: HttpClient) {
    }

    getAllProducts() {
        return this.http.get(this.productsUrl);
    }

    saveOrder(order: ProductOrders) {
        return this.http.post(this.ordersUrl, order);
    }

    // getters and setters for shared fields
}

Как мы могли заметить, здесь есть относительно простые вещи. Мы делаем запросы GET и POST для связи с API. Кроме того, мы делаем данные, которые нам нужно разделить между компонентами, наблюдаемыми, чтобы мы могли подписаться на них позже.

Тем не менее, мы должны указать на одну вещь, касающуюся связи с API. Если мы запустим приложение сейчас, мы получим 404 и не получим никаких данных. Причина этого в том, что, поскольку мы используем относительные URL-адреса, Angular по умолчанию попытается вызвать http://localhost:4200/api/products и наше серверное приложение работает на localhost:8080 .

Конечно , мы могли бы жестко закодировать URL-адреса в localhost:8080 , но это не то, что мы хотим делать. Вместо этого при работе с разными доменами мы должны создать файл с именем proxy-conf.json в вашей папке /frontend |:

{
    "/api": {
        "target": "http://localhost:8080",
        "secure": false
    }
}

А затем нам нужно открыть package.json и изменить scripts.start property , чтобы соответствовать:

"scripts": {
    ...
    "start": "ng serve --proxy-config proxy-conf.json",
    ...
  }

И теперь мы просто должны иметь в виду, чтобы запустить приложение с npm start вместо ng serve .

3.6. Продукты

В нашем ProductsComponent мы введем сервис , который мы создали ранее, загрузим список продуктов из API и преобразуем его в список ProductOrders , так как мы хотим добавить поле количества к каждому продукту:

export class ProductsComponent implements OnInit {
    productOrders: ProductOrder[] = [];
    products: Product[] = [];
    selectedProductOrder: ProductOrder;
    private shoppingCartOrders: ProductOrders;
    sub: Subscription;
    productSelected: boolean = false;

    constructor(private ecommerceService: EcommerceService) {}

    ngOnInit() {
        this.productOrders = [];
        this.loadProducts();
        this.loadOrders();
    }

    loadProducts() {
        this.ecommerceService.getAllProducts()
            .subscribe(
                (products: any[]) => {
                    this.products = products;
                    this.products.forEach(product => {
                        this.productOrders.push(new ProductOrder(product, 0));
                    })
                },
                (error) => console.log(error)
            );
    }

    loadOrders() {
        this.sub = this.ecommerceService.OrdersChanged.subscribe(() => {
            this.shoppingCartOrders = this.ecommerceService.ProductOrders;
        });
    }
}

Нам также нужна возможность добавить товар в корзину или удалить его из нее:

addToCart(order: ProductOrder) {
    this.ecommerceService.SelectedProductOrder = order;
    this.selectedProductOrder = this.ecommerceService.SelectedProductOrder;
    this.productSelected = true;
}

removeFromCart(productOrder: ProductOrder) {
    let index = this.getProductIndex(productOrder.product);
    if (index > -1) {
        this.shoppingCartOrders.productOrders.splice(
            this.getProductIndex(productOrder.product), 1);
    }
    this.ecommerceService.ProductOrders = this.shoppingCartOrders;
    this.shoppingCartOrders = this.ecommerceService.ProductOrders;
    this.productSelected = false;
}

Наконец, мы создадим метод reset (), который мы упоминали в разделе 3.4:

reset() {
    this.productOrders = [];
    this.loadProducts();
    this.ecommerceService.ProductOrders.productOrders = [];
    this.loadOrders();
    this.productSelected = false;
}

Мы пройдемся по списку продуктов в нашем HTML-файле и покажем его пользователю:

{{order.product.name}}

${{order.product.price}}

Мы также добавим простой класс в соответствующий файл CSS, чтобы все было хорошо:

.padding-0 {
    padding-right: 0;
    padding-left: 1;
}

3.7. Корзина для покупок

В компоненте Корзина мы также введем сервис. Мы будем использовать его, чтобы подписаться на изменения в компоненте ProductsComponent (чтобы заметить, когда продукт выбран для размещения в корзине покупок), а затем обновить содержимое корзины и соответственно пересчитать общую стоимость:

export class ShoppingCartComponent implements OnInit, OnDestroy {
    orderFinished: boolean;
    orders: ProductOrders;
    total: number;
    sub: Subscription;

    @Output() onOrderFinished: EventEmitter;

    constructor(private ecommerceService: EcommerceService) {
        this.total = 0;
        this.orderFinished = false;
        this.onOrderFinished = new EventEmitter();
    }

    ngOnInit() {
        this.orders = new ProductOrders();
        this.loadCart();
        this.loadTotal();
    }

    loadTotal() {
        this.sub = this.ecommerceService.OrdersChanged.subscribe(() => {
            this.total = this.calculateTotal(this.orders.productOrders);
        });
    }

    loadCart() {
        this.sub = this.ecommerceService.ProductOrderChanged.subscribe(() => {
            let productOrder = this.ecommerceService.SelectedProductOrder;
            if (productOrder) {
                this.orders.productOrders.push(new ProductOrder(
                    productOrder.product, productOrder.quantity));
            }
            this.ecommerceService.ProductOrders = this.orders;
            this.orders = this.ecommerceService.ProductOrders;
            this.total = this.calculateTotal(this.orders.productOrders);
        });
    }

    ngOnDestroy() {
        this.sub.unsubscribe();
    }
}

Мы отправляем событие в родительский компонент отсюда, когда заказ завершен, и нам нужно перейти к оформлению заказа. Здесь также есть метод reset ():

finishOrder() {
    this.orderFinished = true;
    this.ecommerceService.Total = this.total;
    this.onOrderFinished.emit(this.orderFinished);
}

reset() {
    this.orderFinished = false;
    this.orders = new ProductOrders();
    this.orders.productOrders = []
    this.loadTotal();
    this.total = 0;
}

HTML файл прост:

Shopping Cart
Total: ${{total}}

Items bought:
  • {{ order.product.name }} - {{ order.quantity}} pcs.

3.8. Заказы

Мы постараемся сделать все как можно проще и в компоненте Orders имитируем оплату, установив свойство true и сохранив заказ в базе данных. Мы можем проверить, что заказы сохраняются либо через h2-консоль , либо нажав http://localhost:8080/api/orders .

Нам также нужна Служба электронной коммерции здесь, чтобы получить список продуктов из корзины покупок и общую сумму нашего заказа:

export class OrdersComponent implements OnInit {
    orders: ProductOrders;
    total: number;
    paid: boolean;
    sub: Subscription;

    constructor(private ecommerceService: EcommerceService) {
        this.orders = this.ecommerceService.ProductOrders;
    }

    ngOnInit() {
        this.paid = false;
        this.sub = this.ecommerceService.OrdersChanged.subscribe(() => {
            this.orders = this.ecommerceService.ProductOrders;
        });
        this.loadTotal();
    }

    pay() {
        this.paid = true;
        this.ecommerceService.saveOrder(this.orders).subscribe();
    }
}

И, наконец, нам нужно отобразить информацию для пользователя:

ORDER

  • {{ order.product.name }} - ${{ order.product.price }} x {{ order.quantity}} pcs.

Total amount: ${{ total }}

4. Объединение Spring Boot и угловых приложений

Мы закончили разработку обоих наших приложений, и, вероятно, проще разработать его отдельно, как мы это сделали. Но в производстве было бы гораздо удобнее иметь одно приложение, поэтому давайте теперь объединим эти два.

То, что мы хотим сделать здесь, – это создать приложение Angular, которое вызывает Webpack, чтобы собрать все ресурсы и поместить их в каталог /resources/static приложения Spring Boot . Таким образом, мы можем просто запустить приложение Spring Boot, протестировать наше приложение, упаковать все это и развернуть как одно приложение.

Чтобы сделать это возможным, нам нужно открыть ‘ package.json ‘ снова добавить несколько новых сценариев после scripts . build :

"postbuild": "npm run deploy",
"predeploy": "rimraf ../resources/static/ && mkdirp ../resources/static",
"deploy": "copyfiles -f dist/** ../resources/static",

Мы используем некоторые пакеты, которые у нас не установлены, поэтому давайте установим их:

npm install --save-dev rimraf
npm install --save-dev mkdirp
npm install --save-dev copyfiles

Команда rimraf посмотрит каталог и создаст новый каталог (фактически очистив его), в то время как copyfiles копирует файлы из папки дистрибутива (где Angular помещает все) в нашу статическую папку.

Теперь нам просто нужно запустить npm выполнить команду build , и это должно выполнить все эти команды, и конечным результатом будет наше упакованное приложение в статической папке .

Затем мы запускаем наше приложение Spring Boot на порту 8080, получаем доступ к нему там и используем приложение Angular.

5. Заключение

В этой статье мы создали простое приложение для электронной коммерции. Мы создали API на бэкэнде с помощью Spring Boot, а затем использовали его в нашем интерфейсном приложении, созданном в Angular. Мы продемонстрировали, как сделать необходимые нам компоненты, заставить их взаимодействовать друг с другом и получать/отправлять данные из/в API.

Наконец, мы показали, как объединить оба приложения в одно упакованное веб-приложение внутри статической папки.

Как всегда, полный проект, который мы описали в этой статье, можно найти в проекте GitHub.