잊지 않겠습니다.

Webpack을 이용한 angularJS 1 application 개발

기존 grunt를 이용한 angularjs application을 webpack을 사용하도록 변경하고자 합니다. 일단 webpack을 사용하게 되면 다음과 같은 장점을 가질 수 있습니다.

  1. npm을 이용한 외부 liberary의 관리
  2. es6 문법의 사용
  3. import/require의 사용 가능
  4. nodejs의 server code와 client code의 완벽 호환 가능

위 장점 이외에, 최근의 웹 개발 양상은 module화입니다. 모든 liberary들이 CommonJS 방식으로 module화가 되어가고 있는 시점에서, 추세에 따라가지 못하면 도태가 되어버리는 것 같은 압박감을 느끼기도 하지요.

환경

사용되는 application의 주요 library들은 다음과 같습니다.

  • jquerey
  • angularjs
  • lodash
  • restangular
  • kendo ui Pro

구조

기본적으로 webpack의 폴더구조는 maven의 구조와 비슷하게 가는 경향들이 있습니다. 다음과 같은 folder구조를 가지고 갈 것입니다.

.
├── dist
├── gulpfile.js
├── libs
│   └── kendo
├── LICENSE
├── node_modules
├── package.json
├── README.md
├── src
│   ├── app
│   └── public
├── webpack.config.js
├── webpack-test.sublime-project
└── webpack-test.sublime-workspace
  • dist: build된 file들이 위치할 folder입니다.
  • libs: bower/npm이 지원되지 않는 외부 library들이 위치한 folder입니다. 상기 application에서는 kendo가 포함됩니다.
  • src: 소스가 위치합니다.
    • public: public에 배포될 css, image, html 들이 위치할 folder입니다.
    • app: angularJs application이 위치할 folder입니다. app.js를 시작접으로 application이 구동될 것입니다.

여기서 app folder의 경우 다음과 같은 구조로 구성됩니다. (기본적으로 yo angular와 동일한 구성을 가지고 갑니다.)

├── controllers
│   ├── home.js
│   └── user.js
├── directives
│   └── nameButton.js
├── services
│   ├── BaseService.js
│   └── CodeService.js
├── app.js
└── routes.js

사용자 directive, controller, service를 모두 사용합니다.

packages.js

webpack은 기본적으로 bower를 사용하지 않습니다. 모든 js library들은 npm을 통해 설치되며 관리됩니다.

basic angularJS application (STEP01)

가장 기본적인 angularjs application을 작성해보도록 합니다. 다음과 같은 특징을 갖습니다.

  • /home, /user 라는 두개의 route를 갖습니다.

추후에 npm을 이용해서 module을 관리할 예정이기 때문에 처음 시작은 모두 cdn을 이용해서 처리하도록 합니다.

public안에 다음과 같이 구성합니다.

├── js
│   ├── controllers
│   │   ├── home.js
│   │   └── user.js
│   └── app.js
├── home.html
├── index.html
└── user.html

그리고, /home, /user의 url router를 다음과 같이 구성합니다.

// app.js
'use strict';

angular
  .module('app', ['ui.router'])
  .config(function ($stateProvider, $urlRouterProvider) {
    $stateProvider.state('home', {
      url: '/home',
      templateUrl: 'home.html',
      controller: 'HomeController'
    })
    .state('user', {
      url: '/user',
      templateUrl: 'user.html',
      controller: 'UserController'
    });
  });

index.html

<!doctype html>
<html ng-app="app" lang="en">
<head>
  <meta charset="UTF-8">
  <title>Angular App</title>
</head>
<body>
  <button ui-sref="home">Home</button>
  <button ui-sref="user">User</button>
  <ui-view></ui-view>
</body>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.0-alpha.4/angular-ui-router.min.js"></script>
  <script src="/js/app.js"></script>
  <script src="/js/controllers/home.js"></script>
  <script src="/js/controllers/user.js"></script>
</html>

home.html

<p>this is home.html</p>

user.html

<p>This is user.html</p>

이제 public folder내에서 http-server를 실행해보면 간단한 angularjs webapp 이 생성됩니다.

basic angularJS application + webpack (STEP02)

이제 webpack을 이용해서 angularjs application을 구성해보도록 하겠습니다.
먼저, public/js 안에 있는 모든 js 파일들을 모두 app folder로 옮깁니다.

.
├── app
│   ├── controllers
│   │   ├── home.js
│   │   └── user.js
│   └── app.js
└── public
    ├── home.html
    ├── index.html
    └── user.html

이런 구성을 가지게 됩니다.
이제 webpack.config.js 파일을 생성합니다.

'use strict';

const config = {
  entry: './src/app/app.js',
  output: {
    path: __dirname + '/dist',
    filename: 'app.bundle.js'
  },
  plugins: []
};
module.exports = config;

매우 기초적인 config 파일입니다. app.js 파일을 entry point로 지정하고, compile된 folder를 지정합니다. 이제 bundle된 파일을 사용하도록 index.html을 다음과 같이 수정합니다.

<!doctype html>
<html ng-app="app" lang="en">
<head>
  <meta charset="UTF-8">
  <title>Angular App</title>
</head>
<body>
  <button ui-sref="home">Home</button>
  <button ui-sref="user">User</button>
  <ui-view></ui-view>
  <script src="/app.bundle.js"></script>
</body>
</html>

그리고, webpack에서 각 controller, angular-ui-router의 import를 위해 app.js를 다음과 같이 수정합니다.

var angular = require('angular');
require('angular-ui-router');

angular
  .module('app', [ 'ui.router' ])
  .config(function ($stateProvider, $urlRouterProvider) {
    $stateProvider.state('home', {
      url: '/home',
      templateUrl: 'home.html',
      controller: 'HomeController'
    })
    .state('user', {
      url: '/user',
      templateUrl: 'user.html',
      controller: 'UserController'
    });
  });

require('./controllers/home');
require('./controllers/user');

여기서 중요한 것은 require의 위치입니다. controller들의 등록은 최초 app의 등록이 마쳐진 이후에 등록되어야지 됩니다.

angularJS application (ES2015) + webpack (STEP03)

이제, angularJs application을 es2015 문법으로 구현해보도록 하겠습니다. 개인적으로 가장 욕심이 나는 부분입니다.

es2015 문법을 사용해서 compile 하기 위해서는 babel을 이용해야지 됩니다. babel loader와 babel 2015 loader를 npm을 이용해서 설치합니다.

npm install babel-loader --save-dev
npm install babel-preset-es2015 --save-dev

이제 webpack.config에 babel loader를 등록시켜줍니다. js파일은 모두 babel loader에 의해서 처리가 되도록 설정합니다.

'use strict';
const config = {
  entry: './src/app/app.js',
  output: {
    path: __dirname + '/dist',
    publicPath: '/',
    filename: 'app.bundle.js'
  },
  module: {
    loaders: [{
      test: /\.js$/,
      loader: 'babel',
      query: {
        presets: ['es2015']
      },
      exclude: /node_modules/
    }]
  },
  devtool: 'inline-source-map',
  plugins: []
};
module.exports = config;

이제 app.js, home.js, user.js 파일을 es2015 문법에 맞게 수정을 합니다.

home.js

class HomeController {
  constructor() {
    this.name = 'HomeCtrl';
    console.log('HomeController.init');
  }

  getName() {
    return this.name;
  }

  changeName(name) {
    this.name = name;
    return name;
  }
}

module.exports = HomeController;

user.js

class UserController {
  constructor() {
    this.name = 'UserCtrl';
    console.log('UserController.init');
  }

  getName() {
    return this.name;
  }

  changeName(name) {
    this.name = name;
    return name;
  }
}

module.exports = UserController;

app.js

var angular = require('angular');
require('angular-ui-router');

angular
  .module('app', [ 'ui.router' ])
  .config(function ($stateProvider, $urlRouterProvider) {
    $stateProvider.state('home', {
      url: '/home',
      templateUrl: 'home.html',
      controller: 'HomeController'
    })
    .state('user', {
      url: '/user',
      templateUrl: 'user.html',
      controller: 'UserController'
    });
  })
  .controller('HomeController', require('./controllers/home'))
  .controller('UserController', require('./controllers/user'));

app.js에서 angular에 대한 dependency만을 갖고, 각 controller들은 dependency에서 angular에서 자유로워지는 것을 볼 수 있습니다. 이제 webpack 명령어를 실행해보면 다음과 같은 결과를 볼 수 있습니다.

Hash: f32a9fcb0a0a1f4a0d08
Version: webpack 1.12.14
Time: 1498ms
        Asset     Size  Chunks             Chunk Names
app.bundle.js  3.88 MB       0  [emitted]  main
    + 6 hidden modules

외부 Library 들의 연결 (STEP04)

angularJs application을 개발하면 당연히 기존 library 들과 같이 사용해야지 되는 경우가 많습니다. 먼저 angular-ui-router의 경우에도 마찬가지지요.

대표적인 것들이 일단 lodash와 같은 utility library들이 있을것이고, angularjs에서 rest api 를 call 할때 사용하는 restangular 역시 주로 사용되는 library 입니다.

먼저 loadash에 대해서 알아보도록 하겠습니다. 저는 lodash에 대한 의존이 매우 높은 관계로 전역으로 등록시켜서 사용하고자 합니다.

npm을 통해서 lodash를 설치합니다.

npm install lodash --save

lodash의 경우에는 다른 기타 library들의 dependency가 심한편입니다. 또한 모든 controller에서 전역으로 사용하고싶은 욕심이 있습니다. 그렇다면 webpack에서 미리 등록을 시켜두면 좀 더 사용하기 편할 것 같습니다. browser 환경의 전역 scope로 미리 등록시켜두기 위해서는 webpack에서 제공하는 ProvidePlugin을 이용해서 등록을 시킵니다.

webpack.config.js를 다음과 같이 수정합니다.

const webpack = require('webpack');

const config = {
  entry: './src/app/app.js',
  output: {
    path: __dirname + '/dist',
    publicPath: '/',
    filename: 'app.bundle.js'
  },
  module: {
    loaders: [{
      test: /\.js$/,
      loader: 'babel',
      query: {
        presets: ['es2015']
      },
      exclude: /node_modules/
    }]
  },
  devtool: 'inline-source-map',
  plugins: [
    new webpack.ProvidePlugin({
      'window._': 'lodash',
      '_': 'lodash'
    })
  ]
};
module.exports = config;

lodashwindow 전역과 local 모두에 등록을 시켰습니다. 이제 controller에서는 lodashrequire필요 없이 사용 가능합니다.

home.js

class HomeController {
  constructor() {
    this.name = 'HomeCtrl';
    console.log('HomeController.init');
    console.log(_.random(0, 5));
  }

  getName() {
    return this.name;
  }

  changeName(name) {
    this.name = name;
    return name;
  }
}

module.exports = HomeController;

이제 restangular를 추가할 차례입니다. npm을 이용해서 restangular를 설치하는 일련의 과정은 lodash와 동일합니다. 다만, restangular의 경우에는 angularjs module에 등록을 시켜줘야지 됩니다. app.js는 다음과 같이 변경되어야지 됩니다.

app.js

var angular = require('angular');
require('angular-ui-router');
require('restangular');

angular
  .module('app', [ 'ui.router', 'restangular' ])
  .config(($stateProvider, $urlRouterProvider) => {
    $stateProvider.state('home', {
      url: '/home',
      templateUrl: 'home.html',
      controller: 'HomeController'
    })
    .state('user', {
      url: '/user',
      templateUrl: 'user.html',
      controller: 'UserController'
    });
  })
  .config(RestangularProvider => {
    console.log(RestangularProvider);
  })
  .controller('HomeController', require('./controllers/home'))
  .controller('UserController', require('./controllers/user'));

이제 controller에서는 생성자에서 Restangular를 inject해서 사용가능합니다.

home.js

class HomeController {
  constructor(Restangular) {
    this.Restangular = Restangular;
    this.name = 'HomeCtrl';
    console.log('HomeController.init');
    console.log(_.random(0, 5));

    this.Restangular.all('').get('data.json').then(result => {
      console.log(result);
    });
  }

  getName() {
    return this.name;
  }

  changeName(name) {
    this.name = name;
    return name;
  }
}

module.exports = HomeController;

Service, Directive, filter 추가 (Step 05)

angularJs의 주요 컨셉중 하나인 service, directive, filter의 경우, 기존 controller추가와 동일합니다. 다만 es2015 형식으로 코딩을 하는 경우, class를 이용해서 작성하는 것만 좀 유의해주면 될 것 같습니다.

userservice.js - service

class UserService {
  constructor(Restangular) {
    this.Restangular = Restangular;
  }

  getUserName() {
    return 'username';
  }

  getUserInfo() {
    this.Restangular.all('').get('info.json').then(info => {
      console.log(info);
    });
  }
}
module.exports = UserService;

userfilter.js - filter

function filteringUser () {
  return function (users) {
    return _.filter(users, function (user) {
      return user.name.indexOf('7') >= 0;
    });
  };
}
module.exports = filteringUser;

namedbutton.js - directive

function postLink(scope, element, attrs) {
  console.log(attrs);
  scope.ok = function () {
    alert(scope.name);
  };
}

function template(element, attrs) {
  console.log(attrs);
  return '<button ng-click="ok()">This is named button 확인</button>';
}

module.exports = function NameButtonDef() {
  return {
    restrict: 'E',
    scope: {
      name: '='
    },
    link: postLink,
    template: template
  };
};

app.js

var angular = require('angular');
require('angular-ui-router');
require('restangular');

angular
  .module('app', [ 'ui.router', 'restangular' ])
  .config(($stateProvider, $urlRouterProvider) => {
    $stateProvider.state('home', {
      url: '/home',
      templateUrl: 'home.html',
      controller: 'HomeController',
      controllerAs: 'model'
    })
    .state('user', {
      url: '/user',
      templateUrl: 'user.html',
      controller: 'UserController',
      controllerAs: 'model'
    });
  })
  .config(RestangularProvider => {
    console.log(RestangularProvider);
  })
  .controller('HomeController', require('./controllers/home'))
  .controller('UserController', require('./controllers/user'))
  .service('UserService', require('./services/userservice'))
  .filter('user', require('./filters/userfilter'))
  .directive('namedButton', require('./directives/namedbutton'));

controller와 별 차이가 없는 코드 패턴을 보입니다. 개인적으로는 최고의 장점이 service, directive, filter 코드 자체에 angular dependency가 없어지는 것이 장점인것 같습니다.

jqLite 대신 jquery 사용하기 (step06)

기본적으로 angular.element의 경우, jquery가 없는 경우에는 angularjs내부에 include 되어 있는 jqlite를 이용해서 element handling을 지원합니다. 그런데 angular.element의 경우 jquery selector보다 매우 기능이 빈약합니다. bootstrap도 사용할 가능성도 높고, 그냥 기본적으로 jquery를 포함해서 사용하게 되는 경우가 더 일반적입니다.

그럼 angular.element에서 jquery를 사용하도록 할려면 어떻게 해야지 될까요? 예전 방법대로라면 다음과 같이 해주면 됩니다.

<script src="jquery.js"></script>
<script src="angular.js"></script>

네. angular보다 jquery를 먼저 선언해주면 모든것이 해결되었습니다. 그럼 비슷한 구문으로 실행해보도록 하겠습니다.

    require('jquery');
    const angular = require('angular');
    console.log(angular.element('div'));

이러면 다음과 같은 에러가 발생합니다.

angular.js:13424 Error: [jqLite:nosel] Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element
http://errors.angularjs.org/1.5.3/jqLite/nosel

네. jqLite를 사용하고 있지, jquery를 사용하고 있지 못하게 됩니다.

이런 경우에는 expose-loaderimport-loader를 이용해서 각 module에서 jquery등을 자동으로 내부내에서 import 시켜줘야지 됩니다.

npm을 이용해서 다음 module들을 설치합니다.

npm install imports-loader --dev-save
npm install expose-loader --dev-save

그리고 webpack.config.js의 module 부분을 다음과 같이 변경합니다.

  module: {
    loaders: [{
      test: /\/angular\.js$/,
      loader: 'imports?jQuery=jquery'
    }, {
      test: /\/jquery.js$/,
      loader: 'expose?jQuery'
    }, {
      test: /\.js$/,
      loader: 'babel',
      query: {
        presets: ['es2015']
      },
      exclude: /node_modules/
    }]
  },

코드 해석은 다음과 같습니다. jquery가 요청되는 경우 window.jQuery로 등록을 시키는 구문이 두번쩨 expose 구문입니다. 그리고 angular.js가 로드될 때, jQuery의 경우 jquery를 import 해서 사용하도록 강제하는 것입니다.

위의 두 구문은 매우 중요합니다. jquery 기반의 module들을 사용할 때, 안되는 이유의 대부분이 window.jQuery, window.$, window.jquery가 등록이 되지 않을 때 주로 나타나게 됩니다.

이제 저 구문을 넣어주고 webpack 을 실행해주면 다음과 같은 결과를 볼 수 있습니다.

[div#window-resizer-tooltip, div#feedly-mini, prevObject: jQuery.fn.init[1], context: document, selector: "div"]0: div#window-resizer-tooltip1: div#feedly-minicontext: documentlength: 2prevObject: jQuery.fn.init[1]selector: "div"__proto__: Object[0]

Summary

angularjs2가 나오는 이때에 유행에 어울리지 않는 angularjs와 최신 유행인 webpack간의 결합에 대해서 알아봤습니다.

위 소스는 모두 github에 올려두었고, step별로 tag를 달아두었습니다.
(https://github.com/xyzlast/webpack-angular1-step)

  1. angularjs1 application을 webpack으로 묶는 과정에 대해서 알아봤습니다.
  2. jquery 기반의 library들을 어떻게 결합할 수 있는지에 대해서 알아봤습니다.

css-loader등을 잘 설명하신 글이 있으니, 참고하셔서 보시면 좀 더 쉬울 것 같습니다.
(http://hyunseob.github.io/2016/04/03/webpack-practical-guide/index.html)


Posted by Y2K
,

querydsl 4.x대의 경우, 다음 링크 글을 참고해주세요.

http://netframework.tistory.com/entry/gradle-%EC%A0%95%EB%A6%AC-queryDSL-code-generate-v-4x


기존 Gradle을 이용한 개발환경구성글에서 다음이 변경되었습니다.

Gradle 2.0의 지원

Gradle 2.0에 호환되도록 Project를 변경했습니다. Gradle 2.0의 가장 큰 변화는 groovy 문법의 변경으로 인한 += operator가 변경된 점입니다. 기존 sourceSet에 추가 path를 할 때, 코드는

sourceDir += file('src/main/generated')

로 구성되었지만, groovy의 변경으로 += operator는 모두 같은 형태의 객체들에서만 사용되도록 변경되었습니다. 그래서 이 부분을 다음과 같이 수정하면 됩니다.

sourceDir += [ file('src/main/generated') ]

Java 8의 지원

java 8이 지원될 수 있도록 souceCompatibility와 targetCompatibility가 모두 변경되었습니다.

build.gradle의 간소화

기존에는 base.gradle, domain.gradle과 같이 여러개의 build.gradle 파일로 나뉜 상태를 간소화시켰습니다.
build.gradle과 각 module에서 필요한 gradle 파일을 각기 나누어 처리하도록 변경하였습니다.


web deploy container의 변경

기존 gradle tomcat plugin에서부터 gretty로 변경했습니다. gretty의 특징은 다음과 같습니다.

  • tomcat/jetty 지원
  • tomcat8, jetty9 지원
  • hot-deploy의 지원
  • jvmargs의 지원

무엇보다도 기존 gradle tomcat plugin보다 사용이 편리한 것이 장점입니다. web server를 실행시키는 명령어는 다음과 같습니다.

gradle :privateWeb:jettyRun   //Jetty9을 이용한 web server 실행
gradle :privateWeb:tomcatRun  //tomcat8을 이용한 web server 실행

git hub에 코드를 올려뒀습니다. 모두들 Happy coding~

https://github.com/xyzlast/gradle_multi_project_test

Posted by Y2K
,

최신 Gradle 2.0으로 업데이트 한 내용이 존재합니다.


전에 정리한 적이 있는데, 조금 글을 다듬고 정리할 필요성이 있어서 다시 옮깁니다.
먼저, 개발되는 Project는 다음과 같은 구조를 갖습니다.

rootProject
-- domainSubProject (Spring Data JPA + queryDSL)
-- webAppSubProject (Spring Web MVC)

위와 같은 Project는 개발의 편의를 위해서 다음 두 조건을 만족해야지 됩니다.

  1. queryDSL을 사용하기 위한 Q-Entity를 생성이 compileJava task전에 수행되어야지 됩니다.
  2. web application 개발을 위해, tomcat을 실행시킬 수 있어야합니다.
  3. 개발된 web application을 test 환경 또는 product 환경에 배포가 가능해야지 됩니다.

root project

root project는 sub project들의 공통 설정이 필요합니다.

  1. build시에 필요한 plugin의 repository를 설정합니다.
  2. sub project들에서 사용될 공통 dependency들을 설정합니다. (sub project들의 build.gradle 이 너무 길어지는 것을 막을 수 있습니다.)
공통 plugin 추가
  • maven의 POM과 동일한 provided과 optional의 사용을 위해 spring prop plugin을 추가합니다.
  • subprojects들에 필요한 plugin들을 모두 추가합니다. (저는 java와 groovy, idea, eclipse 등을 추가했습니다.)
  • source java version과 target java version을 정해줍니다.
  • source code의 Encoding을 정해줍니다.
  • SubProject에서 기본적으로 사용될 dependency들을 모두 적어줍니다.

build.gradle (root project)

apply plugin: 'base'

// spring prop plugin 추가
buildscript {
    repositories {
        maven { url 'http://repo.springsource.org/plugins-release' }
    }
    dependencies {
        classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.5'
    }
}

subprojects {
    // Plugin 설정, 만약에 code에 대한 static analysis가 필요한 경우에 이곳에 설정.
    apply plugin: 'java'
    apply plugin: 'eclipse'
    apply plugin: 'eclipse-wtp'
    apply plugin: 'idea'
    apply plugin: 'groovy'
    apply plugin: 'propdeps'
    apply plugin: 'propdeps-maven'
    apply plugin: 'propdeps-idea'
    apply plugin: 'propdeps-eclipse'

    // 기본적으로 사용할 repository들을 정의
    repositories {
        mavenCentral()
        maven { url "http://oss.sonatype.org/content/repositories/snapshots/" }
        maven { url "http://192.168.13.209:8080/nexus/content/repositories/releases" }
    }

    dependencies {
        def springVersion = "4.0.1.RELEASE"

        compile 'org.slf4j:slf4j-api:1.7.5'
        compile "org.springframework:spring-context:${springVersion}"
        compile "org.springframework:spring-aspects:${springVersion}"
        compile "org.springframework:spring-jdbc:${springVersion}"
        compile "org.springframework:spring-context-support:${springVersion}"

        compile 'mysql:mysql-connector-java:5.1.27'
        compile 'com.jolbox:bonecp:0.8.0.RELEASE'
        compile 'com.google.guava:guava:15.0'
        compile 'org.aspectj:aspectjrt:1.7.4'
        compile 'org.aspectj:aspectjtools:1.7.4'
        compile 'org.aspectj:aspectjweaver:1.7.4'

        testCompile 'org.springframework:spring-test:4.0.0.RELEASE'
        testCompile "junit:junit:4.11"

        groovy "org.codehaus.groovy:groovy-all:2.1.6"
        testCompile "org.spockframework:spock-core:1.0-groovy-2.0-SNAPSHOT"
        testCompile "org.spockframework:spock-spring:1.0-groovy-2.0-SNAPSHOT"
    }

    // source, target compiler version 결정
    sourceCompatibility = 1.8
    targetCompatibility = 1.8

    // source code encoding
    [compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
}
domain project
  • domain project는 queryDsl의 Q-Entity들의 생성이 필요합니다.
  • compileJava에 대한 Q-Entity 생성 Task의 Depend가 잡혀있으면 사용하기 편합니다.

build.gradle (domain project)

dependencies {
    def springVersion = "4.0.1.RELEASE"

    compile 'org.hibernate:hibernate-core:4.3.1.Final'
    compile 'org.hibernate:hibernate-entitymanager:4.3.1.Final'
    compile "org.springframework:spring-orm:${springVersion}"

    def queryDSL = '3.2.4'
    compile("com.mysema.querydsl:querydsl-core:$queryDSL")
    compile("com.mysema.querydsl:querydsl-jpa:$queryDSL")
    compile("com.mysema.querydsl:querydsl-sql:$queryDSL")
    provided("com.mysema.querydsl:querydsl-apt:$queryDSL") {
        exclude group: 'com.google.guava'
    }
    compile 'org.springframework.data:spring-data-jpa:1.5.0.RELEASE'
}

sourceSets {
    generated {
        java {
            srcDirs = ['src/main/generated']
        }
    }
}

// QEntity 생성 task
task generateQueryDSL(type: JavaCompile, group: 'build', description: 'Generates the QueryDSL query types') {
    source = sourceSets.main.java
    classpath = configurations.compile + configurations.provided
    options.compilerArgs = [
            "-proc:only",
            "-processor", "com.mysema.query.apt.jpa.JPAAnnotationProcessor"
    ]
    destinationDir = sourceSets.generated.java.srcDirs.iterator().next()
}

// compileJava task에 dependency를 걸어줍니다.
compileJava {
    dependsOn generateQueryDSL
    // compile target에 generated된 QClass들의 위치를 추가.
    source sourceSets.generated.java.srcDirs.iterator().next()
}

compileGeneratedJava {
    dependsOn generateQueryDSL
    options.warnings = false
    classpath += sourceSets.main.runtimeClasspath
}

clean {
    delete sourceSets.generated.java.srcDirs
}

idea {
    module {
        sourceDirs += file('src/main/generated')
    }
}
webapplication project

마지막으로 web application project입니다. 이는 조건이 조금 더 많습니다.

  • tomcat을 실행시켜 local 개발 환경에서 사용할 수 있어야지 됩니다.
  • 외부 테스트 또는 운영환경의 tomcat에 배포가 가능해야지 됩니다.

위 조건을 만족하기 위해서 gradle의 tomcat plugin과 cargo plugin을 이용합니다.

plugin에 대한 자료들은 다음 url에서 보다 많은 자료를 볼 수 있습니다.

tomcat plugin > https://github.com/bmuschko/gradle-tomcat-plugin
cargo plugin > https://github.com/bmuschko/gradle-cargo-plugin

build.gradle (webapp project)

apply plugin: 'war'
apply plugin: 'tomcat'
apply plugin: 'cargo'

// tomcat과 cargo plugin에 대한 repository 설정입니다.
buildscript {
    repositories {
        mavenCentral()
        jcenter()
    }

    dependencies {
        classpath 'org.gradle.api.plugins:gradle-tomcat-plugin:1.0'
        classpath 'org.gradle.api.plugins:gradle-cargo-plugin:1.4'
    }
}


dependencies {
    // tomcat plugin 설정입니다.
    String tomcatVersion = '7.0.47'
    tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}"
    tomcat "org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}"
    tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}") {
        exclude group: 'org.eclipse.jdt.core.compiler', module: 'ecj'
    }
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
    providedCompile 'javax.websocket:javax.websocket-api:1.0'
    providedCompile 'javax.servlet:jsp-api:2.0'
    providedCompile "org.apache.tomcat:tomcat-servlet-api:${tomcatVersion}"

    // cargo에 대한 설정입니다.
    def cargoVersion = '1.4.5'
    cargo "org.codehaus.cargo:cargo-core-uberjar:$cargoVersion",
            "org.codehaus.cargo:cargo-ant:$cargoVersion"


    def springVersion = "4.0.1.RELEASE"
    compile "org.springframework:spring-webmvc:${springVersion}"
    compile 'jstl:jstl:1.2'
    compile 'org.apache.tiles:tiles-jsp:3.0.3'

    compile 'org.slf4j:slf4j-api:1.7.6'
    compile 'org.slf4j:jcl-over-slf4j:1.7.6'

    compile 'ch.qos.logback:logback-classic:1.0.13'
    compile 'ch.qos.logback:logback-core:1.0.13'

    compile 'org.apache.velocity:velocity:1.7'
    compile 'org.freemarker:freemarker:2.3.20'
    compile 'com.ctlok:spring-webmvc-rythm:1.4.4'
    compile project(':bookstoreHibernate')
}

// tomcarRun을 실행시키기 위해서 war에 대한 dependency를 주입합니다.
tomcatRun {
    contextPath = ""
    URIEncoding = 'UTF-8'
    dependsOn war
}

tomcatRunWar {
    dependsOn war
}

// cargo를 이용한 배포를 위해서 war에 대한 dependency를 주입합니다.
cargoRedeployRemote {
    dependsOn war
}

cargoDeployRemote {
    dependsOn war
}

cargo {
    containerId = 'tomcat7x'
    port = 8080

    deployable {
        context = "${project.name}"
    }

    // remoteDeploy 되는 target의 tomcat 정보
    remote {
        hostname = '192.168.13.209'
        username = 'ykyoon'
        password = 'qwer12#$'
    }
}

bower를 이용한 javascript dependency

web application에서의 외부 javascript dependency를 사용하는 방법입니다. bower를 이용하는 경우, 외부에서 javascript에 대한 source code를 모두 다운받고 compile된 javascript를 dist에 저장하게 됩니다.

그런데, 우리의 web application은 dist에 저장된 특정 파일만을 사용하게 됩니다. 그럼 이 dist에 있는 file을 최종적으로 배포할 webapp folder에 넣어줘야지 됩니다. 이를 위해서 개발된 것이 bower-installer 입니다. 그런데 bower-installer의 경우에는 윈도우즈에서 동작이 정상적이지 않습니다. 아니 실행이 되지 않습니다.; 그래서 bower-installer와 동일한 동작을 하는 task를 만들어봤습니다.

먼저, bower-installer는 bower.json의 install property에 설정합니다. jquery와 bootstrap에 대한 dependency를 설정한 bower.json 입니다.

bower.json

{
    "name" : "bookstore-web",
    "version" : "0.0.0.1",
    "dependencies" : {
        "jquery" : "1.11.0",
        "bootstrap" : "3.1.1"
    },
    "install" : {
        "path" : {
            "css" : "src/main/webapp/lib/css",
            "js" : "src/main/webapp/lib/js",
            "eot" : "src/main/webapp/lib/fonts",
            "svg" : "src/main/webapp/lib/fonts",
            "ttf" : "src/main/webapp/lib/fonts",
            "woff" : "src/main/webapp/lib/fonts",
            "map" : "src/main/webapp/lib/js"
        },
        "sources" : {
            "jquery" : [
                    "bower_components/jquery/dist/jquery.min.js",
                    "bower_components/jquery/dist/jquery.min.map"
                ],
            "bootstrap" : [
                    "bower_components/bootstrap/dist/css/bootstrap.min.css",
                    "bower_components/bootstrap/dist/css/bootstrap-theme.min.css",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.eot",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.svg",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff",
                    "bower_components/bootstrap/dist/js/bootstrap.min.js"
                ]
        }
    }
}

위의 install property에 지정된 js와 css들을 옮기는 task는 다음과 같이 설정할 수 있습니다. war task에 dependency를 주입해서 위의 tomcatRun이나 cargoRedeployRemote 등에서도 사용할 수 있습니다.

import org.apache.tools.ant.taskdefs.condition.Os
import groovy.json.JsonSlurper
task bowerInstall(type:Exec, description : 'copy js files dependencies that is defined in Bower.js') {

    if(Os.isFamily(Os.FAMILY_WINDOWS)) {
        commandLine 'cmd', 'bower', 'install'
    } else {
        commandLine 'bower', 'install'
    }

    def jsonHandler = new JsonSlurper()
    def jsonFile = file("bower.json")
    def conf = jsonHandler.parseText(jsonFile.getText("UTF-8"))
    def pathMap = [:]

    conf.install.path.each {
        pathMap.put(it.key, it.value)
    }

    conf.install.sources.each {
        it.value.each { f ->
            def sourceFile = file(f)
            String sourceName = sourceFile.name
            int dotPos = sourceName.lastIndexOf(".")
            String ext = sourceName.substring(dotPos + 1, sourceName.length())
            if(pathMap.containsKey(ext)) {
                copy {
                    from f
                    into pathMap.get(ext)
                }
            }
        }
    }
}

war {
    dependsOn bowerInstall
}


위 정리된 내용을 github에 공유합니다. 아래 주소에서 git clone 하시면 됩니다. ^^

 

 https://github.com/xyzlast/study-spring-bookstore.git



Posted by Y2K
,

Bower는 maven, gradle을 이용한 java에서의 open source library의 버젼관리를 javascript client library에 옮겨온 개념입니다.

bower 소개

Bower는 maven, gradle과 비슷하게 bower.json과 .bowerrc 두개의 파일로 설정이 관리가 됩니다.

  • .bowerrc : Bower에서 source와 dist를 다운받을 위치를 지정합니다.
  • bower.json : Bower에서 다운받을 외부 component의 버젼 및 Project의 name, version을 지정합니다. 이는 maven, gradle에서 project와 비슷한 값을 지정하게 됩니다.

.bowerrc

{
  "directory": "components"
}

bower.json

{
  "name": "my-project",
  "version": "0.0.1",
  "dependencies": {
    "modernizr": "~2.6.2",
    "bootstrap": "~2.2.2",
    "angular-strap": "~0.7.0",
    "angular-ui" : "0.4.0",
    "ngInfiniteScroll" : "1.0.0",
    "angular-underscore" : "",
    "underscore" : "",
    "angular-bootstrap" : "~0.2.0",
    "font-awesome" : "3.0.2",
    "emoji" : "0.1.2"
  }
}

bower의 설치

bower는 node.js를 이용해서 작성되어있습니다. 먼저 node.js를 설치하는 것이 필요합니다. node.js의 설치방법은 너무나 많은 곳들에서 소개가 되고 있기 때문에 넘어가기로 하겠습니다.
Bower는 전역으로 사용되는 application이기 때문에, npm install -g를 이용해서 설치를 해야지 됩니다. 최종적으로 사용하기 위해서는 bower-installer 역시 같이 설치해주는 것이 좋습니다.

sudo npm install -g bower
sudo npm install -g bower-installer

bower의 활용

기본적으로 mvnrepository와 같이 사용할 library를 http://bower.io/search/ 에서 검색해서 찾는 것이 가능합니다. bower를 이용해서 library를 설치하기 위해서는 다음 command를 실행하면 됩니다.

bower install jquery

위는 jquery를 지정해서 설치하게 됩니다. 버젼이 따로 적혀있지 않는 경우, 가장 최신의 버젼을 다운받아 사용하게 됩니다. 특정하게 버젼을 지정하기 위해서는 다음과 같이 작성할 수 있습니다.

bower install jquery#1.11.0

설치와 bower.json 파일 기록을 동시에 할 수 있습니다.

bower install jquery#1.11.0 --save
bower install jquery#1.11.0 -s

bower-installer

bower를 통해서 component들을 설치하게 되면 source 코드와 min file 모두가 다운받게 됩니다. 그렇지만, 사용하게 될 component 들은 대다수 min file들만이 사용되게 됩니다. 이를 위해 설치한 component들의 min file 들만을 사용할 위치로 copy 해주는 tool이 bower-installer 입니다. bower-install는 bower.json에서 설정하며, 다음과 같이 설정할 수 있습니다.

    "install" : {
        "path" : {
            //"파일 확장자" : "copy될 위치"
            "css" : "src/main/webapp/lib/css",
            "js" : "src/main/webapp/lib/js",
            "woff" : "src/main/webapp/lib/fonts"
        },
        "sources" : {
            "component 이름" : [
                "copy 할 file 이름"
            ]
        }
    }

bower.json을 수정한 후, bower-installer를 실행하면 위치에 맞게 file이 모두 copy되는 것을 볼 수 있습니다.

bower와 gradle java war project의 연결

gradle을 이용한 war project의 경우, src/main/webapp에 사용되는 모든 code들과 javascript가 위치하게 됩니다. 여기 위치에 bower_component를 모두 다 다운받아서 처리하게 되면 url이 지저분해지는 경향이 있습니다. 이를 개발 방향에 맞게 정리해주는 것이 필요합니다. 이 때, bower-installer를 사용하면 원하는 directory 구조를 만들 수 있습니다.

제가 개인적으로 생각하는 좋은 구조는 다음과 같습니다.

project
- src
    - main
        - java
        - resources
        - webapp
    - test
        - java
        - resources
 .bowerrc
 .bower.json
 build.gradle
 setting.gradle

.bowerrc

{
    "directory" : "bower_components"
}

bower.json (bootstrap과 jquery를 설치한 상태입니다.)

{
    "name" : "bookstore-web",
    "version" : "0.0.0.1",
    "dependencies" : {
        "jquery" : "1.11.0",
        "bootstrap" : "3.1.1"
    },
    "install" : {
        "path" : {
            "css" : "src/main/webapp/lib/css",
            "js" : "src/main/webapp/lib/js",
            "eot" : "src/main/webapp/lib/fonts",
            "svg" : "src/main/webapp/lib/fonts",
            "ttf" : "src/main/webapp/lib/fonts",
            "woff" : "src/main/webapp/lib/fonts"
        },
        "sources" : {
            "jquery" : [
                    "bower_components/jquery/dist/jquery.min.js"
                ],
            "bootstrap" : [
                    "bower_components/bootstrap/dist/css/bootstrap.min.css",
                    "bower_components/bootstrap/dist/css/bootstrap-theme.min.css",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.eot",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.svg",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf",
                    "bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff",
                    "bower_components/bootstrap/dist/js/bootstrap.min.js"
                ]
        }
    }
}

위와 같이 설정해주면 bower-installer를 실행하면 src/main/webapp/lib 안에 모든 javascript component를 위치할 수 있게 됩니다. 그런데, 이 과정 역시 gradle에 추가하는 것이 보다 더 사용하기 편합니다. 다음과 같이 gradle task를 추가합니다.

import org.apache.tools.ant.taskdefs.condition.Os
task bowerInstaller(type:Exec) {
    if(Os.isFamily(Os.FAMILY_WINDOWS)) {
        commandLine 'cmd', 'bower-installer'
    } else {
        commandLine 'bower-installer'
    }
}

이제 gradle bowerInstaller commmand를 통해 bower-installer를 실행할 수 있습니다. bowerInstaller를 따로 실행시켜줘도 좋지만, war로 배포될 때 자동으로 포함될 수 있도록 war task에 dependency를 추가하도록 합니다.

war {
    dependsOn bowerInstaller
}

이제 gradle war를 통해서도 모든 component들을 같이 관리할 수 있습니다. gradle의 놀라운 확장성에 대해서 다시 한번 감탄하게 됩니다. ^^

마치면서

bower는 java에서의 maven, gradle과 같이 dependencies에 대한 관리 툴입니다. 따라서 기존 관행처럼 모든 javascript를 SCM에 올릴 필요가 더이상 없어집니다. 버젼 관리와 같이 개발되는 application에서 사용되고 있는 component에 대한 관리와 같이 maven, gradle을 사용할 때와 같은 매우 멋진 작업들이 가능하게 됩니다. 모두 bower를 한번 써봅시다. ^^

Posted by Y2K
,
javascript에서 자주 class화 시켰으면 좋을것 같은데.. 하는 일이 자주 벌어진다.
일단 기본 목적이 화면에 표시되는 방법을 제어하는 경우가 더 많기 때문에, 기존 WinForm의 객체들처럼 각각 만들어져있다면 사용하기에 정말 편할 것 같아서 class와 상속 들에 대해서 알아보게 되었다.

먼저, 기본적으로 javascript에서는 class라는 키워드는 존재하지 않는다. function 키워드를 이용해서 객체를 선언 하게 된다.

1. class의 선언

function Product() {    this.name = '';    this.price = 0;    this.SetValues = function(name, price) {        this.name = name;        this.price = price;    };            this.ShowName = function(id) {        this.ShowAlert();        $('#' + id).html('Product' + this.name);    };        this.ShowAlert = function() {        console.debug('this is product message ' + this.name);    };}

class의 선언은 매우 단순한것이. 일반적인 함수의 형태를 취하면서 만들어지게 된다. prototype을 선언해서 만드는 법도 가능하지만, 코드의 가독성이 이게 더 편한 것 같아서. 이 방법을 주로 사용하는 것이 더 나아보인다.
private method나 private property의 경우에는 var 키워드를 이용해서 만들어주고, public의 경우 this 키워드를 이용해서 선언할 수 있다.

2. class의 상속

function KrProduct() {    Product.call(this);    this.ShowAlert = function() {        console.debug('this is krProduct message ' + this.name);            };}

call method를 이용해서 class를 상속한다.
override 를 하고 싶은 경우, 기존의 method를 동일하게 재 선언해주면 된다.
주의점은 javascript의 객체를 만들때는 protected가 없다는 것이다. 상속된 객체에서 절대로 private method나 property는 사용하지 못한다. 이를 고려해서 만들어줘야지 된다. (객체의 캡슐화에는 매우 불리한 것 같다.)

3. 실행 결과.
$(function() {    $('#testButton').click(function() {        var product = new Product();        var product2 = new KrProduct();        var product3 = new KrProduct();        product.SetValues("Product Name", 0);        product2.SetValues("Product2 Name", 1);        product3.SetValues("Product3 Name", 2);         product.ShowName('testDisplay');        product2.ShowName('testDisplay2');        product3.ShowName('testDisplay3');    });})


override 시킨 method가 차례대로 실행되는 것을 알 수 있다.

Posted by Y2K
,
모든 입력은 위조될 수 있다. - 사용자의 입력을 신뢰하지 말아라.

변조 가능한 모든 데이터들:
  • GET QueryString
  • POST로 전송 가능한 Data
  • Cookie
  • HTTP Header에 저장된 데이터
XSS(Cross-site scripting)
: Web Application에 피해를 입히는 가장 유명하고 널리 알려진 방법
Input Message를 이용해서 Web Page에 공격자의 Form 또는 Javascript를 삽입하여 공격하는 방법

* 대응책 : 사용자가 제공한 모든 데이터는 인코딩을 통해 출력한다.

* Html.Encode(string message)를 통해 ASP .NET MVC에서 가능하다. MVC2에서는 <%: %>으로 간단히 표현 가능하다.
* ASP .NET에서 제공되는 ValidationRequest를 이용하는 방법
 - HTML, Javascript와 유사한 모든 입력을 차단한다.
 - 사용자의 입력이 매우 제한되기 때문에 추천되지 않는 방법이다.

Session Hijacking
: Session ID cookie(ASP.NET_ SessionID로 만들어진다.)를 저장하고, 자체 브라우저를 이용해서 신원을 위장 가능하다.

* 대응책
  - Client IP를 Session의 값에 같이 저장해서, Session을 발급한 Client를 재확인하는 절차를 거친다.
  - HttpOnly flag를 설정한다. 이 경우, Cookie를 javascript를 이용해서 Hijacking 하는 것이 불가능하게 된다.

CSRF
: 사용자가 정상적인 로그인을 거친 이후에 다른 사이트에 Session이 유지된 상태로 이동한 이후 타 사이트에서 값을 넘기는 것으로 사용자의 정보를 훔쳐갈 수 있다.

* 대응책
  - 사용자에게 특화된 토큰이 보안적인 요청안에 포함되도록 한다. ASP .NET MVC에서는 이러한 기법을 이미 사용하고 있다.

  <%using(Html.BeginForm()) { %>
      <%= Html.AntiForgeryToken() %>
  <%}%>

  [AcceptVerbs(HttpVerbs.Post)][ValidateAntiForgeryToken]
  public ActionResult SubmitUpdate()

SQL Injection
: SQL Query문을 Query String 또는 POST 데이터에 넣어서 데이터를 위변조한다.

* 대응책
  - 입력을 Encoding해서 방어
  - 매개변수를 사용하는 Query를 이용해서 방어
  - ORM Mapping을 이용해서 방어 (LINQ SQL, ASP .NET Entity Framework, NHibernate etc..)


Posted by Y2K
,
http://www.okjsp.pe.kr/seq/140133

jQuery의 경우 내가 이번년도에 본 내용중에서 가장 쇼킹한 녀석이였는데...
강의마저 나를 쇼킹하게 만들고 있다. -_-
Posted by Y2K
,
회사에서 난생처음 linux에서의 web 작업을 하는 도중에 php를 사용하면 더 편할 것 같은 예감이 들어서, php로 뚝딱뚝딱. 

linux의 언어설정은 unicode로 되어있고, php의 언어설정은 되어있지 않아서, 한글이 깨지는 문제가 계속해서 발생해서 조금 고생한 듯. 기종간 최고의 문제는 역시 언어설정인것을 다시 한번 느끼게 되었다. 

#메일 보내기
<?
function encode_2047($subject) {
    return '=?euc-kr?b?'.base64_encode($subject).'?=';
}

function customer_sendmail($email_str, $userName, $subject, $message)
{
    mb_internal_encoding('EUC-KR'); 

    $from_name = $userName;
    $from_name = encode_2047(iconv("UTF-8","EUC-KR",$from_name));

    $subject = iconv("UTF-8","EUC-KR",$subject);
    $message = iconv("UTF-8","EUC-KR",$message);

    $headers = 'MIME-Version: 1.0' . "\r\n";
    $headers .= 'X-Mailer: PHP' . "\r\n";
    $headers .=  "Content-Type: text/html; charset='ks_c_5601-1987'\r\n";
    $headers .= 'From: '.$from_name.' < test@smtp.co.kr >'. "\r\n";

    // 메일 보내기
    $result = mb_send_mail($email_str, $subject, $message, $headers) ;
    return $result; 
}

// 멋진 코드. Post로 보내진 모든 데이터를 $Name 형태로 모두 만들어준다. 
foreach($_POST as $key=>$value)
{
       $$key=$value;
}

$to = "to@smpt.co.kr";
$subject = "[코로케이션] 시스템 무료 점검 신청_" . $companyName;

$body="
====================================
* 회사 이름 : $companyName
* 사용자 이름 : $userName
* 연락처 : $phoneNumber1-$phoneNumber2-$phoneNumber3
* 운영체제(os) 버젼 : $osVersion
* DB 버젼 : $dbVersion
* 서버 용도 : $useService
* 기타 점검 요청 사항 : $etc   
=====================================
";
customer_sendmail($to, $userName, $subject,$body);
?>


#메일 & 첨부파일 보내기
<?php
function encode_2047($subject) {
    return '=?euc-kr?b?'.base64_encode($subject).'?=';
}
foreach($_POST as $key=>$value)
{
       $$key=$value;
}

if($_SERVER['REQUEST_METHOD']=="POST"){
mb_internal_encoding('EUC-KR'); 
  $to = "to@smtp.co.kr";
  $subject=encode_2047(iconv("UTF-8","EUC-KR","[코로케이션]시스템 무료 점검 사전정보_".$companyName));
$from = iconv("UTF-8","EUC-KR",$companyName);
  $mime_boundary="==Multipart_Boundary_x".md5(mt_rand())."x";
  // 파일 이름 얻어오기. 넘기는 페이지의 input="file"의 name 속성으로 넘엉
  $tmp_name = $_FILES['filename']['tmp_name'];
  $type = $_FILES['filename']['type'];
  $name = $_FILES['filename']['name'];
  $size = $_FILES['filename']['size'];

  $headers = 'MIME-Version: 1.0' . "\r\n";
  $headers .= 'X-Mailer: PHP' . "\r\n";
  $headers .=  "Content-Type: text/html; charset='ks_c_5601-1987'\r\n";
  $headers .= 'From: '.$from."<test@smtp.co.kr>\r\n";
$engineer = "아니오";
if($rdEngineer == "true") {
$engineer = "예";
}
$protect = "아니오";
if($rdProtect == "true") {
$protect = "예";
}
$duplication = "아니오";
if($rdDuplication == "true") {
$duplication = "예";
}
$backup = "아니오";
if($rdBackup == "true") {
$backup = "예";
}
$message = "";
if($rdEngineer == "true") {
 $message = "
  ==================================== <br/>
* 회사 이름 : $companyName<br/>
* 서비스 유형 : $serviceType<br/>
* 구매 날짜 : $buyDate<br/>
* 전문 엔지니어 관리 : $engineer<br/>
* 보안 설정 : $protect<br/>
* 이중화 구성 : $duplication<br/>
* 백업 구성 : $backup<br/>
* 기타 문의 사항 : $etc<br/>
====================================
";
}
else {
$message = "
  ==================================== <br/>
* 회사 이름 : $companyName<br/>
* 서비스 유형 : $serviceType<br/>
* 구매 날짜 : $buyDate<br/>
* 전문 엔지니어 관리 : $engineer<br/>
- 서버 관리 : $noEngineer<br/>
* 보안 설정 : $protect<br/>
* 이중화 구성 : $duplication<br/>
* 백업 구성 : $backup<br/>
* 기타 문의 사항 : $etc<br/>
====================================
";
}
$message = iconv("UTF-8","EUC-KR",$message);
if (file_exists($tmp_name)){
if(is_uploaded_file($tmp_name)){
$file = fopen($tmp_name,'rb');
$data = fread($file,filesize($tmp_name));
fclose($file);
$data = chunk_split(base64_encode($data));
    }
$headers = "From: $from\r\n" .
         "MIME-Version: 1.0\r\n" .
         "Content-Type: multipart/mixed;\r\n" .
         " boundary=\"{$mime_boundary}\"";
$message = "This is a multi-part message in MIME format.\n\n" .
         "--{$mime_boundary}\n" .
         "Content-Type: text/html; charset='ks_c_5601-1987'\n\n" .
         $message . "\n\n";
$message .= "--{$mime_boundary}\n" .
         "Content-Type: {$type};\n" .
         " name=\"{$name}\"\n" .
         //"Content-Disposition: attachment;\n" .
         //" filename=\"{$fileatt_name}\"\n" .
         "Content-Transfer-Encoding: base64\n\n" .
         $data . "\n\n" .
         "--{$mime_boundary}--\n";
}
mail($to, $subject, $message, $headers);
}
?>

php를 사용하건, ruby를 사용하건, 어떤 언어를 사용하더라도...
가장 중요한 것은 일단 해보자라는 용기?? ^^


Posted by Y2K
,
http://www.neuroticweb.com/recursos/css-rounded-box/index.php?color=A9B8CF&fondo=e4ecec

http://www.ajaxload.info/ 에 이은 source 및 gif generator 소스가 잘 되어있는 사이트.
이런 멋진 컨셉으로 만들 수 있는 날은 언제쯤 도달할 수 있을지.;

개발자의 길은 멀고도 험하다. 정말. -_-;;
Posted by Y2K
,
이번에 ASP .NET 사이트에서 발표된 Chart control의 경우에는 지금까지 있었던 상용 Chart들을 완전히 뒤집어버릴 수준의 control을 제시하고 있다. 일반 2D Chart에서 3D Chart까지 거의 안되는 Chart가 없을 정도로 많은 표현을 제공하고 있는데, 한 3년전까지만 해도 chart 만드는 것때문에 엄청나게 고생하던 것을 생각하면 참 여러가지로 많은 생각이 들게 된다. (그때 만든 Chart DLL을 생각하면 눈물이 앞을.. T_T)

그런데, 일반 ASP .NET에서는 chart control을 server 객체로 만들어서 사용을 하는데, MVC에서는 서버 Control이 사용되지 않기 때문에 다른 방법으로 사용해줘야지 된다. 내용을 조금 뒤져본 결과, Chart.SaveImage 함수를 이용해서 Temp 파일로 저장하는 방법으로 해결 가능한 것을 알았다. View에서 저장되는 FileName을 표시해주면 간단하게 해결. 

Chart chart = new Chart();
chart.BackColor = Color.White;
chart.Height = 300;
chart.Width = 450;
chart.AntiAliasing = AntiAliasingStyles.All;
chart.ImageType = ChartImageType.Png;

chart.Titles.Add(new Title() {Text = "판매량", ForeColor = Color.Black});
chart.Legends.Add(new Legend());

Series series = chart.Series.Add("data1");
series.ChartType = SeriesChartType.Column;
series.YValueType = ChartValueType.Int32;
series.LegendText = "Dates";
series.BorderColor = Color.Azure;
series.ShadowOffset = 2;
series["DrawingStyle"] = "Cylinder";

Random rand = new Random();
int dataSize = rand.Next(10, 20);
for(int i = 0 ; i < dataSize ; i++)
{
    string legendText = String.Format("DP {0}", i);
    Double dblValue = (double) rand.Next(1000);
    series.Points.Add(new DataPoint() { YValues = new Double[] { dblValue }, LegendText = legendText });
}

Series series2 = chart.Series.Add("data2");
series2.ChartType = SeriesChartType.Column;
series2.YValueType = ChartValueType.Int32;
series2.LegendText = "Dates2";
series2.BorderColor = Color.Azure;
series2.ShadowOffset = 2;
series2.Color = Color.RosyBrown;
for(int i = 0; i < dataSize; i++)
{
    string legendText = String.Format("DP {0}", i);
    Double dblValue = (double)rand.Next(1000);
    series2.Points.Add(new DataPoint() { YValues = new Double[] { dblValue }, LegendText = legendText });
}


ChartArea chartArea = chart.ChartAreas.Add("Default");
chartArea.BackColor = Color.Transparent;
chartArea.BorderColor = Color.Red;
chartArea.AxisX.IsMarginVisible = true;
chartArea.Area3DStyle.Enable3D = false;

MemoryStream ms = new MemoryStream();
chart.SaveImage(Server.MapPath("~/img.png")); //Save 파일 Name

ms.Dispose();
chartArea.Dispose();
series.Dispose();
chart.Dispose();




Posted by Y2K
,