概要
フロントエンド:react nextjs axios
バックエンド:SpringBoot restapi
でB2CのECサイト作ってます。
SpringBoot側で作ったRestApiをフロントエンド側で動かすことができるか検証します。
ドメインは買い物かごです。
開発環境
開発環境
OS:windows10
バックエンド側:
IDE:IntelliJ Community
spring-boot-starter-parent 2.75
java : 11
データベース
mysql:8.0.29
クライアントソフト: MySQL Workbench
フロントエンド:
IDE:VScode
├── @types/node@18.11.15
├── @types/react-dom@18.0.9
├── @types/react@18.0.26
├── axios@1.2.1
├── eslint-config-next@13.0.6
├── eslint@8.29.0
├── next-auth@4.18.7
├── next@13.0.6
├── react-bootstrap@2.7.0
├── react-dom@18.2.0
├── react@18.2.0
├── styled-components@5.3.6
├── styled-jsx@5.1.1
└── typescript@4.9.4
ユースケース
ユースケースは買い物カゴです。
具体的には
1.顧客の買い物かごの情報を取得する(Read)
2.顧客の買い物かごに商品を追加する (Create)
3.顧客の買い物かごに入ってる商品の数量を変更する(Update)
4.顧客の買い物かごに入っている商品を一つ取り除く(Delete)
5.顧客の買い物カゴに入っている商品を全て乗り除く(Delete)
顧客の認可はSpringBoot側から発行されたjwt方式のトークンを使っています。
ソースコード
Service,repository,Entity分野はここでは書きません。
詳細はgithubのリポジトリで確認お願いします。
バックエンド側
package com.example.restapi.implement.cartItem;
import com.example.restapi.domain.cartItem.CartItemService;
import com.example.restapi.domain.customer.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
@RestController
//@CrossOrigin(origins = "http://127.0.0.1:3000")
@CrossOrigin(origins = "http://localhost:3000")
public class CartItemRestController {
@Autowired
CartItemService cartItemService;
@Autowired
CustomerService customerService;
@PostMapping("/api/cart/add")
public String addProductCart( @RequestParam("productId") Integer productId,
@RequestParam("quantity") Integer quantity,
HttpServletRequest request
){
String message;
try {
//Get customerId from Jwt
Integer customerId = customerService.getIdfromJwtToken(request);
cartItemService.addProduct(productId, quantity, customerId);
// String email = jwtTokenProvider.getCustomernameFromJWT(token);
message = "customerId:"+ customerId + "added" + quantity +"quantity productId" + productId;
return message;
}catch(Exception e){
message = "Exception error";
return message;
}
}
@GetMapping("/api/cart/all")
public List<CartItemDto> listCartItems(HttpServletRequest request){
List<CartItemDto> cartItemDtoList = new ArrayList<>();
//Get customerId from Jwt
Integer customerId = customerService.getIdfromJwtToken(request);
return cartItemService.listCartItems(customerId);
}
@PutMapping("/api/cart/update")
public String updateQuantity(@RequestParam("productId") Integer productId,
@RequestParam("quantity") Integer quantity,
HttpServletRequest request){
//Get customerId from Jwt
Integer customerId = customerService.getIdfromJwtToken(request);
cartItemService.updateQuantity(productId,quantity,customerId);
return "updated";
}
@DeleteMapping("/api/cart/remove")
public String removeProduct( @RequestParam("productId") Integer productId,
HttpServletRequest request){
//Get customerId from Jwt
Integer customerId = customerService.getIdfromJwtToken(request);
cartItemService.removeProduct(productId,customerId);
return "ShoppingCart is removed in customerId" + customerId ;
}
@DeleteMapping("/api/cart/delete")
public String deleteByCustomer(HttpServletRequest request){
//Get customerId from Jwt
Integer customerId = customerService.getIdfromJwtToken(request);
cartItemService.deleteByCustomer(customerId);
return customerId + "is all deleted in all ShoppinCart";
}
}
フロントエンド側
import { useState, useEffect, useCallback } from "react";
import { CartItem } from "../../types/cartItem";
import { Customer } from "../../types/customer";
import * as customerService from "../../service/customerService";
import * as cartItemService from "../../service/cartItemService";
/**
*
* @remarks SpringBootのRestAPIを nextjs側が動かせるかの検証用ページ
*
* @authoer RYA234
*
* @returns
*/
export default function CartItemServiceCheck(this: any) {
const [currentCartItems, setCurrentCartItems] = useState<CartItem[]>([]);
const [jwtAccessToken, setJwtAccessToken] = useState<string | null>();
// ログイン処理は初回時のみ実行 CartItemドメインの検証用ページなので
useEffect(() => {
loginAndGetCartItem(), [];
});
// ログイン処理後カートアイテムの情報取得更新する
const loginAndGetCartItem = async () => {
await login();
console.log("await between login and getCartItems");
await getCartItems();
};
// ログインする用のAPIを叩く
const login = () => {
const requestBody: Customer = {
email: "aaaba@gmail.com",
password: "test",
append: function (arg0: string, inputEmail: string): unknown {
throw new Error("Function not implemented.");
},
};
customerService
.signIn(requestBody)
.then((response) => {
localStorage.setItem("accessToken", response.data.accessToken);
setJwtAccessToken(response.data.accessToken);
console.log("accesssToken is set");
})
.catch((error) => {
localStorage.setItem("accessToken", " error");
setJwtAccessToken(localStorage.getItem("accessToken"));
console.log("accesssToken is not set");
});
};
// カートアイテムの情報を取得する
const getCartItems = () => {
cartItemService
.getCartItems(jwtAccessToken as string)
.then((response) => {
// 再レンダリングを防ぐために、レスポンスのデータが変わっている場合のみsetCurrentCartItemsを実行する。
// if ((response.data as CartItem[]).toString() != currentCartItems.toString()) // ToString()だとエラーになるのでJSON.stringifyに修正
if (
JSON.stringify(response.data as CartItem[]) !=
JSON.stringify(currentCartItems)
) {
setCurrentCartItems(response.data);
console.log("cartItems are set");
console.log(response.data);
}
})
.catch((error) => {
console.log("cartItems are not set");
});
};
let inputProductId: number = 0;
let inputQuantity: number = 0;
// カートアイテムを追加する
const addCartItem = async () => {
const inputCartItem: CartItem = {
productId: inputProductId as number,
quantity: inputQuantity as number,
id: 0,
customerId: 0,
};
await cartItemService
.addProductCart(inputCartItem, jwtAccessToken as string)
.then((response) => {
console.log(response.data);
console.log("cartItems is added");
})
.catch((error) => {
console.log("cartItems are not set");
});
};
// データベースに追加後、データベースから再取得する
// ボタンに割り当てる
const addCartItemHandler = async () => {
console.log("jwt" + jwtAccessToken);
await addCartItem();
console.log("between addCartItem and getCartItems");
await getCartItems();
};
// カートアイテムの数量を更新後、データベースから再取得して画面更新する。
// ボタンに割り当てる
const updateQuantitAndGetCartItemsyHandler = async () => {
await updateQuantity();
console.log("between updateQuantity and getCartItems");
await getCartItems();
};
let updateInputquantiy: number = 0;
let updateInputProductId: number = 0;
// カートアイテムの数量を更新する
const updateQuantity = async () => {
const updateCarItem: CartItem = {
productId: updateInputProductId as number,
quantity: updateInputquantiy as number,
id: 0, // 使わない
customerId: 0, // 使わない
};
await cartItemService
.updateQuantity(updateCarItem, jwtAccessToken as string)
.then((response) => {
console.log(response.data);
})
.catch((error) => {
console.log("update is failed");
});
};
let removeInputProductId: number = 0;
// removeInputProducIdの値の商品を削除後 再度データベースから取得して画面更新する。
// ボタンに割り当てる
const removeCartItemAndGetItemsHandler = async () => {
await removeCartItem();
await getCartItems();
};
// removeInputProducIdの値の商品を削除する
const removeCartItem = async () => {
const removeCartItem: CartItem = {
productId: removeInputProductId as number,
id: 0,
customerId: 0,
quantity: 0,
};
await cartItemService
.removeProduct(removeCartItem, jwtAccessToken as string)
.then((response) => {
console.log(response.data);
})
.catch((error) => {
console.log(error);
});
};
// 顧客のカートアイテム全削除後、再度データベースから取得して画面更新する。
// ボタンに割り当てる
const deleteAndGetItemsHandler = async () => {
await deleteCartItem();
await getCartItems();
};
// 顧客のカートアイテム全削除する
const deleteCartItem = async () => {
await cartItemService
.deleteByCustomer(jwtAccessToken as string)
.then((response) => {
console.log(response.data);
})
.catch((error) => {
console.log(error);
});
};
return (
<>
<h1>カートアイテムの取得、追加、更新、削除 検証ページ</h1>
<div>※aaabaa@gmail.comでログインしているものとする。</div>
<h1>カートアイテムの全取得</h1>
<div>リアルタイムで表示されます。</div>
顧客ID,商品ID,数量
{currentCartItems.map((cartItem: CartItem) => {
return (
// eslint-disable-next-line react/jsx-key
<div>
<div>
{cartItem.customerId},{cartItem.productId},{cartItem.quantity}
</div>
</div>
);
})}
<br />
<h1>カートアイテム追加</h1>
<label>商品ID</label>
<input
type="text"
placeholder="商品ID"
onChange={(event) =>
(inputProductId = event.target.value as unknown as number)
}
/>
{/* onchangeをするとloginAndGetCartItemが実行されるのが良くない */}
<br />
<label>個数</label>
<input
type="text"
placeholder="1"
onChange={(event) =>
(inputQuantity = event.target.value as unknown as number)
}
/>
<button onClick={addCartItemHandler}>カートアイテム追加</button>
<h1>カートアイテムの数量を変更</h1>
<label>数量変更する商品ID</label>
<input
type="text"
placeholder="商品ID"
onChange={(event) =>
(updateInputProductId = event.target.value as unknown as number)
}
/>
<br />
<label>数量</label>
<input
type="text"
placeholder="数量"
onChange={(event) =>
(updateInputquantiy = event.target.value as unknown as number)
}
/>
<br />
<button onClick={updateQuantitAndGetCartItemsyHandler}>数量変更</button>
<h1>カートアイテムを1つ削除</h1>
<label> 商品ID</label>
<input
type="text"
placeholder="削除したい商品ID"
onChange={(event) =>
(removeInputProductId = event.target.value as unknown as number)
}
/>
<br />
<button onClick={removeCartItemAndGetItemsHandler}>remove</button>
<h1>カートアイテムを全削除</h1>
<button onClick={deleteAndGetItemsHandler}>
delete カートアイテム全削除
</button>
</>
);
}
import axios from 'axios';
import { CartItem } from '../types/cartItem';
/**
*
* @remarks SpringBootで作ったAPIを呼ぶ関数
*
*
* @param jwtAccessKey
* @returns
*/
export const addProductCart = ({productId,quantity}: CartItem, jwtAccessKey:string) =>{
return axios.post(`http://127.0.0.1:5000/api/cart/add`
+ `?productId=` +productId
+ `&quantity=` +quantity
,{},
{ headers : {
'Authorization': `Bearer ${jwtAccessKey}`,
'Request-Method' : 'POST',
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'http://127.0.0.1:5000/*',
'Access-Control-Allow-Headers': 'accept, accept-language, content-language, content-type',
'Access-Control-Allow-Credentials': 'true'
}}
);
}
//
export const getCartItems =(jwtAccessKey : string)=>{
return axios.get(`http://127.0.0.1:5000/api/cart/all`,
{ headers : {
'Authorization': `Bearer ${jwtAccessKey}`
}}
);
}
//
export const updateQuantity = ({productId,quantity}:CartItem,jwtAccessKey:string) =>{
return axios.put(`http://127.0.0.1:5000/api/cart/update`
+ `?productId=` +productId
+ `&quantity=` +quantity,
{},
{ headers : {
'Authorization': `Bearer ${jwtAccessKey}`,
'Request-Method' : 'PUT',
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'http://127.0.0.1:5000/*',
'Access-Control-Allow-Headers': 'accept, accept-language, content-language, content-type',
'Access-Control-Allow-Credentials': 'true'
}}
)
}
export const removeProduct = ({productId}:CartItem, jwtAccessKey:string) =>{
return axios.delete(`http://127.0.0.1:5000/api/cart/remove`
+`?productId=`+ productId,
{ headers : {
'Authorization': `Bearer ${jwtAccessKey}`
}}
);
}
export const deleteByCustomer = (jwtAccessKey:string) =>{
return axios.delete(`http://127.0.0.1:5000/api/cart/delete`,
{ headers : {
'Authorization': `Bearer ${jwtAccessKey}`
}}
);
}
export type CartItem = {
id:number; // 買い物かごのID
customerId:number // 顧客のID
productId:number // 商品のId
quantity: number // 数量
}
動きを確認
gifアニメで実際の動きを記録します。
画面左側はブラウザ 右側はmysql workbenchです。
追加時の処理
商品ID23 を2個追加している様子です。
github
数量変更時
商品ID23 を5個に変更している様子です。
github
買い物かごから選択した商品IDを削除
github
買い物カゴ全削除
github