LostCatBox

SpringProject-TrackingPost-CH05

Word count: 2.1kReading time: 13 min
2022/12/24 Share

Project 통합택배조회 api 05편 (네번째 구현-vue,로직개선)

Created Time: September 16, 2022 6:11 PM
Last Edited Time: September 21, 2022 3:09 PM

현재 구조

1

당장할것

  • vue로 동적 페이지 개발 → 백엔드와 프론트엔드 나누기
  • 재설계
    • 크롤링데이터 get 처리.. “”null???
    • https://kth990303.tistory.com/279
    • 함수가 어떤걸 반환해야하는건지
      null 처리 ? 객체로 valid처리?
    • CUpost확인하기
      364321267646,364318961463 됨
      22096084054 → 이런거 들어오면 오류 처리, 막을 방법필요 → 편의점끼리 택배 운송장
  • 멀티 모듈 정리하기(build.gradle dependency정리)
  • 21일 작성→ ppt랑 포트폴리오 → 동영상 → 약식 발표

시도해볼것

  • link로 접속시 userid+postNumber로 간단한 유저 인증 후 검색기록 list로 제공하기
  • 한번 scale-out해보기
    • 많은 consumer와 provider를 docker로띄우고 연결
    • 트레픽, 스트레스 테스트가능? → X
  • 다른 택배사 추가하기 → provider 추가하기

HomeVue 만들기(프론트페이지)

왜?

현재 서비스는 8080포트로 직접 사용자가 servicehome으로 접속하였다. 따라서 API에서는 약속대로 JSON을 반환하였다. 이는 브라우저에 따라서 JSON을 렌더링하거나, 하지못했다.

또한 API에서 지원하는 새로고침 또한 활용할수없었다. → 가장 최근 정보를 반영하는 방법은 다시 카톡을 입력하는방법말고없었음

따라서 프론트 페이지를 만들고, 거기에서 servicehome API를 get과 post를 보내고 응답받는다. 한 페이지에서 새로고침도 가능하다.

즉, 프론트 및 백엔드로 나누므로써 원래 목적한 책임도 분리가능하다.

vue+nginx구성

vue를 그냥 npm serve run만 해서 돌리면되는거아닌가? 라는생각을 했다…근데 다들 nginx를 통해 구현을 하더라 이유를 살펴보니 vue만 실행했을때는 예상하지못하게 vue 빌드가 내려간다고..

그래서 아마 nginx가 포트포워딩에 강점과 static files를 서비스해주는것에도 강점을 가지고있으므로 nginx와 vue를 같이 서비스하는거같다

nginx는 2가지에 보통 쓰인다
첫번째 역할은 *웹서버의 역할은 *HTML, CSS, Javascript, 이미지와 같은 정보(staticfiles)를 웹 브라우저(Chrome, Iexplore, Opera, Firefox 등)에 전송하는것이다.
두번째 역할은 리버스 프록시(reverse proxy)인데, 한마디로 말하면 클라이언트는 가짜 서버에 요청(request)하면, 프록시 서버가 배후 서버(reverse server)로부터 데이터를 가져오는 역할을 한다. 여기서 프록시 서버가 Nginx, 리버스 서버가 응용프로그램 서버를 의미한다.

docker-compose 구조변경

2

나머지 설정

vue 프로젝트의 root밑에 Dockerfile 생성

vue create

vue create frontend

cd frontend

npm install vue-router —-save

Dockerfile

  • 멀티빌드를 사용하여, node에서 vue를 빌드 시키고, 컨테이너 생성시 해당 이미지의 있는 파일들을 nginx로 COPY후 nginx를 실행하였다.
  • 참고로 도커의 멀티 빌드를 사용했다. 따라서 build할때 용량과 막상 docker run 할때 용량은 다르고 축소시킬수있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
# build stage
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
RUN npm run build

# production stage
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
  • docker-compose 내용 추가
1
2
3
4
5
6
7
8
9
10
version: '3'

services:
homevue:
build:./frontend
ports:
- "80:80"
volumes:
- "./frontend:/app"
- "/app/node_modules"

vue-bootstrap

npm이용 설치

1
2
npm install --save @popperjs/core //bootstrap 설치이전 필수
npm install vue bootstrap bootstrap-vue-3 #bootstrap설치

main.js 내 import

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { createApp } from 'vue'

import App from './App.vue'

import router from './router'

import BootstrapVue3 from 'bootstrap-vue-3'
import 'bootstrap/dist/css/bootstrap.css'

import 'bootstrap-vue-3/dist/bootstrap-vue-3.css'

const app = createApp(App)
app
.use(router)
.use(BootstrapVue3) //추가해주기
.mount('#app')

Vue에 기능넣기

스펙

  • onepage동작으로 구성(내부망으로 API요청관건
  • 초기접속 → get method로 homeservice로 API요청→ 해당url pathparameter에 대한 조회해서 가져오기
  • 새로고침 기능 넣기 → post method로homeservice로 API요청 → 해당url pathparameter에 대한 데이터 갱신 요청후 → vue 페이지 alert(”결과보고”)→ reload() or fnGetPostDeatil() 호출 → 프론트에 데이터 반영완료
  • 마이기록 조회 기능넣기 → 누르면 url path 변경하면서 /recodes 로 들어가며, homeservice API에 get method로 요청→ List 반환받아서 작성해줌
  • 이렇게 구현하면, vue를 통해 접속하고 vue에서 내부망으로 접속홀수잇으므로

구현

PostDetail.vue

  • recodes로 넘어갈때 params.userId를 query로 넘겨줌
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<script>
export default {
name: "PostDetails",
components: {},
data() {
return {
requestBody: '', //boardlist에서 push한것 대한 쿼리 값!
postCompany: '',
postNumber:'',
sender:'',
receiver: '',
contentType: '',
message: '',
location: '',
statusData: '',
modifiedDate: '',
};
},
mounted() {
this.fnGetPostDetails()
},
methods: {
fnGetPostDetails() {
this.requestBody = { // JSON 데이터 전송
}
this.$axios.get(
this.$homeserviceAPIUrl + "/"+this.$route.params.userId+"/"+this.$route.params.postNumber+"/", {
params: this.requestBody}
).then((res)=> {
console.log(res); //서버
this.postCompany= res.data.postCompany,
this.postNumber= res.data.postNumber,
this.sender= res.data.sender,
this.receiver= res.data.receiver,
this.contentType= res.data.contentType,
this.message= res.data.message,
this.location= res.data.location,
this.statusData= res.data.statusData,
this.modifiedDate= res.data.modifiedDate
}).catch((err) => {
if (err.message.indexOf('Network Error') > -1) {
alert('네트워크가 원활하지 않습니다.\n잠시 후 다시 시도해주세요.')
}
})
},
fnUpdate(){
//테스트환경에서는 postCompany임의로 넣어주기
this.$axios.post(
this.$homeserviceAPIUrl + "/"+this.$route.params.userId+"/"+this.$route.params.postNumber+"/",
{},{
params: {"postCompany":this.postCompany},
headers: { 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Accept': '*/*'}
}
).then(()=> {
alert("새로고침되었습니다.")
this.fnGetPostDetails()
}).catch((err)=> {
console.log(err)
alert("네트워크 오류 또는 정상적인 처리 못함(400일경우 택배회사오류)")
})
},
fnMyRecodes(){
this.$router.push({
path: '/recodes',
query: {userId: this.$route.params.userId}
})
}
},
}
</script>
<style scoped>

</style>

Recodes.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<script>
export default {
name: "Recodes",
components:{
},
data(){
return{
userId: this.$route.query.userId, //PostDetail.vue가 query로 넘겨준 데이터
list: {}, //리스트 데이터
}
},
mounted() {
this.fnGetList()
},
methods: {
fnGetList() {
this.requestBody = { // 데이터 전송
}
this.$axios.get(this.$homeserviceAPIUrl + "/"+this.userId+"/", {
}).then((res) => {
console.log(res.data);

this.list = res.data //서버에서 데이터를 목록으로 보내므로 바로 할당하여 사용할 수 있다.

}).catch((err) => {
if (err.message.indexOf('Network Error') > -1) {
alert('네트워크가 원활하지 않습니다.\n잠시 후 다시 시도해주세요.')
}
})
},
fnPostDetails(){
this.$router.go(-1)
}
},
}
</script>

PostEntity

PostDTO객체에서 이제 postCompany 값을 같는다

postCompany를 ENUM으로 처리를 String 또는 value 또는 converter로 할지 선택해야함

jpa의 postCompany Enum 처리 → string으로 DB에도 남기고싶기에 다음과같이 처리하였다.

  • post entitiy에 추가
1
2
3
@Enumerated(EnumType.STRING)
@Column(length=50,nullable =false)
private PostCompanyEnum postCompany; //postCompany

위 정보에 맞게 dto들도 수정하였다.

추가로 이제 response용 ResponsePost를 DTO로 따로 만들어 응답하므로 내부 id값등의 불필요한 정보는 유저에게 주지않았다. 또한 postConpany 값마다의 name을 정해 name으로 반환하도록하였다

개선점

null반환?Optional반환?빈 객체반환? (???)

Student객체를 반환하는 책임 갖는 함수는 도대체 어떤것을 반환하는것이 더 좋은 방법일까?

  • return null → 호출한곳에서 if null처리가 필수다. 잘못하면 NullpointException가능
  • return new Student(); 빈 객체 반환 → null대신 쓴다면 에러 생성된 객체가 비어있는지 검사하는 로직 개별적구현..모든 객체 통일어려움 → 호출한쪽에서 NullpointException X
    • 빈 객체반환하여 해당 데이터가 존재하지않음을 보여줄때나 활용할때 사용하는것이 좋을듯하다.
  • return Optional.ofNullable(student); Optional 객체반환 → 호출한 쪽에서 .getOrDefault(new Student())사용하면 null이들어가있어도 다른처리가능 → NullpointException X
    • Optional 방법은 호출자가 받은값은 비어있을수있음을 알수있기 때문에 권장!
    • 하지만 Optional을 쓴다는것은 호출자가 매번 값을 체크해야함을 의미할수있기에 생각해서쓰자.(endpoint에서 쓰는게 좋을듯?→한번체크하면 담부터 Optional안쓰는게나을듯)
1
Optional<Studnet> optionalWithStudent = Optional.of(convert(student)) //convert는 student객체 또는null반환하는 함수

호출자 입장에서의 Optional 활용

1
Student resultstudent = optionalWithStudent.getOrDefault(new Student()) // get modified student or new student if condition inside convert() was not met
1
2
3
4
5
6
7
Optinal<Student> studentOptinal = convert(st);//convert가 student객체 또는null반환하는 함수

if(studentOptional.isPresent(){
// Do the logic when the student follows the condition
}else{
// I know that the student doesn't follow the condition
}

오류

nginx+vue 하위 경로 접속시 404오류

https://slog97.tistory.com/40

현상

메인페이지만 나오고 하위 경로는 404 에러

원인

Vue가 SPA이기 때문이다. Nginx는 정적 컨텐츠를 전달할 때, root 디렉터리에서 파일의 존재 여부를 찾는다.

‘http://{IP주소}:{포트번호}/mainpage’ 로 요청을 보내고 nginx 설정에서 root 디렉터리를 ‘/usr/share/nginx/html’ 이라고 했을 때, 해당 root 경로 안에서 mainpage.html 이라는 파일을 찾아 전달하려고 한다는 말이다.

하지만 나의 경우 하위 경로가 파일로 존재하는 것이 아니고 하나의 페이지에서 내용만 바뀌는 SPA 방식이기 때문에 존재하지 않는 파일이라는 의미에서 404 에러가 나타났던 것이다.

해결

아래 설정을 nginx.conf에 반영이 필요했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
location / {
# 나의 루트 디렉터리는 해당 경로였다.
root /usr/share/nginx/html;
# 다음과 같이 변경을 해서 origin을 제외한 uri경로로 탐색시도 -> 마지막 탐색이 index.html
try_files $uri $uri/ /index.html;
}

# end ---------------------------------------------------------------------------------------------

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}

nginx+vue 405 에러

현상

docker-compose실행후 내부에서 vue에서의 axios를 활용한 get, post method 요청시 405에러 발생

3

해결

  • 일단 요청 주소를 다시 확인
    http://부터 요청 주소 정확히 맞는지확인

  • cross-origin 백엔드에서 허용 여부확인

    • cross-origin(현 도메인과 다른 도메인으로 요청)시 preflight로 먼저 요청을 보낸후 지원하는 메서드, cross-origin 지원여부를 받고, 지원하지않는다면 본래의 요청은 보내지않고, 405 status 나온다!
  • 서버 사이드 렌더링이 아니라서 vue에서 js로 axios를 통해 다시 보내는데, 문제는 브라우저입장에서 docker안에 내부망을 못쓴다.

    • 그래서 8080으로 servicehome을 열고,,요청보냇더니 잘 읽어왔다.
  • requestparam 과 requestbody차이 잘보자

    • requestparam으로 처리하고 이를 다음과같이 params로 처리하였다.
    1
    2
    3
    this.$axios.post(
    this.$homeserviceAPIUrl + "/"+this.$route.params.userId+"/"+this.$route.params.postNumber+"/",
    {},{params: {"postCompany":this.postCompany}}

url encoding

상황

대한통운 params로 axios에서 처리하니 다음고같이 나타났다

CJ%EB%8C%80%ED%95%9C%ED%86%B5%EC%9A%B4

이를 spring에서 requestparams가 그대로 인식하여 오류가 났다.

원인

이유는 요청시 header에 urf-8을 넣어줘야 제대로 넣어줘야, 요청과 응답에서 인코딩제대로 가능하다.

해결

axios.post에서 header정보 추가해주기

'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'

1
2
3
4
5
6
7
8
this.$axios.post(
this.$homeserviceAPIUrl + "/"+this.$route.params.userId+"/"+this.$route.params.postNumber+"/",
{},{
params: {"postCompany":this.postCompany},
headers: { 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Accept': '*/*'}
}
)

vue-cli-service: not found

현상

Dockerfile

  • dockerfile 빌드시 npm run build과정에서 에러가 뜨고,vue-cli-service: not found 였다.
1
2
3
4
5
6
7
8
9
10
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install --production # 반드시 run npm install
RUN npm install -g @vue/cli
RUN npm install axios --save #vue에 axios 설치
RUN npm install --save @popperjs/core #bootstrap설치이전필요
RUN npm install vue bootstrap bootstrap-vue-3 #bootstrap설치
COPY . .
RUN npm run build

원인

package.json파일 내용이 원인이였다

기존은 devDependencies에있어서 개발 및 테스트 환경에서만 @vue/cli이 설치되었음 → Dockerfile에서의 npm run build처리시 vue/cli가 not found될수밖에없음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"dependencies": {
"@popperjs/core": "^2.11.6",
"axios": "^0.27.2",
"bootstrap": "^5.2.1",
"bootstrap-vue-3": "^0.3.3",
"core-js": "^3.8.3",
"vue": "^3.2.39",
"vue-router": "^4.1.5"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3"
},

해결

  • root폴더에 package.json @vue/cli 관련 코드 위치 변경
1
2
3
4
5
6
7
"dependencies": {
...
"@vue/cli-plugin-babel": "~5.0.0", //build시 필요한데 devDependencies에있어서 not found에러 발생
"@vue/cli-plugin-eslint": "~5.0.0",//build시 필요한데 devDependencies에있어서 not found에러 발생
"@vue/cli-service": "~5.0.0" //build시 필요한데 devDependencies에있어서 not found에러 발생
...
},

package.json에 대하여…

4

vue create 명령으로 생성된 node_modulespackage.json 을 살펴본다.

package.json 파일에는 프로젝트 정보 및 scripts, dependencies, devDependencies 등의 속성이 기본적으로 포함되어 있다.

  • scripts : 주로 사용할 명령어 모음
  • dependencies : 해당 모듈에 필요한 라이브러리 모음 (프로덕션 환경))
  • devDependencies : 해당 모듈에 필요한 라이브러리 모음 (개발 및 테스트 환경)

디펜던시를 패키지 관리자(npm, yarn, …)로부터 설치하고 나면 node_modules 디렉토리에 포함된다. 각종 테스트 도구나 컴파일러 관련 도구들은 개발 및 테스트 환경에서만 사용될 것이므로 devDependencies 속성에 포함할 것이다. 또한 빌드시 devDependencies 속성의 디펜던시들은 포함되지 않으므로 더 빠르고 가볍게 빌드가 가능하다. package.json 파일에 디펜던시를 정의한 후 npm 으로 설치해도 되고, npm 명령어(–save)로 설치 후에 package.json 파일의 디펜던시 속성을 업데이트 할 수도 있다.

예시: npm install axios —-save

디버깅시 해당주소 접속해도안됨

local에서 돌릴려하니까 안됨(SpringSecurity도 exclude함)

1
2
3
2022-09-21 10:39:31.747  INFO 65034 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-09-21 10:39:31.748 INFO 65034 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-09-21 10:39:31.749 INFO 65034 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms

package 이슈가있었음. 멀티모듈할떄 잘못되어있었음

또한 @Pathvariable도 {test}로 { }가 안쳐저있었음

Invoke Error → RestController로 선언하지않아서 스프링 컨트롤러가 template찾고있었음.

provider택배 크롤링쪽에서의 오류

크롤링시 get 오류인

IndexOutOfBoundsException 나옴 이때는 try catch문으로 잡았음. → log찍고 끝

CATALOG
  1. 1. Project 통합택배조회 api 05편 (네번째 구현-vue,로직개선)
  2. 2. 현재 구조
  3. 3. 당장할것
  4. 4. 시도해볼것
  5. 5. HomeVue 만들기(프론트페이지)
    1. 5.1. 왜?
    2. 5.2. vue+nginx구성
    3. 5.3. docker-compose 구조변경
    4. 5.4. 나머지 설정
      1. 5.4.1. vue create
      2. 5.4.2. Dockerfile
    5. 5.5. vue-bootstrap
      1. 5.5.1. npm이용 설치
      2. 5.5.2. main.js 내 import
    6. 5.6. Vue에 기능넣기
      1. 5.6.1. 스펙
      2. 5.6.2. 구현
  6. 6. PostEntity
  7. 7. 개선점
    1. 7.1. null반환?Optional반환?빈 객체반환? (???)
      1. 7.1.1. 호출자 입장에서의 Optional 활용
  8. 8. 오류
    1. 8.1. nginx+vue 하위 경로 접속시 404오류
      1. 8.1.1. 현상
      2. 8.1.2. 원인
      3. 8.1.3. 해결
    2. 8.2. nginx+vue 405 에러
      1. 8.2.1. 현상
      2. 8.2.2. 해결
    3. 8.3. url encoding
      1. 8.3.1. 상황
      2. 8.3.2. 원인
      3. 8.3.3. 해결
    4. 8.4. vue-cli-service: not found
      1. 8.4.1. 현상
      2. 8.4.2. 원인
      3. 8.4.3. 해결
      4. 8.4.4. package.json에 대하여…
    5. 8.5. 디버깅시 해당주소 접속해도안됨