Сссылка для перехода на страницу товара

Хочу сделать переход на страницу товара
Вот код контроллера

@GetMapping(value = "/case")
public String shopPage(Model model, @RequestParam("id") Long id, Principal principal) {
    try {
        String username = principal.getName();
        if (username != null) {
            User user = userService.findByLogin(username);
            if (!user.isFieldsNotNull()) {
                return "redirect:/" + username;
            }
            model.addAttribute("sign", "выйти");
        }
    } catch (Exception e) {
        model.addAttribute("sign", "войти");
    }
    ShopCaseDto shop = shopMapper.toShopCaseDto(shopService.findById(id));
    List<ShopCaseItemDto> shopItems = shop.getItems();
    ShopCaseItemDto ratingItem = shopService.getTheMostRatingItem(shop.getItems());
    List<ImageDto> images = ratingItem.getImages();
    ImageDto logo = shop.getLogo();

    model.addAttribute("shop", shop);
    model.addAttribute("images", shopService
            .convertListImages(images));
    model.addAttribute("shopItems", shopItems);
    model.addAttribute("logo", shopService.convertImage(logo));
    model.addAttribute("ratingItem", ratingItem);

    return "shop_page";
}

@GetMapping(value = "/item")
public String itemPage(Model model, @RequestParam("shop") Long shopId, @RequestParam("item") Long itemId, Principal principal) {
    try {
        String username = principal.getName();
        if (username != null) {
            User user = userService.findByLogin(username);
            if (!user.isFieldsNotNull()) {
                return "redirect:/" + username;
            }
            model.addAttribute("sign", "выйти");
        }
    } catch (Exception e) {
        model.addAttribute("sign", "войти");
    }
    Item item = shopService.getItemById(shopService.findById(shopId).getItems(), itemId);


    List<ImageDto> images = imageMapper.toListImageDto(item.getImages());

    model.addAttribute("item", itemMapper.itemToItemDTO(item));
    model.addAttribute("image", shopService.convertListImages(images));
    return "shop_item_page";
}

Вот код карточки, где я хочу сделать кнопку с переходом на страницу

<div class="row" th:fragment="list" xmlns:th="http://www.thymeleaf.org">
<div th:each="shop_item,stat: ${shopItems}" class="col-sm-4">
    <div class="card mb-3">

        <div class="card-body">

            <h5 class="card-title" th:text="${shop_item.name}">Product Name</h5>
            <p class="card-text" th:text="${shop_item.description}">Material</p>
            <p class="card-text" th:text="${shop_item.rating}">Price</p>
            <a th:href="@{/shop/{id}/item/{itemid} (id=${shop.id} ,itemid=${shop_item.id})}" class="btn btn-primary">Открыть</a>
            </div>
    </div>
</div>

В итоге я открываю эту ссылку

http://localhost:8888/shop/2/item/3

Высвечивает такую ошибку

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.

Mon Nov 22 20:58:24 MSK 2021
There was an unexpected error (type=Not Found, status=404).
No message available

Объясните пожалуйста, что я делаю неправильно


Ответы (1 шт):

Автор решения: Михаил Ребров

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

@RequestParam

@RequestParam - это аннотация, которая перехватывает параметр запроса.
Именно ПАРАМЕТР ЗАПРОСА и ничто иное.
Это то, что передается после вопросительного знака в строке запроса.

http://localhost:8888/home?param1=test&param2=value

В данном случае мы бы могли перехватить эти данные с помощью следующих аннотаций и параметров

@RequestParam("param1") String firstParameter, @RequestParam("param2") String secondParameter

И это значит, что это не то что нам нужно, потому что с помощью данных аннотаций мы не можем извлечь данные из пути... (того что указано до знака вопроса)
Для таких вещей используются переменные пути и мы о них поговорим чуть позже.

@GetMapping

Аннотация @GetMapping позволяет связать запросы, соответствующие указанному шаблону, с методом, который будет их обрабатывать.

В данном случае аннотация @GetMapping(value = "/item") позволяет нам обрабатывать запросы вида http://localhost:8888/item или в лучшем случае http://localhost:8888/shop/item (при условии, что вы еще указали mapping над самим контроллером)

И это опять не то, что нам нужно.

Исходя из всех исходных, запрос, который обрабатывает метод itemPage() вашего контроллера должен был бы выглядеть вот так:

http://localhost:8888/item?shop=123&item=456

или

http://localhost:8888/shop/item?shop=123&item=456

(при условии проставленной аннотации с путем над контроллером)

Вы же ожидаете:

http://localhost:8888/shop/123/item/456

Теперь давайте разберемся как этого достигнуть

@PathVariable

Как я уже упоминал, Вам нужно было использовать переменные пути.
Аннотация @PathVariable позволяет связать параметр метода контроллера с частью шаблона, которому должен удовлетворять запрос.

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

Например:

@GetMapping(value="/schools/{school}/courses/{course}/materials")

В аннотации указаны два плейсхолдера {school} и {course}, которые мы впоследствии можем перехватить с помощью аннотаций, следующим образом:

@PathVariable("school") Long schoolId, @PathVariable("course") Long courseId,

В вашем случае эта пара будет выглядеть следующим образом:
Шаблон:

@GetMapping(value = "/shop/{shop}/item/{item}")

или

@GetMapping(value = "/{shop}/item/{item}")

если над контроллером указан шаблон ("/shop")
и параметры:

@PathVariable("shop") Long shopId, @PathVariable("item") Long itemId,

Итог:

// метод shopPage() в данном листинге не указываю, т.к. он без изменений

// далее
@GetMapping(value = "/shop/{shop}/item/{item}")
public String itemPage(Model model, @PathVariable("shop") Long shopId, @PathVariable("item") Long itemId, Principal principal) {
    try {
        String username = principal.getName();
        if (username != null) {
            User user = userService.findByLogin(username);
            if (!user.isFieldsNotNull()) {
                return "redirect:/" + username;
            }
            model.addAttribute("sign", "выйти");
        }
    } catch (Exception e) {
        model.addAttribute("sign", "войти");
    }
    Item item = shopService.getItemById(shopService.findById(shopId).getItems(), itemId);

    List<ImageDto> images = imageMapper.toListImageDto(item.getImages());

    model.addAttribute("item", itemMapper.itemToItemDTO(item));
    model.addAttribute("image", shopService.convertListImages(images));
    return "shop_item_page";
}
→ Ссылка