📝 Outline
- 회원정보 수정을 위한 Profile 페이지 구현을 완료했다.
- 아마 지금까지 구현한 페이지들 중에서 가장 많은 로직과 코드가 들어간 페이지가 아닐까 싶다.
- 구현 디자인은 다음과 같다.
- 해당 페이지에서는 Username을 제외한 계정 정보를 수정할 수 있으며, 배송지를 추가할 수도 있다.
- Account와 Address 입력 데이터 모두 유효성 검사가 포함되어 있으며, 당연하게도 유효성 검사를 통과하지 못할 경우 데이터가 DB에 반영되지 않는다.
📝 Review
- 대부분의 로직이 클라이언트 쪽에 집중되어 있고, 주로 사용자의 편의를 위해 작성된 로직이 대부분이라 코드의 양은 많았지만 사실상 핵심적인 부분은 크게 두 가지 정도로 나눌 수 있을 것 같다.
- 하나는 유효성 검사, 다른 하나는 카카오 주소 API이다.
💬 Validation
- 회원정보 수정을 주 목적으로 하는 해당 페이지에서는 유효성 검사가 필연적으로 들어가야 했다.
- 유효성 검사는 크게 클라이언트측과 서버측으로 나뉘며, 각기 다른 방법으로 유효성 검사를 수행한다.
- 먼저 클라이언트측 유효성 검사 방식부터 알아보자.
<!-- Input Account Info -->
<v-form
ref="accountForm"
v-model="accountValid"
lazy-validation
>
<div class="div-grid_container">
<div>
<div>Username</div>
<v-text-field
v-model="account.username"
class="v-text-field-input_account_info"
dense
color="#666666"
disabled
></v-text-field>
</div>
<div>
<div>Password</div>
<v-text-field
v-model="account.password"
class="v-text-field-input_account_info"
:append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
:type="showPassword ? 'text' : 'password'"
clearable
dense
counter
color="#666666"
:rules="accountRules.password"
@click:append="showPassword = !showPassword"
></v-text-field>
</div>
<div>
<div>Nickname</div>
<v-text-field
v-model="account.nickname"
class="v-text-field-input_account_info"
clearable
dense
counter
color="#666666"
:rules="accountRules.nickname"
></v-text-field>
</div>
<div>
<div>Phone</div>
<v-text-field
v-model="account.phone"
class="v-text-field-input_account_info"
dense
color="#666666"
@keyup="getPhoneMask(account.phone)"
:rules="accountRules.phone"
></v-text-field>
</div>
</div>
</v-form>
<!-- Input Account Info End -->
- vuetify에서 제공하는
<v-form>
은 기본적으로validate()
기능을 제공한다. - 사용 방법 또한 매우 간단한데, 우선 위와 같이
<v-form>
태그에ref
속성을 지정해주고 해당 폼 태그 안에 포함되어 있는 요소에rules
속성을 통해 유효성 검사 조건을 설정해준다. rules
속성에 적용되는 검사 조건은 다음과 같은 방식으로 정의해준다.
export default {
data() {
return {
...
accountRules: {
password: [
v => !!v || "Password is required",
v => (v && 8 <= v.length && v.length <= 16 && /^(?=.*[A-Za-z])(?=.*\d)(?=.*[.`!@#$%^&*?])[A-Za-z\d@.`!@#$%^&*?]{8,16}$/.test(v)) || "Password must be between 8 and 16 characters including english, numbers, and special characters."
],
nickname: [
v => !!v || "Nickname is required",
v => (v && 2 <= v.length && v.length <= 10) || "Nickname must be between 2 and 10 characters"
],
phone: [
v => !!v || "Phone is required",
v => /\d{3}-\d{3,4}-\d{4}/.test(v) || "Phone must be valid"
]
},
...
};
},
}
- 이렇게 간단히 유효성 검사를 수행할 수 있으며, 그 결과에 따라 DB에 반영할지 말지를 결정하기 위해 다음과 같은 코드를 작성해주었다.
updateInfo() {
if(this.$refs.accountForm.validate()) {
this.$axios.put("/customer/update", this.account).then((res) => {
this.account = res.data;
this.beforeAccount = JSON.parse(JSON.stringify(res.data)); // Deep copy
this.successAlert = true;
setTimeout(() => {
this.successAlert = false;
}, 2000);
});
} else {
this.errorAlert = true;
setTimeout(() => {
this.errorAlert = false;
}, 2000);
}
},
this.$refs.accountForm.validate()
를 통해 유효성 검사의 결과를 확인할 수 있으며, 그 결과에 따라 분기하고 있다.- 결과를 살펴보면 다음과 같다.
- 서버측에서는
spring-boot-starter-validation
을 이용하여 유효성 검사를 실시하였다. - 이를 위해 먼저 아래와 같이 의존성 추가를 수행준다.
implementation 'org.springframework.boot:spring-boot-starter-validation'
- 이후 DTO 클래스에 다음과 같이 어노테이션을 설정해주었다.
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class UpdateInfoDto {
private Long id;
@NotBlank(message = "Username is required")
@Pattern(regexp = "^[a-zA-Z0-9+-\\_.]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$", message = "E-mail must be valid")
private String username;
@NotBlank(message = "Password is required")
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[.`!@#$%^&*?])[A-Za-z\\d@.`!@#$%^&*?]{8,16}$",
message = "Password must be between 8 and 16 characters including english, numbers, and special characters.")
private String password;
@NotBlank(message = "Nickname is required")
@Size(min = 2, max = 10, message = "Nickname must be between 2 and 10 characters.")
private String nickname;
@NotBlank(message = "Phone is required")
@Pattern(regexp = "\\d{3}-\\d{3,4}-\\d{4}", message = "Phone must be valid")
private String phone;
}
- 이후 Controller에서 이를 캐치하기 위해
@Valid
어노테이션과BindingResult
를 사용한다.
@RequestMapping("customer")
@RestController
public class CustomerApiController {
@Autowired
private CustomerService customerService;
...
@PutMapping("/update")
public ResponseEntity<?> updateInfo(@Valid @RequestBody UpdateInfoDto updateInfoDto, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
Map<String, String> validatorResult = customerService.validateHandling(bindingResult);
return new ResponseEntity<>(validatorResult, HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(customerService.updateInfo(updateInfoDto), HttpStatus.OK);
}
}
- 만약 유효성 검사에 통과하지 못했다면
bindingResult.hasErrors()
를 통해customerService
에 정의되어 있는validateHandling
메서드가 호출될 것이며, 해당 메서드는 다음과 같이 정의되어 있다.
@Service
public class CustomerService {
...
@Transactional(readOnly = true)
public Map<String, String> validateHandling(BindingResult bindingResult) {
Map<String, String> validatorResult = new HashMap<>();
for(FieldError error : bindingResult.getFieldErrors()) {
String validKeyName = String.format("valid_%s", error.getField());
validatorResult.put(validKeyName, error.getDefaultMessage());
}
return validatorResult;
}
}
- 즉, 유효성 검사에 실패한 요소를 추출하여 실패 원인 메시지와 함께
Map
자료구조로 저장하는 것이다. - 이후 Postman을 사용하여 유효성 검사에 실패하는 요청을 수행해보면 다음과 같은 결과를 확인할 수 있다.
💬 카카오 주소 API
- 주소 데이터를 추가하는 작업을 수행하면서 카카오 주소 API를 사용하게 되었다.
- 그 사용 방법은 매우 간단했다.
- 우선 아래 코드를
public
폴더에 있는index.html
파일의<head>
태그 안에 추가해준다.
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
- 이후 API를 사용할 vue 파일에 다음과 같이 메서드를 정의해준다.
// Kakao Address API
openPostCode() {
new window.daum.Postcode({
oncomplete: (data) => {
this.zipcode = data.zonecode;
this.streetAddress = data.roadAddress;
}
}).open();
},
- 마지막으로 해당 메서드가 적절히 호출될 수 있도록 이벤트를 설정해주었다.
<!-- Set Address Dialog -->
<v-form
ref="addressForm"
v-model="addressValid"
>
<div class="mt-3">
<v-dialog v-model="addressDialog" max-width="600px">
<template v-slot:activator="{ on, attrs }">
<v-btn class="btn-account" dark v-bind="attrs" v-on="on" elevation="0">
Add New Address
</v-btn>
</template>
<v-card>
<v-card-title>
<span class="text-h5">Add New Address</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="6">
<v-text-field
v-model="addressName"
label="Name*"
:rules="addressRules.addressNameRules"
required
hint="Please set the name of your address."
></v-text-field>
</v-col>
<v-col cols="12" sm="6">
<v-text-field
v-model="zipcode"
label="Zip / Postal code*"
:rules="addressRules.addressCodeRules"
required
readonly
@focus="openPostCode"
></v-text-field>
</v-col>
<v-col cols="12">
<v-text-field
v-model="streetAddress"
label="Street address*"
:rules="addressRules.streetAddressRules"
required
readonly
hint="Please enter it through the Zip / Postal code."
></v-text-field>
</v-col>
<v-col cols="12">
<v-text-field
v-model="detailAddress"
label="Detail address"
></v-text-field>
</v-col>
<v-col cols="12" class="mt-n7 ml-n1">
<v-checkbox
v-model="isDefault"
label="Make this my default address"
></v-checkbox>
</v-col>
</v-row>
</v-container>
<small>*indicates required field</small>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="darken-1" text @click="resetDialog">
Close
</v-btn>
<v-btn color="darken-1" text @click="addNewAddress" :disabled="!addressValid">
Save
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</v-form>
<!-- Set Address Dialog End -->
- 자세히 살펴보면, zipcode를 입력하는 요소에서
@focus
를 통해 위에서 정의한 메서드를 호출하는 것을 확인할 수 있다. - 이렇게 하면, 위 메서드가 호출될 경우 주소 검색을 위한 팝업창이 뜨게 되고, 해당 팝업창 내에서 주소를 선택할 경우 해당 데이터가 전달되어
v-model
에 적용됨으로써 알맞게 동작하게 된다. - 결과는 다음과 같다.
'🚗 Backend Toy Project > Baeg-won Clothing Gallery' 카테고리의 다른 글
[Baeg-won Clothing Gallery] 8. WISH 페이지 구현 (0) | 2023.07.28 |
---|---|
[Baeg-won Clothing Gallery] 7. CART 페이지 구현 (0) | 2023.07.24 |
[Baeg-won Clothing Gallery] 6. DETAIL 페이지 구현 (0) | 2023.07.16 |
[Baeg-won Clothing Gallery] 5. SALE, CONTACT 페이지 구현 (0) | 2023.07.15 |
[Baeg-won Clothing Gallery] 4. CLOTHING, FOOTWEAR, ACCESSORIES 페이지 구현 (0) | 2023.07.14 |