https://blog.shako.net/installing-lemp-stack-on-ubuntu-server-16-04/


이거대로 하면 그냥 바로된다~

참고로 오늘기준으로 php-fpm이 7.2 버전으로 깔리기 때문에

7.0으로 되어있는 것들을 7.2로 바꿔주고 셋팅해주면 된다.

Vue.js를 Ubuntu(18.04)에 배포하기


본격적인 설명에 앞서, 아주아주 퓨어한 Ubuntu 환경에서 nginx를 설치하고 vue 첫 페이지가 보여지도록 하는 것이 목표이다.
환경은 ubuntu 18.04 / nginx 1.14.0

1. 서버 접속

ssh root@111.111.11.11

언제나 그렇듯 서버에 접속한다.

root 계정으로 111.111.11.11 IP를 가지는 서버에 접속한다.

비밀번호를 입력하고 서버에 딱 들어옴



2. nginx 설치

$ sudo apt update
$ sudo apt upgrade
위와 같이 업데이트/업그레이드를 해준 후,
$ sudo apt-get install nginx

nginx를 설치한다.


그리고 nginx를 테스트하기 전에 방화벽 소프트웨어를 조정하여 서비스에 액세스 할 수 있도록 해야한다고 한다.

$ sudo ufw app list
[Output]

Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS
  OpenSSH

ufw app list를 치면 아래의 output과 같이 나오는데,

  • Nginx Full :이 프로필은 포트 80 (암호화되지 않은 일반 웹 트래픽)과 포트 443 (TLS / SSL 암호화 트래픽) 모두를 연다.
  • Nginx HTTP :이 프로필은 포트 80 (일반, 암호화되지 않은 웹 트래픽)을 연다
  • Nginx HTTPS :이 프로필은 포트 443 (TLS / SSL 암호화 트래픽)을 연다.

라고 한다.

일단 HTTP를 열 예정이므로

$ sudo ufw allow 'Nginx HTTP'



3. nginx 설정파일 만지기

vue를 본격적으로 설치하기 전에 conf부터 만지자.
$ sudo vi /etc/nginx/sites-available/default

만약 default 파일이 새로 만들어진다면... nginx가 잘 설치되어있는지, 아니면 해당 디렉토리가 맞는지 한번 확인해봅니다.


여튼 #이 붙어있는 주석은 제외하고 알짜만 보자면

server { listen 3000 default_server; listen [::]:3000 default_server;

root /var/www/html/vue-front/dist; index index.html; server_name _; location / { try_files $uri $uri/ /index.html; } }

3000포트로 접속될 수 있게끔 할것이다. 원래 기본이 80포트로 되어있을텐데, 이거는 개개인의 취향에 맞게 설정하면 된다.


그리고 root는 vue dist가 뽑아져나올 디렉토리를 지정하면 된다. 이 또한 기본으로 /var/www/html 디렉토리 안에 vue를 설치할 것 이다.

index는 index.html로 설정하였다.


location / { ... }의 try_files $uri $uri/ /index.html; 는 https://router.vuejs.org/guide/essentials/history-mode.html 를 참고하여 설정해주었다.

참고로 vue cli를 사용하여 vue router를 사용할 것이기 때문에 location을 위와 같이 설정해주었다.


esc키 -> :wq 를 입력하여 저장후닫기 한다.



4. vue 및 webpack 설치

$ cd

앞서 3에서 root 디렉토리를 /root/vue-front로 잡아주었는데, 내가 편하게 직관적으로 쓰기 위해 잡아주었다.

아마 cd를 하면 기본으로 /root로 가질텐데 여기에 vue를 설치할 것이다.


또한, 해당 포스팅에서는 비록 설치를 하지만, git clone을 받아온다던지 하여 연결시켜줘도 무방하다.


4-1. Npm 설치

$ sudo apt-get install npm


4-2. Vue 설치

$ npm install -g vue

vue는 글로벌하게 설치해주었다.


4-3. Vue cli 설치

$ npm install -g vue-cli



4-4. webpack

$ cd /var/www/html

$ vue init webpack vue-front

아까 nginx 설정파일에서 root에 연결했던 /var/www/html 안에 들어가서

vue-front라는 이름으로 뷰를 셋팅해준다.

만약 blahblah 라는 디렉토리로 만들어주고싶으면 vue init webpack blahblah 이렇게 쓰면 되겠다.


4-5. 빌드하여 dist 뽑아내기

$ cd vue-front

$ npm run build

vue-front 디렉토리로 들어가서 build하면 dist 디렉토리에 배포파일이 뽑아져 나올 것이다.




브라우저를 통해 해당 서버로 접속하면 아래와 같이 vue 첫화면이 보이게 된다.




5. 발생했던 에러들...

에러 확인은 아래의 에러 로그로 확인 가능하다~

tail -f /var/log/nginx/error.log


5-1. "/root/vue-front/dist/index.html" failed (13: Permission denied)

처음엔 vue 셋팅할 곳을 /var/www/html이 아닌 /root안에 넣으려고 했었다. 근데 브라우저에 500 error가 찍히면서 에러로그를 확인해보니 권한 문제가 발생하였단다.

http로는 index.html이 실행할 수 없다는 것 간다.

  • /var/www 권한 : drwxr-xr-x

  • /root 권한 : drwxr------ 

/root 폴더 안에 셋팅하는걸 포기하고 그냥 /var/www 안에 셋팅하는걸로 해결했다.


5-2. rewrite or internal redirection cycle while internally redirecting to "/index.html"

무한루프 돈다는 말인거같다. 브라우저에 에러도 안찍히고 실행조차 안되더이다...

문제는 위에 /etc/nginx/sites-available/default 에서 오타가 있어서 생겼던 문제였다.

띄어쓰기로도 문제가 생길수있으니 잘 확인해야한다.

브라우저로 볼 때 자꾸 url 뒤에 #이 붙는다.

예를들어 http://127.0.0.1:8080/#/ 이런식으로

vue-router 설정인 src/router/index.js에 한 줄만 추가해주면 된다. (vue cli를 이용하여 webpack 사용)

import Vue from 'vue'; import Router from 'vue-router'; import HelloWorld from '@/components/HelloWorld'; Vue.use(Router); export default new Router({

mode: 'history',

routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld, }, ], });


mode: 'history' 를 주면 뒤에 # 붙는 현상은 없어진다.

이벤트버스는 vuex사용하지 않은 상태로

부모자식간에 데이터를 주고받기 위해 사용하였는데,

사용하다보니 vuex를 사용하는 것이 가장 베스트일거라는 생각이 들었다.



main.js

Vue.prototype.$EventBus = new Vue()


a.vue (이벤트버스 발송하는 곳)

export default {

...

methods: {

sendEventbus: function() {

this.$EventBus.$emit("result", "오오")

}

}

...

}

<button @click="sendEventbus">오오 보내기</button>

이런식으로 연결해놓는다.



b.vue (이벤트버스 받는 곳)

export default {

...

created() {

this.$EventBus.$on("result", function(data){

console.log(data)

})

}

...

)

이벤트버스는 created에서 받아야한다. 마치 운영체제에서 시그널 받길 기다리는 친구들처럼(?) 이벤트 버스를 받길 기다리면 된다.

그리고 result라는 이벤트버스가 emit될때마다 on으로 받아준다.

<ul class="pagination">
	<!-- 이전 -->
	<li class="paginate_button previous" :class="isFirstOrLast(results.first)" id="noticeList_previous">
		<a href="javascript:;">Previous</a>
	</li>
	<!-- 첫 페이지 -->
	<li class="paginate_button" :class="(1 == (results.number+1))? 'active' : ''">
		<a href="javascript:;" @click="movePage(1)">1</a>
	<!-- ... -->
	<li class="paginate_button disabled" v-if="results.number > 3">
		<a href="javascript:;">...</a>
	</li>
	</li>
	<!-- 반복되는 페이지 -->
	<li class="paginate_button" :class="(n == (results.number+1))? 'active' : ''" v-for="n in (results.totalPages-1)" v-if="showPagingButton(n)">
		<a href="javascript:;" @click="movePage(n)">{{ n }}</a>
	</li>
	<!-- ... -->
	<li class="paginate_button disabled" v-if="results.number < results.totalPages - 4">
		<a href="javascript:;">...</a>
	</li>
	<!-- 마지막 페이지 -->
	<li class="paginate_button" :class="(results.totalPages == (results.number+1))? 'active' : ''">
		<a href="javascript:;" @click="movePage(results.totalPages)">{{ results.totalPages }}</a>
	</li>
	<!-- 다음 -->
	<li class="paginate_button next" :class="isFirstOrLast(results.last)" id="noticeList_next">
		<a href="javascript:;">Next</a>
	</li>
</ul>

showPagingButton: function(n) { const vm = this var thisPage = vm.results.number + 1 var totalPage = vm.results.totalPages + 1 //[조건] 5페이지가 넘어가면 //[결과] 1 ~ 4페이지 숨겨주기 var cond1 = (thisPage < 5) && (n > 5) //[조건] 마지막페이지 ~ 마지막페이지-4 까지이면 //[결과] 마지막페이지-4를 제외한 나머지 페이지들 숨겨주기 var cond2 = (thisPage > (totalPage - 5)) && (n < (totalPage - 5)) //[조건] 첫페이지, 끝페이지 //[결과] 숨겨주기 var cond3 = (n == 1) || (n == totalPage) //[조건] thisPage > 4 이면 //[결과] 현재 페이지의 바로 앞 페이지를 제외한 앞에 애들 숨겨주기 var cond4 = (thisPage > 4) && (n < (thisPage - 1)) && (n < (totalPage - 5)) //[조건] thisPage < 마지막페이지-4 이면 //[결과] 현재 페이지의 바로 뒷 페이지를 제외한 뒤에 애들 숨겨주기 var cond5 = (thisPage < (totalPage - 4)) && (n > (thisPage + 1)) && (n > 5) if(cond1 || cond2 || cond3 || cond4 || cond5) return false return true }, isFirstOrLast: function(boolean) { return boolean? "disabled" : "" }, movePage: function(page) { this.formData.page = page-1 this.getLog(this.id) }

위와같이 하긴 했는데;

좀더 고민이 필요할듯.

promise를 사용하여 api콜하는 부분을 디자인해주었음

mixin의 promise부분을 살리고 

vm.promise(function)와 같이 사용하면 될듯

then 안에서 promise가 리턴되어야 그 결과값을 바탕으로 다음 then으로 넘어감



mixin.js

export default { methods: { promise: function(func){ //프로미스 만들기 return new Promise(function(resolve, reject){ resolve(func()) }) }, call: function(action) { var vm = this var successResult vm.promise(function(){ if (action.confirmMsg !== undefined) return vm.$confirm({ content: action.confirmMsg }) }) .then(() => { return action.run() }) .then(function(result) { successResult = result if (action.successMsg !== undefined){ return vm.$alert({ content: action.successMsg }) } }) .then(() => action.success(successResult)) .catch(function(error){ vm.errorAlert(error, action.errorTypeHandler) }) } }

main.js
import Vue from 'vue'
import myCall from './services/Call.js'

Vue.prototype.$call = myCall
test.vue

<template> ... </template> <script> import mixins from '../mixins/mixins.js' export default {     name: 'test',     mixins: [mixins],     methods: {         submit: function() {             var vm = this             if((vm.userId == '') || vm.userPw == '') return             var data = {                 userId : vm.userId, userPw : vm.userPw             }             var action = { run : function() {     return vm.$call.login(vm.userId, vm.userPw)                 },                 success : function(result) {                     localStorage.setItem('token', result.data.token)     localStorage.setItem('emailId', result.data.emailId)     document.location.href = "/"                 }             }             vm.call(action)         }     } }


여기서 class는 html의 class가 아닌 객체지향 프로그래밍의 class임을 말합니다.


아래 코드는 REST API 방식으로 구현하기 위해 만든 디자인 (참고용)



main.js

import Vue from 'vue' import Apifrom './services/ApiCall' Vue.prototype.$api = new Api(new Vue())

ApiCall.js

import axios from 'axios' import apiUrl from './api.call.js' class ApiCall { constructor (vue) { this.apiInfo = apiUrl.data() this.vueInstance = vue } userData (callBack) { this.callApi(callBack, this.apiInfo.Info.user) } changeAnything (callBack, data) { this.apiInfo.change.anything.setUrl(data.id, data.cardId, data.slot) this.apiInfo.change.anything.setConfirmMsg(data.currentFriends, data.changeFriends) this.callApi(callBack, this.apiInfo.change.anything) } callApi (callBack, api) { return new Promise (function(resolve){ resolve(function(){ }) }).then(() => { if (api.confirm) { return this.vueInstance.$confirm({content: api.confirmMsg}) } }).then(() => { return axios(api)

}).then((result) => { this.response = result if (api.successMsg !== undefined) { return this.vueInstance.$alert({content: api.successMsg}) } }).then(() => { if (callBack !== null) callBack(this.response) }).catch((error) => { this.apiCallFailed(error, api.errorMsg) }) } apiCallFailed (error, errorMsg = {}) { if (error === undefined || error.response === undefined ) return false let responseMsg = errorMsg[error.response.status] === undefined ? error.response.data.message : errorMsg[error.response.status] this.vueInstance.$alert({content: responseMsg}) return true; } }export default ApiCall

api.call.js
'use strict'
export default{
  name: 'api.call',
  data () {
    return {
      Info: {
        user: {
          url: process.env.API_URL + '/admin/user,
          method: 'GET'
        }
      },
      change: {
        anything: {
          url: '',
          method: 'PATCH',
          confirm: true,
          confirmMsg: '',
          successMsg: '변경 되었습니다.',
          setUrl: function(id, cardId, slot) {
            this.url = process.env.API_URL + '/admin/user/' + id + '/cards/' + cardId + '/slot/' + slot
          },
          setConfirmMsg: function(currentFriends, changeFriends) {
            this.confirmMsg = '장착중 프렌드: ' + currentFriends + '\n' + '장착할 프렌드: ' + changeFriends +
              '\n이렇게 변경 하시겠습니까?'
          }
        }
      }
    }
  }
}


지금까지 mixin을 사용하기 위해 각 컴포넌트 마다 export default { ... mixin: mixin ... }을 해주었는데

귀찮으니 전역으로 선언을 해주기로 하자


main.js

import Vue from 'vue'
import myMixin from './mixins/mixins'

Vue.mixin(myMixin)


부모 컴포넌트의 <script></script> 부분

this.items = [
	{
		"title": data.item_type[100],
		"img": require("@/assets/img/i-move.png")
	},
	{
		"title": data.item_type[200],
		"img": require("@/assets/img/i-rainbow.png")
	},
	{
		"title": data.item_type[300],
		"img": require("@/assets/img/i-random.png")
	}
]


자식 컴포넌트의 <template></template> 부분

<div v-for="item in items")

<img :src="item.img" height="45">

</div>


짧게 설명하자면

처음엔

"img" : "../../assets/img/i-move.png"

이런식으로 작성하였는데 이미지를 제대로 가져오지 못하더라..(404 에러)

그래서 구글링한 결과, require로 이미지를 가져온 후 이걸 고대로 쏴주면 (props나 evnetbus나 vuex 등을 이용해서)

이미지가 잘 출력된다.

var url = window.location // for sidebar menu entirely but not cover treeview $('ul.sidebar-menu a').filter(function() { return this.href == url; }).parent().addClass('active'); // for treeview $('ul.treeview-menu a').filter(function() { return this.href == url; }).parentsUntil(".sidebar-menu > .treeview-menu").addClass('active');


window.location을 이용하여 url을 가져오고,

메뉴의 링크와 비교하여 같다면 active class를 추가한다.


+ Recent posts