1var store = [{ 2 "title": "Welcome to Jekyll!", 3 "excerpt":"Youâll find this post in your _posts directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run jekyll serve, which launches a web server and auto-regenerates your site when a file is updated. To add new posts, simply add a file in the _posts directory that follows the convention YYYY-MM-DD-name-of-post.ext and includes the necessary front matter. Take a look at the source for this post to get an idea about how it works. Jekyll also offers powerful support for code snippets: âpython def print_hi(name): print(\"hello\", name) print_hi('Tom') â Check out the Jekyll docs for more info on how to get the most out of Jekyll. File all bugs/feature requests at Jekyllâs GitHub repo. If you have questions, you can ask them on Jekyll Talk. ","categories": ["posts"], 4 "tags": ["index"], 5 "url": "https://subji.github.io/posts/2019/05/25/first-post", 6 "teaser":null},{ 7 "title": "Git ë¹ë°ë²í¸ ì ë ¥ ìì ë ë°©ë²", 8 "excerpt":"1. Git Credential ì ë³´ ì ì¥ git config --global credential.helper store 2. Git Credential ì ë³´ ìºìì ì ì¥ (ìê° ì¤ì ê°ë¥) git config --global credential.helper cache & 'cache --timeout 86400(1ì¼)' Window ì ê²½ì°ìë ìë 주ìì íë¡ê·¸ë¨ì ë°ì ì¤ì¹ í ì ì©í´ë³¸ë¤. https://github.com/microsoft/Git-Credential-Manager-for-Windows ","categories": ["posts"], 9 "tags": ["git"], 10 "url": "https://subji.github.io/posts/2019/05/30/git-credential-helper", 11 "teaser":null},{ 12 "title": "SPRING ìì PROXY ë IP ê¹ì§ ë°ìì¤ë ë°©ë²", 13 "excerpt":"기본ì ì¼ë¡ HttpServletRequest.getRemoteAddr() ë¡ ë°ì ì¬ ì ìì§ë§ Proxy ë IP ì ê²½ì° ì ëë¡ ë°ìì¤ì§ 못íë¤. ê·¸ ì´ì ë ì¹ìë² ëë WAS ì´ì ì Load Balancers (L4, â¦), Proxy Server, Caching Server, HTTP ìë²ì© WAS Connector ë±ì´ ìì ê²½ì° ìì ì¸ê¸í 기í Server ë¤ì ê²½ì° ì¹ìë² & WAS ì HTTP ëë AJP(ì ì© íë¡í ì½) ì¼ë¡ ìì²ì ë³´ë¸ í ë°ì 결과를 í´ë¼ì´ì¸í¸ì ì¬ì ì¡íê² ëë¤. ì´ë¡ ì¸í´ âgetRemoteAddr()â ì null ì ì¶ë ¥íë¤. ê·¸ë¬ë©´ ì´ë»ê² ë°©ë²ì ìë 걸ê¹? íê³ ì°¾ìë³´ë X-Forwarded-For ë± HTTP Header ìì ë°ìì¤ë ë°©ë²ì´ ììë¤. ìëë X-Forwarded-For ì ê°ëµí ì¤ëª ê³¼ ìì ì½ëì´ë¤. X-Forwarded-For (XFF) ë HTTP Header ì¤ íëë¡ HTTP Server ì ìì²í Client IP 를 ìë³í기 ìí ë°©ë² ì¤ íëì´ë¤. ì¼ë°ì ì¼ë¡ Java ìì Client IP 를 ë°ìì¤ê¸° ìí´ì ìëì ê°ì ë°©ë²ì¼ë¡ ìì±íë¤. HttpServletRequest.getHeader(\"X-Forwarded-For\"); HttpServletRequest.getHeader(\"Proxy-Client-IP\"); HttpServletRequest.getHeader(\"WL-Proxy-Client-IP\"); HttpServletRequest.getHeader(\"HTTP_CLIENT_IP\"); HttpServletRequest.getHeader(\"HTTP_X_FORWARDED_FOR\"); ì ì½ë를 ì¬ì©í´ë ë¡ì»¬ íê²½ììë â0:0:0:0:0:0:0:1â ê³¼ ê°ì´ IPv6 ë¡ ì¶ë ¥ëë¤. í´ë¹ ê°ì IPv4 ìì â127.0.0.1â ê³¼ ê°ì ê°ì´ë¤. Tomcat ì ê²½ì° â{âtomcat directoryâ}/bin/catalina.batâ íì¼ì ë¤ìê³¼ ê°ì´ ìì±íë¤. :noJuliConfig set \"JAVA_OPTS=%JAVA_OPTS% %LOGGING_CONFIG%\" -Djava.net.preferIPv4Stack=true :noJuliManager set \"JAVA_OPTS=%JAVA_OPTS% %LOGGING_MANAGER%\" -Djava.net.preferIPv4Stack=true ","categories": ["posts"], 14 "tags": ["spring"], 15 "url": "https://subji.github.io/posts/2019/06/18/getclientiponspring", 16 "teaser":null},{ 17 "title": "ë¸ë¼ì°ì ìì ES6 ì¬ì© ë°©ë²", 18 "excerpt":"vuejs 를 ì¬ì©íë¤ê° import & export 문ì ì¬ì©í ê²½ì°ê° ì겨 ì¬ì©íë ëì¤ ì ìëì§ ìì ìë³ìë¼ê³ ì¤ë¥ê° ëìì ì°¾ìë³´ìë¤. ê²ì ê²°ê³¼ ì´ì ì es6 구문ì ì¬ì©í기 ìí´ì íìíìë babel, webpack ìì´ë ì¬ì©í ì ììë¤. ë°©ë²ì ê°ë¨íë¤. ìë ì½ëìì ì²ë¼ ì¤í¬ë¦½í¸ íê·¸ì âmoduleâ ë§ ì¶ê°í´ì£¼ë©´ ëë¤. <script type=\"module\" src=\"...\"> ì§ìíë ë¸ë¼ì°ì [IE] X [Edge] 17+ [Edge Chromium] 75+ [Chrome] 72+ [Firefox] 66+ [Safari] 12.1+ ","categories": ["posts"], 19 "tags": ["javascript"], 20 "url": "https://subji.github.io/posts/2019/06/21/howtousees6onbrowser", 21 "teaser":null},{ 22 "title": "ZSHìì Vi & Emacs 모ë íë ë°©ë²", 23 "excerpt":"ZSH ìì ìëì ê°ì ëª ë ¹ì´ë¥¼ ì ë ¥íë¤. # Emacs mode bindkey -e # Vi mode bindkey -v ","categories": ["posts"], 24 "tags": ["linux"], 25 "url": "https://subji.github.io/posts/2019/06/25/howtosetvimodeonzsh", 26 "teaser":null},{ 27 "title": "tail ëª ë ¹ì´ë¡ ì¬ë¬ê°ì ë¡ê·¸íì¼ ë³´ê¸°", 28 "excerpt":"ìë ëª ë ¹ì´ë¥¼ ì ë ¥íë©´ ì¬ë¬ê°ì ë¡ê·¸íì¼ì
28 ë¡ê¹ í ì ìë¤. ë¡ê·¸ ë´ì©ì ë³íê° ìì ê²½ì° íì¼ëª ê³¼ ë¡ê¹ ë´ì©ì´ ëì¤ê² ëë¤. (ë¡ê·¸íì¼ì ë³íê° ìë ê²½ì° ì¶ë ¥ X) tail -f *.log ","categories": ["posts"], 29 "tags": ["linux"], 30 "url": "https://subji.github.io/posts/2019/06/27/howtoshowmultiplelog", 31 "teaser":null},{ 32 "title": "Vue.js external script(ì¸ë¶ ì¤í¬ë¦½í¸) ì¶ê° ë°©ë²", 33 "excerpt":"ì¤íë§ & ë·° íë¡ì í¸ë¥¼ íë ëì¤ êµ³ì´ ì¸ë¶ ì¤í¬ë¦½í¸ë¥¼ ì¶ê°í´ì¼ í ì¼ì´ ìê²¼ë¤. ì´ë°ì ë° ë°©ë²ì ëìí´ë ì¤í¬ë¦½í¸ê° ì¶ê°ê° ìëìëë° ë¤ìê³¼ ê°ì´ íëë ì ëì ëìë¤. // vue.config.js module.export = { publicPath: process.env.NODE_ENV === 'production' ? './' : '', // ì ì 리ìì¤ì ê²½ë¡ë¥¼ ìë ê²½ë¡ë¡ ì§ì íë¤. outputDir: '../backend/src/main/resources/static', // ì ì íì¼ ìì± ìì¹ assetsDir: '', // js, css, img íì¼ ìì± ìì¹ indexPath: '../templates/index.html', // index.html íì¼ì´ ìì±ëë ìì¹ devServer: { proxy: { '/': { target: 'localhost:8090', ws: true, changeOrigin: true } } } } vue ì¤ì ì ìì ê°ì´ íê³ vue-cli ëë í 리 ì¤ì public ëë í 리ì ìíë 리ìì¤ íì¼ë¤ì ë£ëë¤. ê·¸ë¦¬ê³ ìëì ê°ì´ vue íì¼ì ìì±íë¤. // xxx.vue export default { methods: { addScript: function () { let scripts = [ 'ì¤í¬ë¦½í¸ ìë ê²½ë¡', 'ex) libs/jquery/jquery.js', ]; for (let i = 0, len = scripts.length; i < len; i++) { let script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); script.setAttribute('src', scripts[i]); script.async = true; document .getElementsByTagName('body or head')[0] .appendChild(script); } } }, created () { this.addScript(); } } ë§ì§ë§ì¼ë¡ ë¹ëíë©´ ì¤íë§ ë¶í¸ resources ëë í 리ì static & templates ëí í 리ìì vue ìì ë¹ëí íì¼ì´ ì긴ë¤. npm run build ì´ì ì¤íë§ì ì¤ííë©´ ì±ê³µì´ë¤. ","categories": ["posts"], 34 "tags": ["vue"], 35 "url": "https://subji.github.io/posts/2019/07/05/vuejs-appendingscript-howto", 36 "teaser":null},{ 37 "title": "Uncaught (in promise) TypeError Cannot read property 'protocol' of undefined ìë¬ ì²ë¦¬", 38 "excerpt":"âUncaught (in promise) TypeError Cannot read property âprotocolâ of undefinedâ Vue 2.x ë²ì ììë axios 를 ì¬ì©í ë, Vue.use(axios) ì ê°ì´ ì¬ì©íë©´ ìì ê°ì ìë¬ê° ë°ìíë¤. ê·¸ë¬ë¯ë¡ ë¤ìê³¼ ê°ì´ ì¬ì©í´ì¼ íë¤. Vue.use(axios); // Incorrect Vue.prototype.$http = axios; // Correct ","categories": ["posts"], 39 "tags": ["vue"], 40 "url": "https://subji.github.io/posts/2019/07/05/vuejs-crossorigin-error", 41 "teaser":null},{ 42 "title": "Apache + Tomcat ì°ë ë° SSL ì ì©", 43 "excerpt":"첫ë²ì§¸ë¡ apache ì tomcat ì ì°ëí기 ìí´ì jk_mod ë¼ë ìíì¹ íë¬ê·¸ì¸ì ì¤ì¹í´ì¼ íë¤. sudo apt install libapache2-mod-jk ê·¸ ë¤ì jk_mod ê° ì¤ì¹ë ëë í ë¦¬ë¡ ì´ëíì¬ íì¼ íë를 ìì íë¤. cd /etc/libapache2-mod-jk vim worker.properties # worker.properties worker.list=\"{ì¬ì©ì ì ì 모ë ì´ë¦}\" ex) ajp13_worker worker.\"{ì¬ì©ì ì ì 모ë ì´ë¦}\".port=\"í¬í¸ ë²í¸ (Port Number)\" worker.\"{ì¬ì©ì ì ì 모ë ì´ë¦}\".host=\"ìì´í¼ 주ì (IP Address)\" worker.\"{ì¬ì©ì ì ì 모ë ì´ë¦}\".type=\"ajp13\" worker.\"{ì¬ì©ì ì ì 모ë ì´ë¦}\".type worker.\"{ì¬ì©ì ì ì 모ë ì´ë¦}\".lbfactor=1 ë¤ ìì±íì¼ë©´ ìíì¹ê° ì¤ì¹ë í´ëë¡ ì´ëíì # ubuntu cd /etc/apache2 첫ë²ì§¸ë¡ ììì ìì±í worker.properties íì¼ì ê²½ë¡ë¥¼ íì¸íë¤. vim mod-availables/jk.conf # jk.conf JkWorkerFile /etc/libapache2-mod-jk/workers.properties ìììë 기본ì ì¼ë¡ jk_mod ìì ì¤ì¹ë íë¡í¼í° íì¼ê²½ë¡ ëì´ìì
43§ë§ ë¤ë¥¸ ê²½ë¡ì ì¶ê°íìì ê²½ì° í´ë¹ ê²½ë¡ë¡ ë³ê²½í´ì¼ íë¤. ë¤ìì¼ë¡ sites-available ì ê°ì í¸ì¤í¸ë¥¼ ì¶ê°íë¤. ì¬ê¸°ììë í°ìº£ê³¼ì ì°ëê³¼ SSL ì¸ì¦ì를 ì¶ê°íë¤. vim sites-available/\"{xxx}\".conf # \"{xxx}\".conf <IfModule mod_ssl.c> <VirtualHost _default_:443> ServerName \"{ëë©ì¸ ë¤ì (Domain name)}\" DocumentRoot \"{í°ìº£ì ROOT ëë í 리 ê²½ë¡ë¥¼ ì¤ì íë¤ (Tomcat Directory path)}\" JkMount /* ajp13_worker <!-- Apache + Tomcat ì°ë --> SSLEngine on <!-- SSL ì°ë --> SSLProtocol all -SSLv2 -SSLv3 SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128:DHES-RSA-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHAAES256-SHA:AES:CAMESIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA SSLHonorCipherOrder on <!-- SSL ì¸ì¦ì ë° í¤ íì¼ ì¤ì --> SSLCertificateFile \"{xxx}\".crt SSLCertificateKeyFile \"{xxx}\".key SSLCertificateChainFile \"{xxx}\".pem SSLCACertificateFile \"{xxx}\".crt <!-- default-ssl.conf 기본 ì¤ì --> SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire <FilesMatch \"\\.(cgi|shtml|phtml|php)\"> SSLOptions +StdEnvVars </FilesMatch> <Directory /usr/lib/cgi-bin> SSLOptions +StdEnvVars </Directory> <!-- í°ìº£ ëë í 리ì ëí ë¤ìê³¼ ê°ì ì¤ì ì í´ì¼ SSL ì´ ì ì©ëë¤. --> <Directory \"{Tomcat ROOT Directory path}\"> Options Indexes FollowSymLinks AllowOverride None Order allow,deny Allow from all Require all granted </Directory> </VirtualHost> ë§ì§ë§ì¼ë¡ í°ìº£ ëë í 리ìì server.xml ì ìì íë¤. vim conf/server.xml #server.xml <Connector port=\"8009\" protocol=\"AJP/1.3\" redirectPort=\"8443\" /> <!-- 주ì ì ê±° --> ì´ì ìíì¹ì í°ìº£ì¼ë¡ ì¬ìì íë©´ ì ì©ëë¤. # restart apache sudo systemctl restart apache2 # restart tomcat sh shutdown.sh sh startup.sh ë§ì§ë§ì¼ë¡ http ë¡ ì¤ë ì ìì https ë¡ ë¦¬ë¤ì´ë í¸í´ì£¼ë ë°©ë²ì´ë¤. ```bash â{user defined configuration file in apache sites-available}â.conf <VirtualHost :80> ServerName â{Domain name}â ServerAlias â{Domain name alias}â ProxyPass / â{url}â ProxyPassReverse / â{reverse url}â # HTTP -> HTTPS Redirect RewriteEngine On RewriteCond %{SERVER_PORT} !443 # â%{HTTPS} offâ is not work RewriteRule ^(/(.))?$ https://%{HTTP_HOST}/$1 [R=301,L] # ^(/(.*))?$ = all url patterns SetOutputFilter DEFLATE SetEnvIfNoCase Request_URI \"\\.(?:gif|jpe?g|png)$\" no-gzip </VirtualHost> ","categories": ["posts"], 44 "tags": ["ssl","tomcat","apache"], 45 "url": "https://subji.github.io/posts/2019/07/08/ssl-apacheandtomcat-howto", 46 "teaser":null},{ 47 "title": "Oracle ë ì§ ê´ë ¨ 쿼리", 48 "excerpt":"ì°¸ê³ í¨ì ë° í¬ë§· # REF FUNCTION TO_CHAR(TARGET, FORMAT) : ì«ì ëë ë ì§ë¥¼ ì§ì í í¬ë§·ì ë§ê² ë³í TO_DATE(TARGET) : 문ì를 DATEë¡ ë³í # FORMATS 'YYYY' : 4ì리 ë ëë¡ íì 'YY' : ëì 2ì리 ì°ëë¡ íì ('2019' -> '19') 'YEAR' : ì°ë를 ìíë±ì¼ë¡ íì 'Q' : í´ë¹ìì ë¶ê¸° 'MM' : ë¬ì ëì리 ì«ìë¡ íì 'MON' : ë¬ì ìíë± ì½ì´ë¡ íì 'MONTH' : ë¬ì ìíë±ì¼ë¡ íì 'W' : í´ë¹ ìì 주차 'WW' : íì¬ ë ëì 1ì¼ë¶í° 7ì¼ë¨ìì 주차 'IW' : ë ì§ê° í´ë¹í ì£¼ê° í루ë¼ë ë¤ìí´ì¸ ê²½ì°ìë ê·¸ë¬ì ì²«ì£¼ë¡ íë¤ 'DD' : ì¼ì를 ëì리 ì«ìë¡ íì 'DAY' : ì¼ìì í´ë¹íë ìì¼ 'DY' : ì¼ìì í´ë¹íë ìì¼ì ì½ì´ 'HH' : 12ìê°ì¼ë¡ íì (1 - 12) 'HH24' : 24ìê°ì¼ë¡ íì (0 - 23) 'MI' : ë¶ì íì 'SS' : ì´ë¥¼ íì 'AM, PM': ì¤ì , ì¤í를 íì ë ì§ ê³ì° # EX) SYSDATE = \"2019-08-27 13:24:00\" # í¹ì ë ì§ì íì¬ ë ì§ìì ì°¨ì´ë¥¼ NUMBER íì¼ë¡ ë°í # = 0.050020534647550776583
480346475507765830346 SELECT MONTHS_BETWEEN(SYSDATE, TO_DATE('2019-08-26'), 'YYYYMMDD') FROM DUAL; # í¹ì ë ì§ì N ê°ìì ëí기 # = 2019-12-27 13:13:51 SELECT ADD_MONTHS(SYSDATE, 4) FROM DUAL; # í¹ì ë ì§ì ë¤ì주 í¹ì ìì¼ êµ¬í기 # ! ìì¤í ì¥ì ì ë³´ì ë°ë¼ íêµì¼ ê²½ì° ëë²ì§¸ ì¸ì를 íê¸ë¡, ìì´ê¶ì¼ ê²½ì° ìì´ë¡í´ì¼ # ìë¬ê° ë°ìíì§ ìëë¤. # = 2019-09-02 13:26:27 SELECT NEXT_DAY(SYSDATE, 'ììì¼') FROM DUAL; # í¹ì ë ì§ì ë°ì¬ë¦¼ EX) í¹ì ë ì§ì ìê°ì´ ì¤íë©´ ë°ì¬ë¦¼ # = 2019-08-28 00:00:00 SELECT ROUND(SYSDATE, 'DD') FROM DUAL; # ì ë ëì 12ì 31ì¼ SELECT TRUNC(SYSDATE, 'IY') FROM DUAL; # ê°ì ë ëì 첫ë SELECT TRUNC(SYSDATE, 'YY') FROM DUAL; # ê°ì ë ëì ë§ì§ë§ë # = 2019-12-31 00:00:00 SELECT ADD_MONTHS(TRUNC(SYSDATE, 'YEAR'), 12) - 1 FROM DUAL; # ë¶ê¸°ì ì²«ë¬ 1ì¼ SELECT TRUNC(SYSDATE, 'Q') FROM DUAL; # í¹ì ë ì§ê° ìí ë¬ì 첫ë # = 2019-08-01 00:00:00 SELECT TRUNC(SYSDATE, 'MONTH') FROM DUAL; # í¹ì ë ì§ê° ìí ë¬ì ë§ì§ë§ ë # = 2019-08-31 13:23:53 SELECT LAST_DAY(SYSDATE) FROM DUAL; # í¹ì ë¬ì ì¼ì # = 31 SELECT CAST(TO_CHAR(LAST_DAY(SYSDATE), 'DD') AS INT) FROM DUAL; # YYYY-MM-01 íì SELECT TRUNC(SYSDATE, 'MM') FROM DUAL; # YYYY-MM-DD íì SELECT TRUNC(SYSDATE, 'DD') FROM DUAL; # ë¶ê¸° ê³ì° SELECT TRUNC(SYSDATE, 'Q') 'Në¶ê¸°' FROM DUAL; # 주차 구í기 # ì¼ìì¼ ê¸°ì¤ SELECT TRUNC((TRUNC(SYSDATE) - TRUNC(TRUNC(SYSDATE, 'YY'), 'D')) / 7) + 1 # ììì¼ ê¸°ì¤ SELECT TRUNC((TRUNC(SYSDATE) - TRUNC(TRUNC(SYSDATE, 'YY'), 'IW')) / 7) + 1 ì¶ì²: ì¤ë¼í´ TRUNC ë ì§ ê¸°ë¥ SYSDATE ê¸°ì¤ ì¼ìê´ë ¨ ë¤ìí íí2 ë ì§ í¨ì ë° ë ì§ êµ¬í기 ì¤ë¼í´ ë ì§ê³ì° ë 주차 구í기 ","categories": ["posts"], 49 "tags": ["Oracle"], 50 "url": "https://subji.github.io/posts/2019/08/27/oracle-getweeknumberofyear-howto", 51 "teaser":null},{ 52 "title": "Git error: src refspec master does not match any", 53 "excerpt":"Git í¸ì ëì¤ ìëì ê°ì ìë¬ê° ë°ìíìë¤. error: src refspec master does not match any error: failed to push some ref to 'URL' ì ìë¬ë í¸ì를 í ë, 커ë°ë ë´ì©ì´ ì기ë문ì ê¹ ìë²ì ì¶ê°í ë¸ëì¹ê° ì¡´ì¬íì§ ìì ë°ìíë ê²ì´ë¤. ì¦, ë¹ ë¦¬í¬ì§í 리 (ì. ì´ê¸°ì 리í¬ì§í 리를 ìì±í ê²½ì°)ì¼ ë, ìë¬´ë° ì»¤ë° ìì´ í¸ì를 íììë ë°ìíë¤. ì´ë 커ë°ì íê³ í¸ì를 íë©´ ì½ê² í´ê²°ì´ ê°ë¥íë¤. ","categories": ["posts"], 54 "tags": ["git"], 55 "url": "https://subji.github.io/posts/2019/08/28/git-commiterror-solve", 56 "teaser":null},{ 57 "title": ".gitignoreê° ì ì©ëì§ ìëë¤", 58 "excerpt":"git í¸ì를 í ë ê°ë gitignore ê° ì ëë¡ ëìíì§ ìë ê²½ì°ê° ìë¤. ì´ ê²½ì° git ìºì를 ì§ì°ê³ ë¤ì í¸ìí´ì£¼ì git rm -r --cached . git add . git commit -m 'ê¹ ì´ê·¸ë ¸ì´ê° ì ì©ëì§ ììì ë¤ì ì¬ë ¤ì~' ì¶ì²: My .gitignore file is ignored by git and it does not work ","categories": ["posts"], 59 "tags": ["git"], 60 "url": "https://subji.github.io/posts/2019/08/28/git-gitignorenotwork-solve", 61 "teaser":null},{ 62 "title": "/tmp/spring.log (Permission denied)", 63 "excerpt":"íëì íë¡ì í¸ìì ì¬ë¬ê°ì íë¡íì¼ì ê´ë¦¬í ë /tmp/spring.log ê° ê°ì´ ì¬ì©ëì´ ê¶í ì¤ë¥ê° ë°ìíë ê²½ì°ê° ìë¤. ë³´íµ logback ì 기본ì¤ì íì¼ì ê°ì ¸ì¤ë©´ì ê°ì ìì¹ë¥¼ ê°ë¦¬í¤ê² ëì´ ì기ë ì¤ë¥ì¸ë°, ì´ë´ ê²½ì° ìëì ê°ì´ í´ê²°í ì ìë¤. <property name=\"LOG_TEMP\" value=\"Log/\" /> <include resource=\"org/springframework/boot/logging/logback/base.xml\"/> ì 문구를 ê°ë³ logback íì¼ì ì¶ê°í´ì£¼ë©°, 주ìì¬íì ë°ëì property íê·¸ê° base.xml 를 í¬í¨íë íê·¸ë³´ë¤ ìì ìì¼ íë¤. ","categories": ["posts"], 64 "tags": ["spring"], 65 "url": "https://subji.github.io/posts/2019/11/12/springboot-logbackpermissiondenied-solve", 66 "teaser":null},{ 67 "title": "How to access Resources (images, etc...) on spring boot (ì¸ë¶ìì íì¼ ì ê·¼ ë°©ë²)", 68 "excerpt":"ì¤íë§ ë¶í¸ë¥¼ ë커ì ì¬ë ¤ì ì¬ì©íë¤ê° ì¸ë¶ìì ì´ë¯¸ì§ ì ê·¼ì´ ìëë¤.. ë¡ì»¬ììë ì ëìíì§ë§ í´ë¼ì´ì¸í¸ê° ìë²ìì ê²½ë¡ë¥¼ 몰ë¼ì ì¸ì§ 404ìë¬ë¥¼ ë±ì´ëë¤. ì´ë´ ê²½ì° ë¤ìê³¼ ê°ì´ ëë í 리 ê²½ë¡ë¥¼ ì§ì í´ì£¼ë©´ ëë¤. @Configuration public class WebConfig extends WebMvcConfigurerAdapter { \t@Value(\"${'íì¼ì´ ìë ëë í 리 ê²½ë¡'}\") private String path; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { \t\tregistry \t\t\t.addResourceHandler(path + \"/**\") // url ì ê·¼ ê²½ë¡ \t\t\t.addResourceLocations(\"file:\" + path + \"/\"); // ëë í 리 ê²½ë¡ (ë°ëì file: ì ë¶ì¬ì£¼ì´ì¼ íë¤.) } } ì´ë¡ì í´ë¼ì´ì¸í¸ììë ìë²ì 리ìì¤ì ì½ê² ì ê·¼ì´ ê°ë¥í´ì¡ë¤. ","categories": ["posts"], 69 "tags": ["spring boot"], 70 "url": "https://subji.github.io/posts/2019/11/21/springboot-external-reources-solve-copy-2", 71 "teaser":null},{ 72 "title": "List or Map value print just string (vscode debuggerìì list ëë mapì´ ë¬¸ìì´ë¡ë§ ëì¬ ê²½ì°)", 73 "excerpt":"ì¤íë§ íë¡ì í¸ ì¤ ëë²ê¹ ì´ íìí´ì vscode ìì ì§ìíë ëë²ê¹ ì ì¬ì©íë¤. ê·¸ë°ë°.. list ë map ì´ ê°ì¼ë¡ ì¡°íëëê² ìëë¼ ë¬¸ìì´ë¡ âarraylist@xxx size=0â ì´ë°ìì¼ë¡ ëì¤ëê² ìëê°??! ê·¸ëì ì´ì¬í 구ê¸ë§ì í´ë³´ë íí 리ì¼ì ì ê³µ
73ì´ ëì´ìì´ ì½ê² í´ê²°í ì ììë¤. { \"version\": \"0.2.0\", \"configurations\": [ { \"name\": \"(lldb) Launch\", \"type\": \"cppdbg\", \"request\": \"launch\", \"program\": \"${workspaceFolder}/helloworld.out\", \"args\": [], \"stopAtEntry\": true, \"cwd\": \"${workspaceFolder}\", \"environment\": [], // set debugger config for printing map or list value // ======================== \"externalConsole\": true, \"MIMode\": \"lldb\", \"logging\": { \"trace\": true, \"traceResponse\": true, \"engineLogging\": true } // ======================== } ] } íìë ê³³ì ëë²ê±° í´ ì í±ëë°í´ë¥¼ ëë¬ ëì¤ë configuration ì ì ë ¥í´ì£¼ë©´ ëë¤. ","categories": ["posts"], 74 "tags": ["Visual Studio Code"], 75 "url": "https://subji.github.io/posts/2019/11/21/vscode-debuggerlistandmapvalue-solvecopy", 76 "teaser":null},{ 77 "title": "Spring Security Cross Domain", 78 "excerpt":"íë¡ì í¸ë¥¼ íë¤ë³´ë©´ íì ë§ì£¼íë Cross Domain 문ì ì´ë²ìë ì
78´ê¹ìì´ ë¬¸ì ê° ìê²¼ë¤. ê°ì ìë²ìì í¬í¸ê° ë¤ë¥¸ ëê°ì ì¹ìë²ê° ìê³ , í ì¹ ìë²ìì ë¤ë¥¸ ì¹ ìë²ê° ê´ë¦¬íë ì´ë¯¸ì§ë¥¼ ë¶ë¬ì¬ë ë°ìíë 문ì ìë¤. íì ë°ìíëë° ì ì´ëì§ ìì¼ë ë§¤ë² ì°¾ì보기를 ë°ë³µíë¤. ê·¸ëì ì°¾ì¼ë©´ì í´ê²°? ì¡°ì¹ ë°©ë²ì 기ë¡í´ëìë¤. 1. CORS ë HTTP Header ì OPTIONS ìì²ì 무ìíë¤? </br>HTTPMethod.OPTIONS 를 íì©í´ë³´ìë¤. </br>ê²°ê³¼ë.. </br>ì무 ìê´ì´ ììë¤.. 2. CorsFilter 를 ì¶ê°? </br>ì¸ì¦ê³¼ì ì ê±°ì¹ê¸°ì ì Filter 를 ì¶ê°í´ì ì¬ì ì CORS ê²ì¬ë¥¼ í´ë³´ìë¤. </br>ì´ì¯¤ìì í´ê²°ëë©´ ë¤íì´ë¤. </br>íì§ë§â¦ </br>ììë ìëë¤. 무ìì´ ë¬¸ì ì¼ê¹.. 3. ê°ì ëë©ì¸ í¹ì ê°ì ìì´í¼ìì ì¿ í¤ë¥¼ ê³µì í¨ì ìì´ì ì기ë 문ì ? </br>ì´ë²ì ì²ì 겪ê²ë ê²½ì°ë¤. </br>ì¤íì¤ë²íë¡ì°ìì ê²ìíê³ ì°¾ìë´ì ì´ë¦¬ì 리 í´ë´ë ìëëê² ë¨ì§ ìë²ìì ê´ë¦¬ëë ì¿ í¤ ë문ì´ìë¤. </br>ê·¸ëì curl ìì² ì ì¿ í¤ë¥¼ ì ê±°íê³ ìì²íë CORS ìë¬ê° ë°ìíì§ ìëê²ì´ìë¤! í´ê²° / ì¡°ì¹ : </br>ì´ë µì§ ììë¤. ë¨ì§ ì´ë¯¸ì§ë¥¼ ì ê³µí´ì£¼ë ì¹ìë²ìì ì¸ì ê´ë¦¬ë¥¼ STATELESS íê² íë©´ëë¤. ìì¤ ì½ë : /** * Web Security Config */ @Override public void configure (WebSecurity web) throw Exception { web.ignore().antMatcher(\"${URL_PATH}\"); } @Override protected void configure (HttpSecurity http) throw Exception { http .csrf().disable() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); // ì¤ì! } /** * Web MVC Config (CORS ë±ë¡) */ @Override public void addCorsRegistry (CorsRegistry registry) { registry .addMapping(\"/*\") .allowedOrigins(\"*\") .allowedMethods(\"GET\", \"POST\", \"OPTIONS\", \"PUT\") .allowedHeaders(\"Content-Type\", \"X-Requested-With\", \"accept\", \"Origin\",\"Access-Control-Request-Method\", \"Access-Control-Request-Headers\") .exposedHeaders(\"Access-Control-Allow-Origin\", \"Access-Control-Allow-Credentials\") .allowCredentials(true) .maxAge(3600); } ","categories": ["posts"], 79 "tags": ["Spring Security"], 80 "url": "https://subji.github.io/posts/2019/12/06/springsecurityignorecors", 81 "teaser":null},{ 82 "title": "Spring Getter & Setter ë³´ìì·¨ì½ì± ê´ë ¨", 83 "excerpt":"ì¹ ì´í리ì¼ì´ì ì ë³´ìì·¨ì½ì± ì ê²ì ë°ê³ ë¤ìê³¼ ê°ì 문ì ê° ìë¤ëê²ì ë³´ê³ ë°ìë¤. âPublic ë©ìëë¶í° ë°íë Private ë°°ì´ - privateë¡ ì ì¸ë ë°°ì´ì´ public ë©ìë를 íµí´ ë°íì´ ëê³ ììâ ìê³ ë³´ë Getter & Setter ê° ì ìë VO í¹ì Domain íì¼ì´ìë¤. ë¤ë¥¸ ê³³ì ì무 ì´ììëë° ì ì 기 í¹ì ë©ìëìë§ ë¬¸ì ê° ìëê²ì¸ê° íë ìë¬¸ì´ ìê²¼ë¤. ìì¸í ë³´ë ë¤ë¥¸ ë©ìëììë 기본í íì ì ë°ííê³ ììê³ , 문ì ê° ë í´ë¹ ë©ìëììë 참조(ê°ì²´) íì ì ë°ííê³ ììë¤. ì¦, 기본í íì (int, String, .. ë±) ì´ ìë 참조(ê°ì²´) (Array, ⦠ë±) ì 주ì ì ë³´ê° ì ë¬ë기 ë문ì ì´ë¥¼ ê·¸ëë¡ ë°ííë ê²ë³´ë¨ ìë¡ ë³µì¬ë¥¼ í´ì ë°íí´ì¼ íë¤ë ê²ì´ìë¤. ê·¸ëì ë¤ìê³¼ ê°ì´ ìì íë¤. ìì¤ ì½ë : private String[] test; public String[] setTest (String[] test) { String[] temp = new String[test.length]; System.arraycopy(test, 0, temp, 0, test.length); this.test = temp; } public String[] getTest (String[] test) { String[] temp = new String[test.length]; System.arraycopy(test, 0, temp, 0, test.length); return temp; } ","categories": ["posts"], 84 "tags": ["spring"], 85 "url": "https://subji.github.io/posts/2019/12/20/springprivategettersetter-solve", 86 "teaser":null},{ 87 "title": "Docker Got permission denied while trying to connect to the Docker daemon socket at... ìë¬ í´ê²°", 88 "excerpt":"ë커를 ì²ìì¤ì¹íê³ ì¤í í âdocker ps -aâ 를 ì ë ¥í´ë³´ë **âGot permission denied while trying to connect to the Docker daemon socket atâ ìë¬ë¥¼ ì¶ë ¥íë¤ ê¶íì´ ìë¤ë 문ì ì¸ë° ê°ë¨íê² íì¬ ë¡ê·¸ì¸í ì ì ìê² ë커 ê¶íì 주면 ëìë¤. ìì¤ ì½ë : sudo usermode -a -G docker $USER sudo reboot ","categories": ["posts"], 89 "tags": ["Docker"], 90 "url": "https://subji.github.io/posts/2019/12/22/dockerGot-permission-denied-while-trying-to-connect-to-the-Docker-daemon-socket/.-solve", 91 "teaser":null},{ 92 "title": "ìë° & ì¤íë§ ì¤í ëª ë ì´ìì ìì±ê°ì ë³ê²½íë ì¤í 커맨ë(h
92ow to change property when use java -jar command with option)", 93 "excerpt":"| 문ì ì¤íë§ ë¶í¸ íë¡ì í¸ë¥¼ íë©´ì application.yml ë´ì íë¡í¼í°ê°ì ë³ê²½ & ì¶ê°í´ì£¼ì´ì¼ íë ìí©ì´ ë°ìíë¤. | í´ê²° ë³´íµ war íì¼ì ì¤íí ë java -jar file.war ë¡ ì¤ííë¤. ì´ë, -Drun.arguments=â{íë¡í¼í°ëª =ê°} ì ì¶ê°í´ì£¼ë©´ application.yml ë´ì ê°ì´ ë³ê²½ & ì¶ê°ê° ëë¤. ì²ììë -Díë¡í¼í°ëª =ê° ì¼ë¡ íëë° ì ì©ì´ ìëìë¤. ê·¸ëì ì°¾ìë³´ë -D ë ìì¤í íë¡í¼í°ë¥¼ ì¤ì íë ìµì ì´ë¼ê³ íë¤. ê·¸ëì ìì¤í íë¡í¼í°ì¸ run.arguments 를 ì¬ì©íì¬ ì»¤ì¤í íë¡í¼í°ë¥¼ ë겨주ë ì ëë¤. 주ìí ì ì ì¤íë§ë¶í¸ì ë©ì¸ í¨ììì ë¤ìê³¼ ê°ì´ ìì±ì´ ëì´ìì´ì¼ íë¤. | ìì¤ ì½ë java -jar -Drun.arguments=--test.test=test application.war public static void main(String[] args) { // args 를 ë°ëì íë¼ë¯¸í°ë¡ ë겨주ì´ì¼ íë¤. SpringApplication.run(MainApplication).class, args); } ","categories": ["posts"], 94 "tags": ["java","spring"], 95 "url": "https://subji.github.io/posts/2020/01/10/java-jar-with-option-override-property-solve", 96 "teaser":null},{ 97 "title": "Crontab ì ì© ì 주ìì¬í(Precautions to apply Crontab)", 98 "excerpt":"| 문ì ShellScript íì¼ì Crontab ì ì ì©íëë° ìì´ì ì¤íì´ ìëë ê²½ì°ê° ë°ìíìë¤. | í´ê²° ê°ê¸ì ì´ë©´ âvi crontabâ ëì ```crontab -eâ 를 ì¬ì©íì âviâ ë¡ ì¤ííë ê²½ì°ë ê³ì ê¶íì ë°ë¥¸ ì¤ì ì´ ë¬ë¼ì§ëë°, âcrontab -eâ ë¡ ì¤ííë ê²½ì°ë ì´ë¥¼ ê³ ë ¤íì§ ììë ë기 ë문ì´ë¤. Crontab ìë ì ëê²½ë¡ / íê²½ë³ì ì¤ì ì ê¼ í´ì£¼ì´ì¼ íë¤. ê°ê¸ì ì´ë©´ ShellScript ë¡ ë§ë¤ê³ , íì¼ ìë¨ì â/bin/bash, /â¦/bin/pythonâ ë±ê³¼ ê°ì´ íê²½ë³ì ì¤ì ì í´ì£¼ì ShellScript ë ì´ìì´ë©´ ì¤íê°ëí íì¼ë¡ ë§ë¤ì. chmod +x ì¤í¬ë¦½í¸íì¼.sh Crontab ì ìµì ìê°ì 1ë¶ì´ë©°, ì´ë¨ìë¡ íê³ ì í ëë * * * * sleep {second}; {ëª ë ¹ì´} ì ííë¡ ì¬ì©íì. ","categories": ["posts"], 99 "tags": ["linux"], 100 "url": "https://subji.github.io/posts/2020/01/15/crontab-cautions", 101 "teaser":null},{ 102 "title": "Java í¹ì ìì¼ì 기ì¤ì¼ë¡ 주차 구í기(Get number of week where first day of some day)", 103 "excerpt":"| 문ì ë ë를 기ì¤ì¼ë¡ 주차를 구íê±°ë ë¬ì 주차를 구í ë Calendar ê°ì²´ì 기본ê°ì¼ë¡ ì í´ì§ 주차를 구íë¤ë³´ë ìíë ì£¼ì°¨ê° ëì¤ì§ ìë ê²½ì°ê° ë°ìíìë¤. | í´ê²° Calendar ì ì¸ì¤í´ì¤ë¥¼ ê°ì ¸ì¬ë íë¼ë¯¸í°ë¡ ë¡ì¼ì¼ê°ì 주ë ë°©ë²ì´ìë¤. // ìì - Calendar cal = Calendar.getInstance(new Locale(\"en\", \"US\")); // ì¼ìì¼ ê¸°ì¤ ì£¼ì°¨ 구í기 Calendar cal = Calendar.getInstance(new Locale(\"en\", \"UK\")); // ììì¼ ê¸°ì¤ ì£¼ì°¨ 구í기 cal.get(Calendar.WEEK_OF_YEAR); Calendar ê°ì²´ì setFirstDayOfWeek, minimalDaysInFirstWeek 를 ì ì©íë ë°©ë²ì´ìë¤. minimalDaysInFirstWeek ë©ìëë í´ë¹ ì°ëì 첫주ì í¬í¨í ìµì ì¼ ì를 ì¤ì íë ê²ì´ë¤. setFirstDayOfWeek ì 기ì¤ìì¼ì ì¤ì íê³ , minimalDaysInFirstWeek ì í¬í¨í ì¼ ì를 ì¤ì íë©´ëë¤. // ìì - ì¼ìì¼ì ìììì¼ë¡ ì¤ì íê³ ì£¼ì°¨ë¥¼ ê³ì° Calendar cal = Calendar.getInstance(); cal.setFirstDayOfWeek(Calendar.SUNDAY); cal.minimalDaysInFirstWeek(7); cal.get(Calendar.WEEK_OF_YEAR); ","categories": ["posts"], 104 "tags": ["java"], 105 "url": "https://subji.github.io/posts/2020/01/15/java-get-numberofweekstartwithday-solve", 106 "teaser":null},{ 107 "title": null, 108 "excerpt":"| 문ì ì¹ ì´í리ì¼ì´ì ì ë§ë¤ë©´ì íì¬íì´ì§ìì ë¤ìíì´ì§ë¡ ì´ëíì ì´ì íì´ì§ë¡ ê°ê¸°ìí Referer í¤ë를 ë°ìì¤ëë°, 주ì를 ì§ì ì ë ¥í´ì ì ê·¼íë©´ ì´ì íì´ì§ ì ë³´ì¸ Referer 를 Null ë¡ ë°ììë¤. | ê³¼ì âRefererâ ë? Referer ìì² í¤ëë íì¬ ìì²ë íì´ì§ì ë§í¬ ì´ì ì ì¹ íì´ì§ 주ì를 í¬í¨íë í¤ëì´ë¤. Interceptor ìì ê°ë¡ì±ë¯ë¡ Interceptor ìì Referer 를 ë°ììë³´ìë¤. => ì¤í¨ í¹ì¬ë ì¸ì ì ë°ìì¬ ì ìì§ ììê¹íê³ ì¸ì ì ì¶ê°í´ì ë°ìë³´ìë¤. => ì¤í¨ | ê²°ë¡ êµ¬ê¸ë§í ê²°ê³¼ Referer ë ë¤ìê³¼ ê°ì ê²½ì°ìë ë°ìì¬ ì ìë¤ê³ íë¤. 주ì를 ì§ì ì ë ¥í ê²½ì° ì¦ê²¨ì°¾ê¸°ë¡ ì´ëí ê²½ì° location.href ë¡ ì´ëí ê²½ì° ì°¸ì¡°ëë 리ìì¤ê° ë¡ì»¬ âíì¼â í¹ì âë°ì´í°âì URIì¸ ê²½ì° ìì íì§ ìì HTTP ìì²ì´ ì¬ì©ëê³ ì°¸ì¡° íì´ì§ê° ë³´ì íë¡í ì½(HTTPS)ë¡ ìì ë ê²½ì° ","categories": [], 109 "tags": null, 110 "url": "https://subji.github.io/2021/12/09/2020-01-16-refererhttpheader-why", 111 "teaser":null},{ 112 "title": "Git username, password ì ì¥ ë°©ë² (how to save username and password in git)", 113 "excerpt":"| 문ì git pull, git push íëë° ìì´ì ê³ì ë¹ë°ë²í¸ë¥¼ ì ë ¥í´ì¼ëì ë²ê±°ë¡ì ë¤. ê·¸ëì ë¹ë°ë²í¸ë¥¼ ì ë ¥íì§ ìê³ ì ì¥í´ì ì¬ì©íë ë°©ë²ì ì°¾ìë³´ìë¤. | ê³¼ì git config credential.password={password} => ìì ëª ë ¹ì´ê° ìëë¤. | ê²°ë¡ ë¤ì ëª ë ¹ì´ë¥¼ 먼ì ì¤ííë¤. ì ì²´ì ê³µíµ ì ì ì´ë¦ê³¼ ë¹ë°ë²í¸ë¥¼ ì ì¥íê³ ì¶ì¼ë©´ âglobal ë©ë´ë¥¼ ë¶ì¬ì¤ë¤. git config (--global) credential.helper store ê·¸ë¦¬ê³ ëì ë¤ì ëª ë ¹ì´ë¥¼ ì¤ííë¤. git pull ì´ì ë§ì§ë§ì¼ë¡ username ê³¼ password 를 ì ë ¥íë©´ ì ì¥ì´ ëë¤. ","categories": ["posts"], 114 "tags": ["git"], 115 "url": "https://subji.github.io/posts/2020/01/17/git-hotwtosaveusernameandpassword-solve", 116 "teaser":null},{ 117 "title": "Javascript SNS(Social Media) ê³µì ë°©ë² (How to share social media in javascript.)", 118 "excerpt":"| 문ì íë¡ì í¸ ê°ë° ì¤ íì´ì§ë¥¼ SNS (Social Media) ë¡ ê³µì íë 기ë¥ì ì¶ê°í´ì¼ íë¤. ê³µì í SNSë Facebook, Twitter, Kakao, ê·¸ë¦¬ê³ ê°ë¨íê² ë§í¬ë¥¼ í´ë¦½ë³´ëë¡ ë³µ
118ì¬íë ì ëì´ë¤. | ê³¼ì ê°ë¨íë¤. Facebook ì´ë Twitter ì ê²½ì° ê³µì API ê° ì¡´ì¬í기 ë문ì, í´ë¹ API ë¡ íë¼ë¯¸í°ë§ ë겨주면 ëë¤. ë°ë©´ ì¹´ì¹´ì¤ì ê²½ì°ìë ì¹´ì¹´ì¤ ê°ë°ì íì´ì§ìì ì±(or ì¹) ë±ë¡ì í´ì£¼ì´ì¼ íë¤. ê³µì í ë ê·¸ë¥ ë§í¬ë§ ë¬ë ê³µì í ì ìì§ë§ ê·¸ë 기ìë ë°ë°íë¯ë¡ 미리보기 íì´ì§ê° ê°ìê² ëì¤ë©´ ì¢ë¤. 미리보기 íì´ì§ë Facebook ìì ë§ë OpenGraph ê° ìë¤. ì¬ì¤ì íì¤ì¼ë¡ êµ³ì´ì§ë ë¯ íë¤. ì¤íê·¸ëíë ê°ë¨íê² ë©ííê·¸ì Prefix ë¡ âogâ ê° ë¶ì í그를 ë§íë©°, íì´ì§ë¥¼ í¬ë¡¤ë§ íë ë´ë¤ì´ í´ë¹ í그를 ì½ì´ì ë¯¸ë¦¬ë³´ê¸°ë¡ ë§ë¤ì´ì£¼ë ê²ì´ë¤. ì¤íê·¸ëí를 ì¤ì í ëë ë¤ìê³¼ ê°ì´ ì¤ìí ì ì´ ìë¤. ì¸ë¶ì ê³µê°ë ëë©ì¸ ëë ê³ ì ìì´í¼ë¡ ë íì´ì§ì´ì¼ íë¤. Javascript ë¡ ëì ì¼ë¡ ë³ê²½ëë ì¤í ê·¸ëíë ì ì©ëì§ ìëë¤. ì²ì ë¶ë¬ì¨ html íì´ì§ì og ìì±ë§ì ê°ì ¸ì¨ë¤. ëì ë©íí그를 ìì±í기 ìí´ìë ìë²ì¬ì´ë ë ëë§ ëë ìíì¹ nginx ì ê°ì ì¹ìë²ìì ë°ë¡ Redirect 를 í´ì£¼ì´ì¼ íë¤. og:image ì ê²½ì° âhttp://â 를 í¬í¨í ì ë ê²½ë¡ë¥¼ ì ë ¥íë©° ìë ê²½ë¡ë¥¼ ì ë ¥íë©´ ìëë¤. og:image ì ê²½ì° ìµì í¬ê¸°ê° 200x200 í½ì ì´ë¤. Twitter ì ì© Open Graph ê° ìì§ë§ Facebook ì OpenGraph ë ê³µì©ì¼ë¡ ì¬ì©ëë¤. | ê²°ë¡ // Facebook ê³µì window.open( 'https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent('ê³µì í 주ì') ); // Twitter ê³µì window.open( 'https://twitter.com/intent/tweet?url=' + encodeURIComponent('ê³µì í 주ì') ); // Kakao ê³µì Kakao.init('ì¹´ì¹´ì¤ ê°ë°ì íì´ì§ìì ë°ê¸ë°ì í¤'); Kakao.Link.sendDefault({ objectType: 'feed', content: { title: 'ê³µì 미리보기 ì 목', imageUrl: 'ê³µì 미리보기 ì¸ë¤ì¼', link: { webUrl: 'ê³µì í 주ì' } }, buttons: [{ title: 'ë§í¬ ì´ê¸°', link: { webUrl: 'ê³µì í 주ì' } }]; }); <!-- Facebook (Common) Open Graph --> <meta property=\"og:type\" content=\"미리보기 ì¢ ë¥\" /> <meta property=\"og:url\" content=\"íì´ì§ URL\" /> <meta property=\"og:title\" content=\"미리보기 ì 목\" /> <meta property=\"og:description\" content=\"미리보기 ì¤ëª \" /> <meta property=\"og:image\" content=\"미리보기 ì´ë¯¸ì§ 주ì\" /> <!-- Twitter Open Graph --> <meta name=\"twitter:card\" content=\"í¸ìí° ë¯¸ë¦¬ë³´ê¸° ì¢ ë¥\" /> <meta name=\"twitter:url\" content=\"íì´ì§ URL\" /> <meta name=\"twitter:title\" content=\"미리보기 ì 목\" /> <meta name=\"twitter:description\" content=\"미리보기 ì¤ëª \" /> <meta name=\"twitter:image\" content=\"미리보기 ì´ë¯¸ì§ 주ì\" /> ","categories": ["posts"], 119 "tags": ["git"], 120 "url": "https://subji.github.io/posts/2020/01/17/javascriptsnsshare", 121 "teaser":null},{ 122 "title": "Javascript SNS(Social Media) ê³µì ë°©ë² (How to share social media in javascript.)", 123 "excerpt":"| 문ì Eclipse ìì Spring íë¡ì í¸ë¥¼ Tomcat ììì ì¤ííììë ë¤ìê³¼ ê°ì ìë¬ íë©´ì´ ëìë¤. â404 Not Foundâ âThe origin server did not find a current representation for the target resource or is not willing to disclose that one exists.â | ê³¼ì ë³ë¤ë¥¸ ìë¬ ë¡ê·¸ê° ì¶ë ¥ì´ ëì¤ì§ ìì êµ¬ê¸ ê²ìì íìë¤. 문ì ë ê°ë¨íë¤. 컨í¸ë¡¤ë¬ê° ì´ë¤ ê²½ë¡ë¥¼ 못찾ëê². 컨í¸ë¡¤ë¬ 주ì 매íì´ ì못ëìëíì§ë§ ìëìë¤. ì°¾ìë³´ë Web Context Path ê° ì못ëììë¤. | ê²°ë¡ IDE ëë Context Path 를 ìì íì. ","categories": ["posts"], 124 "tags": ["git"], 125 "url": "https://subji.github.io/posts/2020/01/17/Theoriginserverdidnotfind.", 126 "teaser":null},{ 127 "title": "ì ê·ííì ì 리 (ì§íì¤..) Regex", 128 "excerpt":"| 문ì ë¹ë°ë²í¸ë¥¼ ê²ì¬íë ì ê·ìì ë§ëëë° ì´ë°ì ë° ìíì°©ì¤ë¥¼ 겪ë¤ê° ë´ì¹ê¹ì ìì¼ë¡ ì주 ì¬ì©ëë ì ê·ìì ì¬ê¸°ì ì 리í´ë³´ì íê³ ë§ë¤ê² ëìë¤. | ì ê·ííì ëª©ë¡ ë¹ë°ë²í¸ ì ê·ííì í¹ì문ì, ì문ì, ë문ì, ì«ì 1ê° ì´ì. í¹ì문ì, ì문ì, ë문ì, ì«ì ì¸ ë¤ë¥¸ ì ë ¥ ì ì¸ 8ì ì´ì, 16ì ì´í /^(?=.*[a-zA-z0-9])(?=.*[$`~!@$!%*#^?&\\\\(\\\\)\\-_=+])(?!.*[^a-zA-z0-9$`~!@$!%*#^?&\\\\(\\\\)\\-_=+]).{8,16}$/g ","categories": ["posts"], 129 "tags": ["regular expression"], 130 "url": "https://subji.github.io/posts/2020/02/11/regexpopular", 131 "teaser":null},{ 132 "title": "Git fatal: refusing to merge unrelated histories ì¤ë¥", 133 "excerpt":"| 문ì 기존ì ìë ë¡ì»¬ ëë í 리ì ê¹ ë¦¬í¬ì§í 리를 ì°ê²°íê³ Push ëë Pull ì í ë âfatal: refusing to merge unrelated historiesâ ì¤ë¥ê° ë°ìíì¬ ëìíì§ ììë¤. | ê³¼ì ì´ ì¤ë¥ë ë¡ì»¬ ëë í 리ì ê¹ ë¦¬í¬ì§í 리ì 기ë¡ì ë¹êµíìë ìì¤ì½ëì ì°¨ì´ê° ì¬í ê²½ì° ë³í© (Merge) ì ì¤ë¥ê° ë°ìí ê²ì ëë¹íì¬ ë°ììí¤ë ì
133¤ë¥ì´ë¤. | ê²°ë¡ ëê°ì§ ë°©ë²ì´ ìë¤. 첫ë²ì§¸, íì¬ ëë í 리ì ë´ì©ì ì ë¶ ìì íê³ ê¹ ë¦¬í¬ì§í 리를 Clone íë ë°©ë² ëë²ì§¸, âallow-unrelated-histories 를 git pull ë¤ì ì ë ¥íì¬ ë°ì ì¤ë ë°©ë²ì´ë¤. ëë²ì§¸ì ê²½ì° ê´ë ¨ìë ë ê°ì§ (ë¡ì»¬ ëë í 리, ê¹ ë¦¬í¬ì§í 리) ì 기ë¡ì ë³í©ì íì©íë ìµì ì´ë¤. $ git pull origin {branch name} --allow-unrelated-hitories ","categories": ["posts"], 134 "tags": ["git"], 135 "url": "https://subji.github.io/posts/2020/02/12/Git-fatalrefusing-to-merge-unrelated-histories-error", 136 "teaser":null},{ 137 "title": "Spring Boot (Maven) with React Study day 1", 138 "excerpt":" 목ì === Spring boot ì React 를 íì©íì¬ ê°ë¨í ì¹ íì´ì§ë¥¼ ë§ë¤ì´ë³´ê³ ì íë¤. 1. React JS ë? íì´ì¤ë¶ìì ê°ë°í ë¼ì´ë¸ë¬ë¦¬ UI 를 ì»´í¬ëí¸ííì¬ ì¬ì¬ì©ì ì©ì´í¨ Virtual DOM ì´ë¼ë ê°ë ì ì¬ì© JSX 1-1. Virtual DOM? UI ì ê°ìì DOM ê°ì²´ë¥¼ ë©ëª¨ë¦¬ì ì ì¥íê³ ì´ë¥¼ ë¼ì´ë¸ë¬ë¦¬ë¥¼ íµí´ ì¤ì DOM ì ë기í íë íë¡ê·¸ëë° ê°ë ì´ì ê°ì ê³¼ì ì ì¬ì¡°ì ì´ë¼ê³ íë¤. 기존ì DOM ì ê²½ì° íì´ì§ë¥¼ ë¡ëí ë ì¬êµ¬ì±ëë ê³¼ì ìì ì±ë¥ì´ ë®ìë°, Virtual DOM ì ê²½ì° ëì ì¸ íì´ì§ ë¡ë íê²½ìì ì´ì ê°ê³¼ íì¬ê°ì ë¹êµíì¬ ì°¨ì´ê° ìë ë¶ë¶ë§ ë³ê²½í´ì£¼ê¸° ë문ì ì±ë¥ì ì´ì ì´ ìë¤. ë¨, DOM ì ê²½ì° ë¬´ì¡°ê±´ ë린 ê²ì´ ìë ReFlow íìì´ ì¦ì íì´ì§ìì ì±ë¥ ì íê° ëì¤ë©° ê·¸ì¸ì ReDraw ìí© ëë ë°ì´í° ë³íê° ì ì íê²½ììë ì±ë¥ ì°¨ì´ê° ìë¤. Virtual DOM ì ê²½ì° ë°ì´í°ì ì¦ì ë³íê° ìë íì´ì§ìì ì ì©íë¤ë ì ì 주목íì¬ ì´ì ë§ê² ì¬ì©íë í¸ì´ ë«ë¤. 1-2. JSX? Javascript íì¥ ë¬¸ë² Javascript ì½ë ììì UI ê´ë ¨ìì ì í ë ëìì´ ëë©°, React ì ìë¬ ì²ë¦¬ìë ëì React ì ì´ë²¤í¸ ì²ë¦¬ë°©ì, ìíê° ë³íë ë°©ì, ë°ì´í°ë¥¼ íë©´ì ë³´ì¬ì¤ ë°©ìë± ë¡ì§ê³¼ ë§í¬ì ì ì»´í¬ëí¸ì ë¬¶ì´ ì¬ì© const name = 'Tester'; const element = <h1>Hellow, {name}</h1> // JSX, name ë³ì를 JSX ìì ë£ì´ì£¼ìë¤. ReactDOM.render(element, document.getElementById('root')); 모ë Javascript ííìì ì¬ì©í ì ìë¤. ```javascript function formatName (user) { return user.firstName + â â + user.lastName; } const user = { firstName: âFirstâ, lastName: âlastâ }; const element = ( Hello, {formatName(user)} ) ReactDOM.render(element, document.getElementById(ârootâ)); - JSX ë íëì Javascript ê°ì²´ì´ë©°, **if,for loop** ë¡ ì¬ì©ì´ ê°ë¥íë¤. - MarkUp ê³¼ ê°ì´ Attribute 를 íê·¸ìì ë£ì´ì¤ì ìì¼ë©°, ë³ì ìì ì½ì ì´ ê°ë¥íë¤. ë¨, ë³ì ì½ì ììë **\"\"** 를 ì ì¸íê³ ì¤ê´í¸ë¡ ê°ì¼ë¤. - Attribute ì´ë¦ì JSX ì í¹ì±ì Javascript ì ê°ê¹ê¸° ë문ì camelCase 를 ì¬ì©. ```javascript const element = <img src={user.avatarUrl}></img>; ReactDOM ì ë ëë§ ì 모ë ê°ì ì´ì¤ì¼ì´í(&lt, â¦) íë¯ë¡, ì í리ì¼ì´ì ìì ëª ìì ì¼ë¡ ìì±ëì§ ìì ë´ì©ì 주ì ëì§ ìëë¤. 모ë í목ì ë ëë§ì 문ìì´ë¡ ë³íëë©°, ì´ë¬í í¹ì±ì¼ë¡ XSS 공격ì ë°©ì§ í ì ìë¤. 2. React JS ì²´í 2-1. ì¸í NodeJS ì¤ì¹ : https://nodejs.org/ko/ ìì LTS ë²ì ì¼ë¡ ì¤ì¹íìë¤. NPM ì¤ì¹ : NodeJS 를 ì¤ì¹íë©´ NPM ì´ ìëì¼ë¡ ì¤ì¹ëë¤. ì¤ì¹ íì¸ $ node -v $ v10.16.0 $ npm -v $ 6.13.7 2-2. React íë¡ì í¸ ìì± NPX : NPM í¨í¤ì§ë¥¼ ì¤ííë ëª ë ¹ì´ create-react-app ë¡ íë¡ì í¸ ìì± $ npx create-react-app my-app ë¤ìê³¼ ê°ì íë¡ì í¸ êµ¬ì¡°ê° ìì± ânpm startâ ëª ë ¹ì´ë¡ ì¤í $ npm start http://localhost:3000 ì¼ë¡ ì ìíê³ ë¤ì íë©´ì´ ëì¤ë©´ ì ìì ì¼ë¡ ëìëê²ì´ë¤. ì 리 React JS ë ë¼ì´ë¸ë¬ë¦¬ì´ë¤. JSX, Virtual DOM ë±ì ì¬ì©íë¤. ì¤ì¹ë°©ë² ë¤ìì ì 리í ë´ì© Spring Boot ì¤ì¹ Spring Boot & React ì°ë ","categories": ["posts"], 139 "tags": ["spring boot","maven","react"], 140 "url": "https://subji.github.io/posts/2020/02/12/react-with-spring-1", 141 "teaser":null},{ 142 "title": "Git Log 보기", 143 "excerpt":"| 문ì git history 를 ë³´ê³ ì¶ì´ì¡ë¤. ê°ë¨íê² git log 를 ì´ì©íì¬ íì¸ í ì ìì§ë§ ì¢ ë ìíë ëë¡ ì¶ë ¥í ì ìì§ ììê¹ íë ìê°ì ì°¾ìë³´ê² ëìë¤. | ê³¼ì 구ê¸ê²ìì í
143ìë¤. | ê²°ë¡ git log ìì ë´ì©ì ë³´ë©´ commit ìì í´ì¬ ê°ì´ ì¶ë ¥ëëë° í´ë¹ ê°ìì ì 8ì리를 ì¬ì©í´ HEAD 를 ì´ëí ì ìë¤. $ git log $ commit 3089658e7229cd1d130a1a89095f8184b1eb53c5 Author: ... Date: ... âstat ìµì ì 기존 ë¡ê·¸ìì ì´ë¤ íì¼ì ìì ì´ ì¼ì´ë¬ëì§ë¥¼ ë§ë¶ì¬ì¤ë¤. $ git log --stat âpretty ìµì ì email, format, full, fuller, medium, oneline, raw, short ì¤ì ì´ ê°ë¥íë¤. ìëë í ì¤ë¡ ê°ëµíê² ë³´ì¬ì£¼ë ì¤ì ì ì¶ê°í ìì´ë¤. $ git log --pretty=oneline $ f617baa0cf80a331b6b97c6d1e792749399b5588 202002121305 ddbc68766b4a4ad1c0a291c178ad72f8876ba53e 202002111257 bb5b51571a010c86dbb7d9b292d44282e3cc8a6f 202002041210 22423b1e00444b7fa8274c66f28dc5de993baa26 202001301720 format ì¤ì ì ì»¤ë° ì 보를 format ì ë§ì¶ì´ ë³´ì¬ì¤ë¤. ìì¸í ë´ì©ì ìëì ìë¤. % â raw % D â ref name N â notes a â author details d â ref name in brackets g â reflog n â newline t â abbreviated tree hash B â raw body G â GPG details P â parent hash b â body e â encoding h â abbreviated commit hash p â abbreviated parent hash w â switch line wrapping C â color H â commit hash T â tree hash c â committer details f â sanitized subject m â mark s â subject x â hex code -p -{ì«ì} ì«ìë§í¼ ìì í ë´ì©ì ë ë³´ì¬ì£¼ë ìµì ì´ë¤. $ git log -p -10 $ commit 3089658e7229cd1d130a1a89095f8184b1eb53c5 (HEAD -> master, origin/master, origin/HEAD) Author: ... Date: Wed Feb 12 13:54:09 2020 +0900 202002121354 diff --git a/_posts/2020-02-12-Git fatal: refusing to merge unrelated histories error.md b/_posts/2020-02-12-Git fatal: refusing to merge unrelated histories error.md new file mode 100644 index 0000000..f05290e --- /dev/null +++ b/_posts/2020-02-12-Git fatal: refusing to merge unrelated histories error.md @@ -0,0 +1,23 @@ +--- +title: \"Git fatal: refusing to merge unrelated histories ì¤ë¥\" +date: 2020-02-12 ... ë§ì§ë§ì¼ë¡ âgraph ìµì ì GUI ê° ì íì§ ë¡ê·¸ë¥¼ ì¶ë ¥í´ì¤ë¤. ","categories": ["posts"], 144 "tags": ["git"], 145 "url": "https://subji.github.io/posts/2020/02/13/git-log", 146 "teaser":null},{ 147 "title": "Apache ì ëì ë°©ì", 148 "excerpt":"| Apache? ì°ì ìíì¹ë HTTP Server ë¡ ì¤íìì¤ì´ë¤. Nginx ë ë§ì´ ì¬ì©íì§ë§ ìì§ê¹ì§ë Apache ì ì¬ì©ë¥ ì´ ì°ì¸ì¸ê² ê°ë¤. ë³´íµì Web Application Server ì¸ í°ìº£ë±ìì ì¬ì©ëë ì ì 리ìì¤ (Static Resource) ë¤ì ì²ë¦¬í ë ì¬ì©íë¤. ëí 모ëì ì¶ê°íì¬ ì¬ì©í ìë ìë¤. ëíì ì¼ë¡ SSL ê³¼ ê°ì 모ëì ì¬ì©íì¬ HTTPS 를 ì ì©í곤íë¤. | Apache ì ëì ìíì¹ë ë¤ìí íê²½, íë«í¼ìì ì¬ì©í ì ìê² ì¤ê³ëìë¤. ê·¸ëì ê°ê°ì ë¤ë¥¸ íê²½, íë«í¼ì ëìí ì ìê² ëª¨ëíë ì¤ê³ë¥¼ ì¬ì©íë¤. ì´ì²ë¼ ì ì°í 모ëíë ì¤ê³ë°©ìì ìíì¹ììë ìì¤í 쪽ì¼ë¡ë íì¥íì¬ ë¤í¸ìí¬ì ì²ë¦¬ë¥¼ Root ì Child 를 ëì´ ë¶ì°ìí¤ë ë¤ì¤ì²ë¦¬ëª¨ë(Multi-Processing Modules, MPMs) 를 ì¬ì©íìë¤. ìíì¹ì ëì ë°©ìì http -V 를 ì ë ¥íì¬ íì¸í ì ìë¤. $ http -V Server version: Apache/2.2.15 (Unix) Server built: Jul 23 2014 14:17:29 Server's Module Magic Number: 20051115:25 Server loaded: APR 1.3.9, APR-Util 1.3.9 Compiled using: APR 1.3.9, APR-Util 1.3.9 Architecture: 64-bit Server MPM: Prefork threaded: no forked: yes (variable process count) Server compiled with.... ì ëª ë ¹ì´ì ëíê²°ê³¼ìì ë³´ë©´ MPM ì´ë¼ê³ ì í ë¶ë¶ ìì Prefork ê° íì¬ êµ¬ëì¤ì¸ ìíì¹ì ëìë°©ìì´ë©° ìíì¹ ì¤ì¹ í ì´ê¸°ì ëìë°©ìì´ë¤. | Apache ëì ë°©ìì ì¢ ë¥ 1. Prefork ìì² íëë¹ íëì íë¡ì¸ì¤ë¥¼ í ë¹íë ë°©ì ìì³¥ì ëí ì²ë¦¬ê° ê°ê°ì íë¡ì¸ì¤ìì ì²ë¦¬ëë¯ë¡, íëì íë¡ì¸ì¤ìì ì¤ë¥ê° ë°ìí´ë ë¤ë¥¸ íë¡ì¸ì¤ì ìí¥ì 미ì¹
148ì§ ìëë¤. ìì²ì ë§ìì§ì ë°ë¼ íë¡ì¸ì¤ê° ëì´ëë¯ë¡ ìì¤í ì ë©ëª¨ë¦¬, CPU ë± ë¦¬ìì¤ì ë¶íê° ì길 ê°ë¥ì±ì´ ëë¤. 2. Worker ê°ê°ì ìì²ì ëí´ íë¡ì¸ì¤ê° ì¬ë¬ê°ê° ìì±ëë ê²ì´ ìë íë¡ì¸ì¤ì ì°ë ë를 ì¬ë¬ê° ìì±íì¬ í ë¹íë ë°©ì ë¤ë¥¸ ìì²ì´ë¼ë ê°ì íë¡ì¸ì¤ë´ì ì°ë ë를 ì¬ì©íë¯ë¡ ë©ëª¨ë¦¬ë¥¼ ê³µì íê² ëë¤. ê·¸ë¬ë¯ë¡ ìë¡ ìí¥ì 미ì¹ê² ëë¤. Prefork ì ë¹í´ ì ì íë¡ì¸ì¤ë¥¼ ì¬ì©íë¯ë¡ ìì¤í 리ìì¤ì ë¶íê° ë¹êµì ì ë¤. ë¨ì¼ íë¡ì¸ì¤ìì ì¬ë¬ê°ì ì°ë ë를 ê´ë¦¬íë¯ë¡ íë¡ì¸ì¤ì ì¤ë¥ê° ë°ìíì ê²½ì°ì ê·¸ ìì ì°ë ëì ìí¥ì 미ì¹ë¤. í íë¡ì¸ì¤ì ì°ë ë íê³ì¹ì í´ë¹íë ëëì ì°ê²°ì´ ë°ìíë ìë²ìì ì í©íë¤. 3. Event nginx ì ë¹í´ ë¤ë¨ì´ì§ë í¼í¬ë¨¼ì¤ë¡ ì¸í´ Apache 2.4 ë²ì ë¶í° ì¶ê°ë ëì ë°©ì ì´ì ì ë ë°©ìì íë¡ì¸ì¤ê° ìì²ì ëí´ ì°ê²°ì´ ëì´ì§ê¸° ì ê¹ì§ ê³ì ì°ê²°ì ì ì§íìë¤. ì´ì ê°ì ë°©ìì ëë ì ìì ê²½ì°ìë ë§ì ìì¤í 리ìì¤ì ìì¤ì´ ì¤ê² ëë¤. Event ë°©ìììë ì´ë¬í 문ì 를 í´ê²°í기 ìí´ ìì²ì ë°ë ì°ë ë를 ë¶ë¦¬íì¬ ë¶ì°ííìë¤. | Apache ëì ì¤ì /etc/httpd/conf/httpd.conf íì¼ ëë /etc/apache2/conf/httpd.conf íì¼ìì ì¤ì ì´ ê°ë¥íë¤. ì íì¼ì ì´ì´ë³´ë©´ Prefork ì Worker ë°ì ìë³´ì´ëë°, Event ë°©ìì Worker ì ì¤ì ì´ ê°ê¸° ë문ì Event ë°©ìì ì ì©íê³ ì íë¤ë©´ Worker ì ì¤ì ì ìì í´ì£¼ë©´ëë¤. # prefork MPM # StartServers: number of server processes to start # MinSpareServers: minimum number of server processes which are kept spare # MaxSpareServers: maximum number of server processes which are kept spare # ServerLimit: maximum value for MaxClients for the lifetime of the server # MaxClients: maximum number of server processes allowed to start # MaxRequestsPerChild: maximum number of requests a server process serves <IfModule prefork.c> StartServers 8 MinSpareServers 5 MaxSpareServers 20 ServerLimit 256 MaxClients 256 MaxRequestsPerChild 4000 </IfModule> # worker MPM # StartServers: initial number of server processes to start # MaxClients: maximum number of simultaneous client connections # MinSpareThreads: minimum number of worker threads which are kept spare # MaxSpareThreads: maximum number of worker threads which are kept spare # ThreadsPerChild: constant number of worker threads in each server process # MaxRequestsPerChild: maximum number of requests a server process serves <IfModule worker.c> StartServers 4 MaxClients 300 MinSpareThreads 25 MaxSpareThreads 75 ThreadsPerChild 25 MaxRequestsPerChild 0 </IfModule> ","categories": ["posts"], 149 "tags": ["apache"], 150 "url": "https://subji.github.io/posts/2020/02/13/whatistheapache", 151 "teaser":null},{ 152 "title": "Spring Boot (Maven) with React Study day 2", 153 "excerpt":" 목ì === Spring boot ì React 를 íì©íì¬ ê°ë¨í ì¹ íì´ì§ë¥¼ ë§ë¤ì´ë³´ê³ ì íë¤. ì´ì ë´ì© ì´ì ìë React ì ê°ë¨í ìê°ì ëë¶ì´ 주ìê°ë ì¤ì ì¼ë¶ì¸ VirtualDOM ê³¼ JSX 를 ììë³´ìë¤. ê·¸ë¦¬ê³ create-react-app ëª ë ¹ì´ë¡ 첫 react ì±ì ì¤íìì¼ë³´ìë¤. Spring Boot (Maven) with React Study day 1 ì´ë² ìê°ìë Maven ë¹ëí´ì 기ë°ì¼ë¡í Spring Boot íê²½ì ì¤ì íê³ ì¤íì í´ë³´ì. 1. Spring Boot ? 기존ì Spring MVC ì ê²½ì°ìë ê°ë°ìê° ë루ë루 ì¬ì©ëë ë¼ì´ë¸ë¬ë¦¬ë¤ì ì¤ì ì ì§ì íëíë í´ì¤ì¼íë¤. 매 íë¡ì í¸ë§ë¤ ê°ì ìì ì ë°ë³µíë ê²ì ì°¸ì¼ë¡ ì§ê²¹ê³ ë ê³ ë ì¼ì´ë¤. ê·¸ëì ëì¨ê² Spring Boot ë¤. Spring Boot ë ê°ë¨í ë§í´ Java ë¡ ì´í리ì¼ì´ì ì ë¹ ë¥´ê³ ê°í¸íê² ê°ë°í ì ìëë¡ ëì주ë ë구ì´ë¤. 1-1. Why Spring Boot ? ê·¸ë ë¤ë©´ ê·¸ë¥ Spring MVC 를 ì¬ì©í´ì ë§ë¤ì´ë ëëë° êµ³ì´ Spring Boot ë¼ë ê²ì ë ë°°ìì ì ì©í´ì¼ ë ê¹? 기존ì Spring ê°ë°ë°©ìì ê³ ì§íë¤ë©´ ë§ë¦´ì ìë¤. í린건 ìëëê¹.. íì§ë§ ëì²ë¼ ë°ë³µëë ì¼ì ì§ê²¨ìíë¤ë©´ ëë 기본ì ì¸ ì¤ì ì ëë ììì í´ì¤¬ì¼ë©´ íë ê°ë°ìë¼ë©´ íë²ì¯¤ ì¬ì©í´ë³¼ë§íë¤. Spring Boot 를 ì¬ì©í¨ì¼ë¡ì¨ ì»ë ì´ì ì ë¤ìê³¼ ê°ë¤. Was 구ì¶ì´ í¸ë¦¬íë¤. Spring MVC ììë í°ìº£ì ë¤ì´ë¡ëí´ì IDE ì ì¶ê°íê³ ì´í리ì¼ì´ì ê³¼ ì°ê²°ë í´ì¼íë¤. íì§ë§ Spring Boot ì ê²½ì°ìë ë´ì¥ í°ìº£ì ì§ìíì¬ Spring Boot íë¡ì í¸ë¥¼ ìì±íê³ ì¤íë§íë©´ ëë¤. ì주 ì¬ì©ëë ëë ë¤ìí ì¢ ë¥ì ë¼ì´ë¸ë¬ë¦¬ë¤ì´ ì§ìëë¤. 기존ì Spring ë°©ìë³´ë¤ ê°ë³ê³ ì½ë¤. 기존ì Spring ììë xml íì¼ì
153 ë§ë¤ê³ ì¤ì ì ìì±íë ì´ë µê³ ë ê³ ë ë°©ìì´ìë¤. Spring Boot ììë ì´ë¬í ë³µì¡íê³ ë ì´ë ¤ì´ ë°©ìì .properties ëë .yml íì¼ì 모ì ì½ê³ ê°í¸íê² ìì±ì´ ê°ë¥íë¤. 2. Install Spring Boot Spring Boot 를 ì¤ì¹í´ë³´ê³ ì¤íìì¼ë³´ì. 2-1. Install VSCode VSCode ì¤ì¹ ìì ìì ì OS ì ë§ë VSCode 를 ë¤ì´ë¡ëíë¤. 2-1. Install Spring Plugin Ctrl + Shift + P (Mac ì ê²½ì° Cmd + Shift + P) 를 ëë¬ ìë íì¥íë¡ê·¸ë¨ì ì¤ì¹íë¤. Debugger for Java Language Support for Java Java Test Runner Maven for Java Spring Boot Tools Spring Initializer Java Spring Boot DashBoard Spring Boot Extentions ì¤ì¹ê° ìë£ëë©´ VSCode ì ì§ìì ë°ë¼ ì¬ììì íê³ ë¤ì Ctrl + Shift + P 를 ë른ë¤. ìì²ë¼ âSpring initializer: Generate a Maven Projectâ ë¡ íë¡ì í¸ë¥¼ ìì±íë¤. ì²ììë ì´ë¤ ì¸ì´ë¡ ê°ë°í ê²ì¸ì§ë¥¼ 묻ëë¤ (Java, Kotlin, Groovy) ëë Java ë§ ìê³ ì기 ë문ì Java 를 ì ííìë¤. ì´íìë GroupId ì ArtifactId 를 ì ë ¥íê³ ì§ííë¤. ê·¸ ë¤ìì¼ë¡ë ì¤ìí í목ì¸ë° ìì²ë¼ Dependency 를 ì ííë ì°½ì´ ëì¨ë¤. ì¬ê¸°ì ê°ë°ìê° ìíë Dependency 를 ì ííì¬ ì¶ê°í ì ìë¤. ì ë§ ê°í¸íì§ ììê°? ê·¸ë¦¬ê³ ë§ì§ë§ì¼ë¡ íë¡ì í¸ë¥¼ ìì±í ëë í 리를 ì ííë©´ íë¡ì í¸ê° ìì±ëë¤. 3. Run Spring Boot ì¼ìª½ ìë âSPRING-BOOT DASHBOARDâ 를 íì¸íë©´ ìì±í íë¡ì í¸ëª ì´ íìëë¤. íì´í ë²í¼ì ë르거ë íë¡ì í¸ëª ì ì°í´ë¦íì¬ run/debug 모ëë¡ ì¤íí ì ìë¤. ì 리 Spring Boot ë ê°í¸íë¤. Spring Boot íë¡ì í¸ ìì± ë° ì¤í ë¤ìì ì 리í ë´ì© Spring Boot & React ì°ë ","categories": ["posts"], 154 "tags": ["spring boot","maven","react"], 155 "url": "https://subji.github.io/posts/2020/02/14/react-with-spring-2", 156 "teaser":null},{ 157 "title": "Spring Boot íë ì¸ì ì ì´ ìë ìì±ì ì¸ì ì ì í´ì¼ íë ì´ì ", 158 "excerpt":"Field Inject ëì ìì±ì¬ 주ì ì í´ì¼íë ì´ì ? ìì¡´ì± ì£¼ì (Dependency Indject) ? íë¡ê·¸ëë°ìì 구ì±ììê°ì ìì¡´ ê´ê³ê° ìì¤ì½ë ë´ë¶ê° ìë ì¸ë¶ì ì¤ì íì¼ë±ì íµí´ ì ìëê² íë ëìì¸ í¨í´ì¤ì íë. ìì¡´ì± ? ì½ê² ë§í´ ìì§íë¤ë ííì¼ë¡ ë°ì ë¤ì¼ ì ìë¤. B í´ëì¤ê° A í´ëì¤ë¥¼ ìì¡´íê³ ìê³ , A í´ëì¤ê° ë³íê² ëë¤ë©´ B í´ëì¤ ìì ë³íê² ëë ê²ì ë§íë¤. ë°ë¼ì í´ëì¤ê° ìí¥ì ë§ì´ ë°ì ê²½ì° ìì¡´ì±ì´ ëë¤ê³ íë©°, ìí¥ì ì ê² ë°ì ê²½ì° ìì¡´ì±ì´ ë®ë¤ê³ íë¤. 주ì ? ë´ë¶ê° ìë ì¸ë¶ ì¤ì íì¼ë±ì íµí´ ê°ì²´ë¥¼ ìì±íê³ ë¬´ì¸ê°ë¥¼ ë£ì´ì£¼ë ê²ì ë§íë¤. ìì¡´ì±ì´ ëì ê²½ì° public Class A { public String str = \"DI test\"; public void printA () { System.out.println(str); } } public Class B { public void printB () { A a = new A(); a.printA(); } } B b = new B(); b.printB(); ìì ì½ëìì ë³´ë¯ì´ í´ëì¤ B ë í´ëì¤ A ì ìì¡´ì±ì´ ê°í ííì´ë¤. 무ì¨ë§ì¸ê°íë©´ í´ëì¤ B ì printB í¨ìë í´ëì¤ A ì printA í¨ì를 í¸ì¶í´ì¤ë¤. ë°ë¼ì printA ì í¨ìì ë³íì ë°ë¼ printB í¨ìì ê°ì´ ë¬ë¼ì§ê² ëë¤. ê·¸ë¬ë¯ë¡ í´ëì¤ B ê° í´ëì¤ A ì ìì¡´í´ìë¤ê³ ë³¼ ì ìë¤. ì 리íë©´ ìì²ë¼ í´ëì¤ B ë´ë¶ìì í´ëì¤ A 를 ì§ì ìì±íê³ ìê³ í´ëì¤ A ê° ìë ë¤ë¥¸ ê°ì²´ë¡ ë³ê²½íê³ í ëì í´ëì¤ B 를 ìì í´ì¼ íë íí를 í´ëì¤ B ê° í´ëì¤ A ì ìì¡´ííê³ íë©° ê°í ê²°í© ì´ë¼ê³ íë¤. ìì¡´ì±ì´ ë®ì ê²½ì° ìì ê°ì ê´ê³ììë ìì ê°ì²´ì¸ B ê° íì ê°ì²´ì¸ A ì ìì¡´íê³ ìë¤. íì§ë§ ì´ë´ ê²½ì° ìì¡´ëê° ëìì ¸ ì¬ì¬ì©ì±, ë¨ìí ì¤í¸ ë±ì ìì¡´ì± ì£¼ì ì ì¥ì ì´ ë¬´ìíê² ëë¤. ê·¸ëì ìì¡´ë를 ë®ì¶°ì¼ íëë° ê·¸ ë°©ë²ì¼ë¡ë ìì¡´ ê´ê³ ìì ìì¹ì´ ìë¤. ìì¡´ê´ê³ ìì ìì¹ ì´ë ìì ê°ì²´ê° íì ê°ì²´ì ìì¡´íë ê´ê³ë¥¼ ë¶ë¦¬íì¬ ê°ê°ì 모ë ííë¡ ëëë ê²ì´ë¤. ì¦, ì íµì ì¸ ìíì ê´ê³ì ìì¡´ì±ì ë°ì (ìì ) ìí´ì¼ë¡ì¨ ìì ê°ì²´ê° íì ê°ì²´ì 구íì¼ë¡ë¶í° ë 립ëê² íë ê²ì´ë¤. ì´ ìì¹ì ë¤ìê³¼ ê°ì ë´ì©ì ê°ì§ê³ ìë¤. 첫째. ìì ê°ì²´ë íì ê°ì²´ì ìì¡´í´ìë ìëê³ ëª¨ë ì¶ìíì ìì¡´í´ì¼ íë¤. ë쨰. ì¶ìíë ì¸ë¶ ì¬íì ìì¡´í´ìë ìëê³ ì¸ë¶ì¬íì´ ì¶ìíì ìì¡´í´ì¼ íë¤. -> ì¸í°íì´ì¤ë¥¼ íì©í ìì¡´ì± ì£¼ì ë°©ë² ì¸í°íì´ì¤ (Setter) 주ì ? ìì¡´ì± ì£¼ì ì ê°ì²´ ìì±ì ì¸ë¶ë¡ë¶í° 주ì ë°ë ê²ì´ë¤. ê·¸ ì¤ ìì¡´ì±ì ë®ì¶ ì ìë ë°©ë²ì¤ì íë
158ì¸ ì¸í°íì´ì¤ 주ì
ì ë¤ìê³¼ ê°ì´ ì¬ì©í ì ìë¤. public interface TestInterface { void print (String text); } public class A implements TestInterface { @Override public void print (String text) { System.out.println(\"A: \" + text); } } public class B implements TestInterface { @Override public void print (String text) { System.out.pritln(\"B: \" + text); } } public class MainClass () { private TestInterface testInterface; public void setTestInterface (TestInterface testInterface) { this.testInterface = testInterface; } public void printText () { TestInterface.print(\"Test\"); } } MainClass m = new MainClass(); TestInterface interA = new A(); m.setTestInterface(interA); m.printText(); ì´ì²ë¼ 구ííì§ë§ ëê°ê° ë¶ì¡±íë¤. ìì§ MainClass ê°ì²´ì A ê°ì²´ë ìì¤ì½ë ë´ë¶ìì ìì±ì í´ì£¼ê³ ìë¤. ì´ë¥¼ ë ë¤ì ë°ì¼ë¡ ë¶ë¦¬íë ¤ë©´ xml íì¼ì ì¬ì©íì¬ bean ì¼ë¡ ê°ì²´ë¥¼ ìì±í´ì£¼ë ë°©ë² ëë Spring Annotation ì ì¬ì©íë ë°©ë²ì´ ìë¤. Bean ì ì¬ì©íì¬ ì£¼ì
íë ë°©ë²ì ë¤ìê³¼ ê°ë¤. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <beans xmlns=\"http://www.springframework.org/schema/beans\" \txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \txsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\"> <!-- A a = new A(); --> <bean id=\"A\" class=\"A.class\" /> <!-- B b = new B(); --> <bean id=\"B\" class=\"B.class\" /> <!-- MainClass m = new MainClass(); --> <bean id=\"Main\" class=\"Main.class\"> <!-- m.setTestInterface(interA) --> <property name=\"interA\" ref=\"A\" /> </bean> </beans> ì´ì í¸ì¶íë 쪽ììë ë¤ìê³¼ ê°ì´ ì¬ì©íë¤. public static void main (String args) { ApplicationContext ctx = new ClassPathXmlApplicationContext(\"test.xml\"); MainClass m = (MainClass) ctx.getBean(\"Main\"); m.printText(); } Interface 주ì
ë°©ë² (Setter 를 ì´ì©í 주ì
ë°©ë²) ì ì´ì²ë¼ ê°ì²´ë¥¼ ë¶ë¦¬íì¬ íì ê°ì²´ì ë©ìë를 ê°ì ¸ì¤ë ìì ê°ì²´ì ìì¡´ì±ì ë®ì¶°ì¤ë¤. íì§ë§ 문ì ì ì íì ê°ì²´ë¥¼ ì¤ë¡ì§ Setter ë¼ë ë©ìëë¡ë§ ë°ì¼ë¯ë¡ ìì ê°ì²´ë ë¹ ê°ì²´ë¡ ìì±ì´ ë ìë ìë¤ë ì ì´ë¤. ì´ë´ê²½ì° ìì ê°ì²´ìì íì ê°ì²´ ë©ìë를 í¸ì¶íë ë©ìë를 í¸ì¶íê² ëë©´ Null Pointer ì¤ë¥ê° ë°ìíë¤. ì´ë ìì ê°ì²´ì ë©ìë를 ë¶ë¬ì¤ë©´ ì무ê°ë ì ìê° ëì§ ììì± í¸ì¶ì´ ëë¯ë¡ NullPointerException ì´ ë°ìíë¤. ì´ë¥¼ í´ê²°í ë°©ë²ì´ íìíë° ê·¸ê²ì ë¤ìì ì´í´ë³¼ ìì±ì를 íµí 주ì
ì´ë¤. ìì±ì 주ì
? public interface TestInterface { void print (String text); } public class A implements TestInterface { @Override public void print (String text) { System.out.println(\"A: \" + text); } } public class B implements TestInterface { @Override public void print (String text) { System.out.pritln(\"B: \" + text); } } public class MainClass () { private TestInterface testInterface; public MainClass (TestInterface testInterface) { this.testInterface = testInterface; } public void printText () { TestInterface.print(\"Test\"); } } MainClass m = new MainClass(new A()); m.printText(); ìì±ì를 íµí 주ì
ì ì ì½ë ë´ì©ì²ë¼ ìì±ìì íë¼ë¯¸í°ë¡ íì ê°ì²´ë¥¼ ëê²¨ì£¼ì§ ìë ì´ì Null ìë¬ë ë°ìí ì ìë¤. ì¶ê°ë¡ final ì ì¬ì©í ì ìë¤. final ì ê°ì²´ì ìì±ê³¼ ëìì ì´ê¸°íê° ëë¯ë¡ setter í¨ì를 íµí´ìë ì´ê¸°íê° ë ì ìë¤. ì¤íë§ììë ì¸í°íì´ì¤ë¥¼ íµí 주ì
, ìì±ì를 íµí 주ì
ì¸ìë íë ì¸ì ì
ì´ë¼ë ì¸í°íì´ì¤ë¥¼ íµí 주ì
ê³¼ ë¹ì·í ëìì¼ë¡ ì´ë£¨ì´ì§ë ë°©ë²ì´ ìë¤. íí ì¬ì©ëë @Autowired ë¡ ë¶ë¬ì¤ë ë°©ìì´ë¤. ê·¸ë¬ë©´ ì ì¸í°íì´ì¤ 주ì
(ìì ì (setter) 주ì
) ë§ê³ ìì±ì 주ì
ì ì¶êµ¬í´ì¼ëëê±´ê° ? ì¤íë§ììë ìí참조ë¼ëê² ìë¤ê³ íë¤. ê°ì²´ A ì ë©ìëìì ê°ì²´ B ì ë©ìë를 í¸ì¶íê³ ê°ì²´ B ì ë©ìëìì ê°ì²´ A ì ë©ìë를 í¸ì¶íë©° ìíëëê²ì¸ë°, ì¸í°íì´ì¤ 주ì
(ìì ì 주ì
) ì ê²½ì° ë°íì íì ë¹ì¦ëì¤ ë¡ì§ììì 주ì
ì´ ì´ë¤ì§ë¯ë¡ stackoverflow ê° ë ëê¹ì§ ê³ì ë°ë³µíê² ëë¤. ë°ë©´ ìì±ì 주ì
ì ê²½ì° ë°íììì ê°ì²´ ìì±ì´ ì´ë¤ì§ê¸° ë문ì ì´ë ìíì°¸ì¡°ê° ì¼ì´ë ê²½ì° ê³§ë°ë¡ ìë¬ë¥¼ ì¶ë ¥íë©° ì¤íì´ ëì§ ìëë¤. ì´ì²ë¼ ë°íìì´ ìí참조ìì ì리며 ì¤íì´ ëì§ ìë ê²ê³¼ ì무ê²ë ëª¨ë¥´ê³ ì¤íì´ ëì´ ì¤ë¥ë¥¼ ë±ë ê²½ì° ì´ë ë°©ë²ì´ ì¢ ë ì íí ìë¬ë¥¼ íì
í기ì ì¬ìê²ì¸ê°ì ëí´ ìê°í´ë³´ë©´ ìì±ì 주ì
ì ì¶êµ¬íëê² ë§ëê² ê°ë¤. ìì±ì 주ì
ì í
ì¤í¸ ì½ë ìì±ìë ì©ì´íë¤. ìì ì 주ì
ì ê²½ì° ì¤íë§ì IOC 컨í
ì´ë (ì ì´ì ìì (ìì¡´ì± ìì )) ìì ê°ì²´ë¥¼ ìì±í´ì£¼ë ë°©ìì´ë¤. ê·¸ë¬ë¯ë¡ í
ì¤í¸ ì ì¬ì©ìê° íìí ê°ì²´ë¥¼ ìì±íì¬ ì½ë를 ìì±í ì ìë¤. ê·¸ëì ìì ì 주ì
ì´ ë ë©ì르를 ë¨ì í
ì¤í¸ë¥¼ í´ë³´ë©´ NullPointerException ì´ ë°ìíë¤. ìì±ì 주ì
ì ê²½ì°ìë ê°ì²´ë¥¼ ìì±í´ì 주ì
í´ì£¼ê¸° ë문ì Null ìë¬ê° ëì§ ìê³ , ììí í
ì¤í¸ê° ê°ë¥íë¤. ì 리 DI ë ìì¡´ì±ì ë®ì¶ë í¸ì´ ì¢ë¤. DI ë ìì ì 주ì
, ìì±ì 주ì
ì¼ë¡ ëëë¤. ìì ì 주ì
Setter í¨ì를 íì©í ë°©ì íë ì¸ì ì
ëí ê°ì ë°©ìì¼ë¡ ëì ìì±ì 주ì
ìì±ìì ê°ì²´ë¥¼ ë겨주ë ë°©ì ìì±ì 주ì
ì ì´ì ìì¡´ê´ê³ ì¤ì ì ì 무 íì¸ ê°ë¥ final ì ì¸ ê°ë¥ ìí참조 ë°ê²¬ì ì©ì´ì± í
ì¤í¸ ì½ë ìì±ì´ ì©ì´ ìì¼ë¡ë ì
158ì±ì 주ì ì ì¬ì©í´ë³´ëë¡ íì. ","categories": ["posts"], 159 "tags": ["spring boot"], 160 "url": "https://subji.github.io/posts/2020/02/17/whatistheDI", 161 "teaser":null},{ 162 "title": "Java equals & hashCode", 163 "excerpt":"| 문ì ì¤íë§ ìí리í°ë¥¼ ì ì©íë¤ê° Entity ê°ì²´ì ê°ì ë¹êµíë ê³¼ì ìì equals ì hashCode ë©ìë를 ì¬ì ì í´ì¼ íë¤ë ê²ì ììë¤. ê·¸ë¬ë©´ ë ë©ìë를 ì¬ì ìíë ì´ì ë 무ìì¼ê¹..? | equals equals ë ê°ì²´ë¥¼ ë¹êµíë¤. ì¬ì´ ìë¡ Java ìì 문ìì´ ë¹êµê° ìë¤. String a = \"test\"; if (a.equals(\"test\")) { System.out.println(\"Same!\"); } else { System.out.println(\"Wrong!\"); } ì ì½ëì²ë¼ 문ìì´ì ëí´ìë ì½ê² ë¹êµê° ê°ë¥íë¤. íì§ë§ ê°ì²´ë¥¼ ìë¡ë¤ë©´ ì´ë í ê¹? class A { private String a; public A (String a) { this.a = a; } } class Main { public static void main (String args[]) { A a = new A(\"a\"); A aa = new A(\"a\"); System.out.println(a.equals(aa)); // false } } ì ì½ëììë³´ë©´ A ë¼ë ê°ì²´ì ì´ê¸°ê°ì¼ë¡ âaâ ë¼ë 문ìì´ì ìì±íê³ ìë¤. ê·¸ë¬ë©´ ë ê°ì²´ë ê°ìê²ì¼ê¹? ê²°ë¡ ì ìëë¤ ê°ì²´ë ìì±ë¨ìë°ë¼ ìë¡ ë¤ë¥¸ 주ìê°ì ê°ì§ê³ ìë¤. ê·¸ë¬ë¯ë¡ ê°ì ê°ì¼ë¡ ì´ê¸°íëì´ë ê°ì ìê° ìë¤. ê·¸ë¬ë©´ ê°ì ê°ì ê°ì§ë ë ê°ì²´ë¥¼ ê°ë¤ê³ ë¹êµí ì ììê¹? ê·¸ ë°©ë²ì ê°ì²´ë´ì equals ë©ìë를 Override íëê²ì´ë¤. class A { private String a; public A (String a) { this.a = a; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } A other = (A) obj; if (a == null) { if (other.a != null) { return false; } } else if (!a.equals(other.a)) { return false; } return true; } } class Main { public static void main (String args[]) { A a = new A(\"a\"); A aa = new A(\"a\"); System.out.println(a.equals(aa)); // true } } equals ë©ìë를 A ê°ì²´ë´ì Override íì¬ êµ¬ííìë¤. ì½ë를 ë³´ë©´ ê°ì²´ë¥¼ íë¼ë¯¸í°ë¡ ë°ê³ ê·¸ ê°ì²´ê° ì§ê¸ì ê°ì²´ ê·¸ ìì ì´ë©´ true 를 ë°ííë¤. ëí íë¼ë¯¸í° ê°ì²´ì ê°ì´ null ì´ê±°ë getClass ì í¨ìê°ì´ ë¤ë¥´ë©´ false 를 ë°ííë¤. ì ê³¼ì ì íµê³¼íê³ ëë©´ ì¤ìí ë¶ë¶ì¸ ë¹êµí ê°ì²´ì 기존 ê°ì²´ì ë¹êµë¥¼ ì§ííë¤. ì½ëììë a ë¼ë ê°ì²´ì íë¡í¼í°ë¡ ë¹êµë¥¼ íë¤. a íë¡í¼í°ë null ì´ê±°ë ë¹êµê°ì²´(íë¼ë¯¸í° ê°ì²´) ì ê°ì´ ë¤ë¥´ë©´ false 를 ë°ííë¤. ì ì¡°ê±´ë¤ì í´ë¹ì´ ëì§ ìëë¤ë©´ ìµì¢ ì ì¼ë¡ ê°ì ê°ì²´ë¼ê³ íë¨íì¬ true 를 ë°ííë¤. ì ë´ì©ì ë³´ë©´ ì²ìì ìì±ëë ê°ì²´ë 주ìê°ì´ ë¤ë¥´ê¸°ë문ì ìì°í ë¤ë¥¸ ê°ì²´ë¼ê³ ë§íëë°, ì íí ë³´ë©´ ì ê²ë ë¤ë¥¸ ê°ì²´ê° ìëê°ì? ë¼ê³ í ì ìë¤. ë¬¼ë¡ ë§ë ë§ì´ì¼ ìì°í ë³´ë©´ ë¤ë¥¸ ê°ì²´ì´ë¤. íì§ë§ ê°ì²´ ì체를 íëì ê°ì¼ë¡ ë³´ê³ ë¹êµíê³ ì´ê²ì í ëë¡ ë¤ë¥¸ ì¶ê°, ìì , ìì ë±ì 기ë¥ì ìíí기 ìí´ìë ê°ì²´ê° ì§ë íë¡í¼í°ë¥¼ ë¹êµíì¬ ê°ì ê°ì²´ì¸ì§ì ì¬ë¶ë¥¼ íë¨íëê²ë íëì ë°©ë²ì¸ ê²ì´ë¤. ì´ìê°ì ë°©ë²ì ì£¼ë¡ JPA ì¬ì©ì Entity ê°ì²´ë¥¼ ë¹êµíê³ ì í ë ì¬ì©ëë¤. | hashCode hashCode ëí ììì ì¤ëª í equals ì ê°ì ë§¥ë½ì´ë¤. ê°ì²´ì ë´ì©ì¼ë¡ ë ê°ì²´ê° ê°ìì§ë¥¼ íë¨íë ë©ìëì´ë¤. ì¶ê°ì ì¼ë¡ ë§íìë©´ equals ê°ì²´ë¥¼ ì¬ì ìí ëë ë°ëì hashCode ë©ìëë¡ ì¬ì ìí´ì£¼ì´ì¼ íë¤. ê·¸ ì´ì ë ë¤ìê³¼ ê°ì hashCode ê·ì½ì¼ë¡ íì¸í ì ìë¤. íë¡ê·¸ë¨ ì¤í ì¤ ê°ì ê°ì²´ì hashCode 를 ì¬ë¬ë² í¸ì¶íë ê²½ì°, equals ê° ì¬ì©íë ì ë³´ë¤ì´ ë³ê²½ëì§ ìëë¤ë©´, ì¸ì ë ëì¼í ê°ì´ ë°íëì´ì¼ íë¤. íì§ë§ íë¡ê·¸ë¨ì´ ì¢ ë£ëì´ ë¤ì ì¤í ë ê²½
163ì°ìë ê·¸ë´ íìê° ìë¤. equals ë©ìëê° ê°ë¤ê³ íì ëë©´ hashCode ë©ìëë ê°ìì¼ íë¤. equals ë©ìëê° ë¤ë¥´ëë¼ë hashCode ê°ì´ ê°ì ìë ë¤ë¥¼ ìë ìì§ë§ hashTable ì ì±ë¥ í¥ìì ìí´ìë ë¤ë¥¸ ê²ì´ ë«ë¤. ì ìì ì½ëìì ìì±í equals ë©ìëê° í¬í¨ë ê°ì²´ë ê°ì ì 보를 ê°ì§ê³ ì기ë문ì ê°ì ê°ì²´ë¼ê³ íì ëìë¤. íì§ë§ ìì§ê¹ì§ ìì°í ë¤ë¥¸ ê°ì²´ì´ë¤. ì´ë ìì ë§í hashCode ì ëë²ì¨° ê·ì½ì ìë°íë ë´ì©ì´ë¤. class A { private String a; public A (String a) { this.a = a; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } A other = (A) obj; if (a == null) { if (other.a != null) { return false; } } else if (!a.equals(other.a)) { return false; } return true; } } class Main { public static void main (String args[]) { A a = new A(\"a\"); A aa = new A(\"a\"); System.out.println(a.hashCode()); // 918221580 System.out.println(aa.hashCode()); // 2055281021 } } ê·¸ë¬ë¯ë¡ ë ëª ííê² ê°ì ê°ì²´ìì íì í기 ìí´ìë hashCode ë©ìë를 ì¬ì ì í´ì£¼ì´ì¼ íë¤. ë§ì½ HashMap, HashSet, HashTable ë±ì ê°ì²´ë¥¼ ì¬ì©í ë ì´ ê°ì²´ë¤ì ìì ì equals ë§ ì¬ì ìí ê°ì²´ë¥¼ ì¶ê°íë¤ë©´ ìë¡ ë¤ë¥¸ hashCode 를 ê°ì§ë ê°ì²´ì´ë¯ë¡ ê°ì ê°ì²´ê° ì¤ë³µëì´ ì¶ê°ëê² ëë¤. ê·¸ë¬ë¯ë¡ hashCode ê°ì ë¹êµ í´ì¤ ì ìê² hashCode ë©ìë를 ì¬ì ì í´ì£¼ì´ì¼ íë¤. class A { private String a; public A (String a) { this.a = a; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } A other = (A) obj; if (a == null) { if (other.a != null) { return false; } } else if (!a.equals(other.a)) { return false; } return true; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((a == null) ? 0 : a.hashCode()); return result; } } class Main { public static void main (String args[]) { A a = new A(\"a\"); A aa = new A(\"a\"); System.out.println(a.hashCode()); // 128 System.out.println(aa.hashCode()); // 128 } } ê²°ë¡ ì ì¼ë¡ VO, DTO, Entity ë±ì ê°ì²´ë¥¼ ë¹êµíê³ ì í ëìë ìì²ë¼ equals, hashCode ë©ìë를 ì¬ì ì í´ì£¼ì´ì¼íë¤. ì¶ê°ì ì¼ë¡ lombok ì ì¬ì©í ê²½ì°ìë @EqualsAndHashCode Annotation ì ì¬ì©íì¬ ì½ë를 ê°ë¨íê² í ì ìë¤. ","categories": ["posts"], 164 "tags": ["java"], 165 "url": "https://subji.github.io/posts/2020/02/19/whyusehashcodeandequals", 166 "teaser":null},{ 167 "title": "ì주 ì°¾ìë³´ë? ì¬ì©ëë ìë°ì¤í¬ë¦½í¸ ì½ë 모ì (Useful Javascript code)", 168 "excerpt":"| ì주 ì°¾ìë³´ë ìë°ì¤í¬ë¦½í¸ ì½ë 모ìì´ë¤. Javascript ë²ì /** * ì«ìê°ì´ ì ìì¸ì§ ì¬ë¶ë¥¼ ë°ííë í¨ì * @param { Number } value : ì«ì ê° * @return { Boolean } : ì ì(true), ì ì ìë ê²½ì°(false) */ function isInt(value) { let parse = parseFloat(value); return !isNaN(value) && (parse | 0) === parse; } /** * ì ìë¶ì 3ì리 ë§ë¤ ',' 를 ì²´í¬í´ì£¼ë í¨ì * @param { Integer } value : ì ì ëë ì¤ì ê° * @return { String } : ',' ê° ì ì©ë ì¤ì ëë ì ì 문ìì´ ê° */ function addComma(value) { if (!isInt(value)) { let integer = value | 0; let floater = (value - integer).toFixed(2).toString().substring(1); return addComma(integer) + floater; } else { return value.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','); } } /** * ì¤ë ê³ì° ë©ìë * @param { Integer }
168 year : Date ê°ì²´ì ë
ë */ Date.isLeafYear = function (year) { return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); }; /** * ì¤ë
ê³ì° ì¸í°íì´ì¤ */ Date.prototype.isLeafYear = function () { return Date.isLeafYear(this.getFullYear()); }; /** * ì¤ë
ì í¬í¨í í´ë¹ ë
ëì ê° ìì를 ë°ííë ë©ìë * @param { Integer } year : Date ê°ì²´ì ë
ë * @param { Integer } month : Date ê°ì²´ì ì (0-11) */ Date.getDaysInMonth = function (year, month) { return [31, (Date.isLeafYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; }; /** * ì¤ë
ì í¬í¨í í´ë¹ ë
ëì ê° ìì를 ë°ííë ì¸í°íì´ì¤ * @param { Integer } year : Date ê°ì²´ì ë
ë * @param { Integer } month : Date ê°ì²´ì ì (0-11) */ Date.prototype.getDaysInMonth = function (year, month) { return Date.getDaysInMonth(this.getFullYear(), this.getMonth()); }; /** * Date ê°ì²´ìì N ê°ìë§í¼ì ì´ëíë ë©ìë * @param { Integer } n : ë³ê²½í ê°ì ì */ Date.prototype.changeMonth = function (n) { let day = this.getDate(); this.setDate(1); this.setMonth(this.getMonth() + n); this.setDate(Math.min(day, this.getDaysInMonth())); return this; }; Date.prototype.changeWeek = function (n) { let day = this.getDate(); this.setDate(day + (7 * n)); return this; }; /** * YYYY(구ë¶ì) MM(구ë¶ì) DD ì ê°ì ííë¡ ë ì§ë¥¼ ë°ííë ë©ìë, 기본 구ë¶ìë '-' * @param { String } delimiter : 구ë¶ì */ Date.prototype.formatDate = function (delimiter) { let d = (this.getDate() > 9 ? '' : '0') + this.getDate(); let m = ((this.getMonth() + 1) > 9 ? '' : '0') + (this.getMonth() + 1); delimiter = delimiter || ''; return this.getFullYear() + delimiter + m + delimiter + d; }; /** * YYYY-MM-DD hh24:mi:ss ë³í */ Date.prototype.formatDateYMDHMS = function () { let d = (this.getDate() > 9 ? '' : '0') + this.getDate(); let m = ((this.getMonth() + 1) > 9 ? '' : '0') + (this.getMonth() + 1); let h = (this.getHours() > 9 ? '' : '0') + this.getHours(); let mi = (this.getMinutes() > 9 ? '' : '0') + this.getMinutes(); let s = (this.getSeconds() > 9 ? '' : '0') + this.getSeconds(); return this.getFullYear() + \"-\" + m + \"-\" + d + \" \" + h + \":\" + mi + \":\" + s; }; /** * ë ì§ ë³ê²½ í¨ì * @param { Date } beforeDate : ê¸°ì¤ ë ì§ * @param { int } addNumber : ëíê±°ë ëº ê° * @param { String } type : ë
(y) ì(m) ì¼(d) */ function dateAddDel(beforeDate, addNumber, type) { var yy = parseInt(beforeDate.substr(0, 4), 10); var mm = parseInt(beforeDate.substr(5, 2), 10); var dd = parseInt(beforeDate.substr(8), 10); if (type == \"d\") { //ì¼ d = new Date(yy, mm - 1, dd + addNumber); } else if (type == \"m\") { //ì d = new Date(yy, mm - 1, dd + (addNumber * 31)); } else if (type == \"y\") { //ë
d = new Date(yy + addNumber, mm - 1, dd); } yy = d.getFullYear(); // 19를 2019ë¡ ë³ê²½ mm = d.getMonth() + 1; mm = (mm < 10) ? '0' + mm : mm; //ì ë³ê²½ +1 íë ì´ì ë ìë°ì¤í¬ë¦½í¸ìì 0ì´ 1ìì´ë¼ dd = d.getDate(); dd = (dd < 10) ? '0' + dd : dd; //10ì¼ ì´ì ì´ë©´ ì«ì ì릿ì ë§ì¶ê¸° ìí¨ return yy + '-' + mm + '-' + dd; } /** * í´ë¹ ë¬ì 첫째ë ë¡ ë³í */ Date.prototype.getFirstDay = function () { return new Date(this.getFullYear(), this.getMonth(), 1); }; /** * í´ë¹ ë¬ì ë§ì§ë§ë ë°í */ Date.prototype.getLastDay = function () { return new Date(this.getFullYear(), this.getMonth() + 1, 0); }; /** * ë ì§ì 주차를 ë°ííë ë©ìë * @param { String } dowOffset */ Date.prototype.getWeek = function (dowOffset) { /*getWeek() was developed by Nick Baicoianu at MeanFreePath: http://www.meanfreepath.com */ dowOffset = typeof (dowOffset) == 'int' ? dowOffset : 0; //default dowOffset to zero let newYear = new Date(this.getFullYear(), 0, 1); let day = newYear.getDay() - dowOffset; //the day of week the year begins on day = (day >= 0 ? day : day + 7); let dayNum = Math.floor((this.getTime() - newYear.getTime() - (this.getTimezoneOffset() - newYear.getTimezoneOffset()) * 60000) / 86400000) + 1; let weekNum; //if the year starts before the middle of a week if (day < 4) { weekNum = Math.floor((dayNum + day - 1) / 7) + 1; if (weekNum > 52) { let nYear = new Date(this.getFullYear() + 1, 0, 1); let nDay = nYear.getDay() - dowOffset; nDay = nDay >= 0 ? nDay : nDay + 7; /*if the next year starts before the middle of the week, it is
168week #1 of that year*/ weekNum = nDay < 4 ? 1 : 53; } } else { weekNum = Math.floor((dayNum + day - 1) / 7); } return weekNum; }; /** * ë ì§ì ì¼ë¨ì ë³ê²½ í¨ì */ Date.prototype.changeDate = function (num) { this.setDate(this.getDate() + num); return this; }; /** * í´ë¹ ë ì§ê° ìí 주ì ì¼ìì¼ ~ í ìì¼ ë ì§ë¥¼ 문ìì´ë¡ ë°ííë í¨ì */ Date.prototype.getFirstAndLastOfWeek = function (precision) { let day = this.getDay(); let sunday = 0 - day; let saturday = 6 - day; let first = new Date(this.getFullYear(), this.getMonth(), this.getDate() + sunday); let last = new Date(this.getFullYear(), this.getMonth(), this.getDate() + saturday); if (precision) { first = this > first ? this : first; last = new Date() < last ? new Date() : last; } return { first: first, last: last }; }; /** * YYYYMMDD ííì 문ìì´ ë ì§ë¥¼ Date ê°ì²´ë¡ ë³ííë í¨ì * @param { String } dateStr */ function dateParser(dateStr) { dateStr = dateStr.replace(/\\W/g, ''); let y = dateStr.substring(0, 4); let m = dateStr.substring(4, 6); let d = dateStr.substring(6, 8); return new Date(y, m - 1, d); } /** * 문ìì´ì ê°ë¡, ì¸ë¡ 길ì´ë¥¼ ë°ííë í¨ì * @param { Integer } fontSize */ String.prototype.getMeasureText = function (fontSize) { let $temp = document.getElementById('measureTextWidth'); let type = typeof (fontSize); if (type === 'undefined' || type === 'boolean' || type === 'object' || type === 'function' || type === 'symbol') { throw Error('ì§ìíì§ ìë íìì
ëë¤.'); } fontSize = '' + fontSize; $temp.innerText = this; $temp.style.fontSize = fontSize.indexOf('em') > -1 ? fontSize : (fontSize + 'em'); return { width: ($temp.clientWidth + 1), height: ($temp.clientHeight + 1) }; } /** * 공백 ì ì²´ ì ê±° */ String.prototype.trimAll = function () { return this.replace(/(\\s+)/g, ''); }; /** * ì¢
ì± ë°ì¹¨ì ë°ííë í¨ì (ì¼ë¡ / ë¡ ê²°ì ì ëìì¤) * false ë 'ë¡', true ë 'ì¼ë¡' * @param {*} word */ function checkBatchimEnding(word) { const t = [ '', 'ã±', 'ã²', 'ã³', 'ã´', 'ãµ', 'ã¶', 'ã·', 'ã¹', 'ãº', 'ã»', 'ã¼', 'ã½', 'ã¾', 'ã¿', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
' ]; if (typeof word !== 'string') { return null; } let lastLetter = word[word.length - 1]; let unicode = lastLetter.charCodeAt(0); if (unicode < 44032 || unicode > 55203) { return null; } let endChar = t[(unicode - 44032) % 28]; let addChar = (endChar !== '' && endChar !== 'ã¹') ? 'ì' : 'ë'; return addChar; } /** * ê¸°ê° ë³ ë ì§ ë¬¸ìì´ì ë°ííë í¨ì * @param {*} period */ function getDateStringByPeriod(str, period, options) { if (period === 0) { return dateParser(str).formatDate('-'); } else if (period === 1) { let date = dateParser(str); let weeks = date.getFirstAndLastOfWeek(options.precision); return weeks.first.formatDate('-') + ' ~ ' + weeks.last.formatDate('-'); } else if (period === 2) { let result = null; if (str.length < 7) { result = str.substring(0, 4) + '-' + str.substring(4, 6); } else { let date = dateParser(str).formatDate(''); result = date.substring(0, 4) + '-' + date.substring(4, 6); } return result; } } /** * ì°í´ë¦ & ë³µì¬ ë°©ì§ í¨ì */ function preventPointerEvents() { $('.prevent-pointer-events') .mousedown(function (e) { if (e.button == 2) { alert('ë´ì©ì ë³µì¬ í ì ììµëë¤.'); return false; } else { return true; } }) .on('contextmenu', function () { return false; }) .on('dragstart', function () { return false; }) .on('selectstart', function () { return false; }); } /** * Canvas DataURL ì Blob ì¼ë¡ ë³ê²½íë í¨ì * @param {*} dataUrl */ function dataURLtoBlob(dataUrl) { let arr = dataUrl.split(','); let mime = arr[0].match(/:(.*?);/)[1]; let bstr = atob(arr[1]); let n = bstr.length; let u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new Blob([u8arr], { type: mime }); } /** * target (input or textarea, ...) ì ê°ì í´ë¦½ë³´ëì ë³µì¬íë í¨ì * @param {*} target * @param {*}
168 callback */ function setClipBoard(value, callback) { try { let text = document.createElement('textarea'); document.body.appendChild(text); text.value = value; text.select(); document.execCommand('copy'); document.body.removeChild(text); return callback(true); } catch (e) { return callback(null); } } /** * ì¿ í¤ ì¤ì í¨ì * @param {*} key * @param {*} value * @param {*} expireDays */ function setCookie (name, value, expireDays) { let expire = new Date(); expire.setDate(expire.getDate() + expireDays); let cookieString = escape(value); cookieString += '; path=/'; cookieString += (expireDays === null ? '' : '; expires=' + expire.toUTCString()); document.cookie = name + '=' + cookieString; } /** * ì¿ í¤ ê°ì ê°ì ¸ì¤ë í¨ì * @param {*} name */ function getCookie (name) { let result = null; document.cookie.split(';').forEach(function (cookie) { let split = cookie.split('='); let n = split[0].replace(/ /g, ''); let v = unescape(split[1]); if (n === name) { result = v; } }); return result; } /** * ì/를 í¬ë§· í¨ì * @param {*} text */ function formatJosa (text) { let lastChar = text.charAt(text.length - 1); let lmn = ['l', 'm', 'n']; let afhiorsuvwxyz = ['a', 'f', 'h', 'i', 'o', 'r', 's', 'u', 'v', 'w', 'x', 'y', 'z']; let bcdegkpt = ['b', 'c', 'd', 'e', 'g', 'k', 'p', 't']; let bckpt = ['b', 'c', 'k', 'p', 't']; let deg = ['d', 'e', 'g']; let aeiou = ['a', 'e', 'i', 'o', 'u']; let lastIndex = ''; if (lastChar < 0xAC00 || lastChar > 0xD7A3) { if ((lastChar > 0x0041 && lastChar < 0x005A) || (lastChar > 0x0061 && lastChar < 0x007A)) { if (lmn.indexOf(lastChar) > -1) { lastIndex = \"ì\"; } else if (afhiorsuvwxyz.indexOf(lastChar) > -1) { lastIndex = \"를\"; } else if (bcdegkpt.indexOf(lastChar) > -1) { if (text.length() < 2) { lastIndex = \"를\"; } else { let beforeLastChar = text.charAt(text.length - 2); if (bckpt.indexOf(lastChar) > -1) { if (lastChar == 'k' && beforeLastChar == 'c') { lastIndex = \"ì\"; } else if (lastChar == 'b' && beforeLastChar == 'm') { lastIndex = \"ì\"; } else if (aeiou.indexOf(beforeLastChar) > -1) { lastIndex = \"ì\"; } else { lastIndex = \"를\"; } } else if (deg.indexOf(lastChar) > -1) { if (lastChar == 'e' && beforeLastChar == 'l') { lastIndex = \"ì\"; } else if (lastChar == 'e' && beforeLastChar == 'n') { lastIndex = \"ì\"; } else if (lastChar == 'e' && beforeLastChar == 'm') { lastIndex = \"ì\"; } else if (lastChar == 'g' && beforeLastChar == 'n') { lastIndex = \"ì\"; } else if (lastChar == 'd' && aeiou.indexOf(beforeLastChar) > -1) { lastIndex = \"ì\"; } else if (lastChar == 'g' && aeiou.indexOf(beforeLastChar) > -1) { lastIndex = \"ì\"; } else { lastIndex = \"를\"; } } } } } else { lastIndex = \"ì/를\"; } } else { // 0 ë³´ë¤ í¬ë©´ ë°ì¹¨ì´ ìì¼ë¯ë¡ \"ì\" // 0 ë³´ë¤ ìê±°ë ê°ì¼ë©´ \"를\" lastIndex = (lastChar - 0xAC00) % 28 > 0 ? \"ì\" : \"를\"; } return lastIndex; } /** * ì¿ í¤ë¥¼ ìì íë í¨ì * @param {*} name */ function deleteCookie (name) { document.cookie = name + '= ; expires=' + new Date().toUTCString() + '; path=/'; } function checkIe () { let agent = navigator.userAgent.toLowerCase(); if ((navigator.appName == 'Netscape' && agent.indexOf('trident') != -1) || (agent.indexOf(\"msie\") != -1)) { return true; } else { return false; } } Typescript ë²ì /** * ì«ìê°ì´ ì ìì¸ì§ ì¬ë¶ë¥¼ ë°ííë í¨ì * @param { Number } value : ì«ì ê° * @return { Boolean } : ì ì(true), ì ì ìë ê²½ì°(false) */ function isInt (value:any): boolean { const parse: number = parseFloat(value); return !isNaN(value) && (parse | 0) === parse; } /** * ì ìë¶ì 3ì리 ë§ë¤ ',' 를 ì²´í¬í´ì£¼ë í¨ì * @param { Integer } value : ì ì ëë ì¤ì ê° * @return { String } : ',' ê° ì ì©ë ì¤ì ëë ì ì 문ìì´ ê° */ function addComma (value:any): string { if (!isInt(value)) { let integer:number = value | 0; let floater:string = (value - integer).toFixed(2).toString().substring(1); return addComma(integer) + floater; } else { return value.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','); } } /** * ì¤ë
ê³ì° ë©ìë * @param { Integer }
168 year : Date ê°ì²´ì ë
ë */ Date.isLeafYear = function (year) { return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); }; /** * ì¤ë
ê³ì° ì¸í°íì´ì¤ */ Date.prototype.isLeafYear = function () { return Date.isLeafYear(this.getFullYear()); }; /** * ì¤ë
ì í¬í¨í í´ë¹ ë
ëì ê° ìì를 ë°ííë ë©ìë * @param { Integer } year : Date ê°ì²´ì ë
ë * @param { Integer } month : Date ê°ì²´ì ì (0-11) */ Date.getDaysInMonth = function (year, month) { return [31, (Date.isLeafYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; }; /** * ì¤ë
ì í¬í¨í í´ë¹ ë
ëì ê° ìì를 ë°ííë ì¸í°íì´ì¤ * @param { Integer } year : Date ê°ì²´ì ë
ë * @param { Integer } month : Date ê°ì²´ì ì (0-11) */ Date.prototype.getDaysInMonth = function (year, month) { return Date.getDaysInMonth(this.getFullYear(), this.getMonth()); }; /** * Date ê°ì²´ìì N ê°ìë§í¼ì ì´ëíë ë©ìë * @param { Integer } n : ë³ê²½í ê°ì ì */ Date.prototype.changeMonth = function (n) { let day = this.getDate(); this.setDate(1); this.setMonth(this.getMonth() + n); this.setDate(Math.min(day, this.getDaysInMonth())); return this; }; /** * Date ê°ì²´ìì N 주 ë§í¼ì ì´ëíë ë©ìë * @param { Integer } n : ë³ê²½í ê°ì ì */ Date.prototype.changeWeek = function (n) { let day = this.getDate(); this.setDate(day + (7 * n)); return this; }; /** * YYYY(구ë¶ì) MM(구ë¶ì) DD ì ê°ì ííë¡ ë ì§ë¥¼ ë°ííë ë©ìë, 기본 구ë¶ìë '-' * @param { String } delimiter : 구ë¶ì */ Date.prototype.formatDate = function (delimiter) { let d = (this.getDate() > 9 ? '' : '0') + this.getDate(); let m = ((this.getMonth() + 1) > 9 ? '' : '0') + (this.getMonth() + 1); delimiter = delimiter || ''; return this.getFullYear() + delimiter + m + delimiter + d; }; /** * YYYY-MM-DD hh24:mi:ss ë³í */ Date.prototype.formatDateYMDHMS = function () { let d = (this.getDate() > 9 ? '' : '0') + this.getDate(); let m = ((this.getMonth() + 1) > 9 ? '' : '0') + (this.getMonth() + 1); let h = (this.getHours() > 9 ? '' : '0') + this.getHours(); let mi = (this.getMinutes() > 9 ? '' : '0') + this.getMinutes(); let s = (this.getSeconds() > 9 ? '' : '0') + this.getSeconds(); return this.getFullYear() + \"-\" + m + \"-\" + d + \" \" + h + \":\" + mi + \":\" + s; }; /** * ë ì§ ë³ê²½ í¨ì * @param { Date } beforeDate : ê¸°ì¤ ë ì§ * @param { int } addNumber : ëíê±°ë ëº ê° * @param { String } type : ë
(y) ì(m) ì¼(d) */ function dateAddDel(beforeDate, addNumber, type) { var yy = parseInt(beforeDate.substr(0, 4), 10); var mm = parseInt(beforeDate.substr(5, 2), 10); var dd = parseInt(beforeDate.substr(8), 10); if (type == \"d\") { //ì¼ d = new Date(yy, mm - 1, dd + addNumber); } else if (type == \"m\") { //ì d = new Date(yy, mm - 1, dd + (addNumber * 31)); } else if (type == \"y\") { //ë
d = new Date(yy + addNumber, mm - 1, dd); } yy = d.getFullYear(); // 19를 2019ë¡ ë³ê²½ mm = d.getMonth() + 1; mm = (mm < 10) ? '0' + mm : mm; //ì ë³ê²½ +1 íë ì´ì ë ìë°ì¤í¬ë¦½í¸ìì 0ì´ 1ìì´ë¼ dd = d.getDate(); dd = (dd < 10) ? '0' + dd : dd; //10ì¼ ì´ì ì´ë©´ ì«ì ì릿ì ë§ì¶ê¸° ìí¨ return yy + '-' + mm + '-' + dd; } /** * í´ë¹ ë¬ì 첫째ë ë¡ ë³í */ Date.prototype.getFirstDay = function () { return new Date(this.getFullYear(), this.getMonth(), 1); }; /** * í´ë¹ ë¬ì ë§ì§ë§ë ë°í */ Date.prototype.getLastDay = function () { return new Date(this.getFullYear(), this.getMonth() + 1, 0); }; /** * ë ì§ì 주차를 ë°ííë ë©ìë * @param { String } dowOffset */ Date.prototype.getWeek = function (dowOffset) { /*getWeek() was developed by Nick Baicoianu at MeanFreePath: http://www.meanfreepath.com */ dowOffset = typeof (dowOffset) == 'int' ? dowOffset : 0; //default dowOffset to zero let newYear = new Date(this.getFullYear(), 0, 1); let day = newYear.getDay() - dowOffset; //the day of week the year begins on day = (day >= 0 ? day : day + 7); let dayNum = Math.floor((this.getTime() - newYear.getTime() - (this.getTimezoneOffset() - newYear.getTimezoneOffset()) * 60000) / 86400000) + 1; let weekNum; //if the year starts before the middle of a week if (day < 4) { weekNum = Math.floor((dayNum + day - 1) / 7) + 1; if (weekNum > 52) { let nYear = new Date(this.getFullYear() + 1, 0, 1); let nDay = nYear.getDay() - dowOffset; nDay = nDay >= 0 ? nDay : nDay + 7; /*if the next year starts before the middle of the week, it is
168week #1 of that year*/ weekNum = nDay < 4 ? 1 : 53; } } else { weekNum = Math.floor((dayNum + day - 1) / 7); } return weekNum; }; /** * ë ì§ì ì¼ë¨ì ë³ê²½ í¨ì */ Date.prototype.changeDate = function (num) { this.setDate(this.getDate() + num); return this; }; /** * í´ë¹ ë ì§ê° ìí 주ì ì¼ìì¼ ~ í ìì¼ ë ì§ë¥¼ 문ìì´ë¡ ë°ííë í¨ì */ Date.prototype.getFirstAndLastOfWeek = function (precision) { let day = this.getDay(); let sunday = 0 - day; let saturday = 6 - day; let first = new Date(this.getFullYear(), this.getMonth(), this.getDate() + sunday); let last = new Date(this.getFullYear(), this.getMonth(), this.getDate() + saturday); if (precision) { first = this > first ? this : first; last = new Date() < last ? new Date() : last; } return { first: first, last: last }; }; /** * YYYYMMDD ííì 문ìì´ ë ì§ë¥¼ Date ê°ì²´ë¡ ë³ííë í¨ì * @param { String } dateStr */ function dateParser(dateStr) { dateStr = dateStr.replace(/\\W/g, ''); let y = dateStr.substring(0, 4); let m = dateStr.substring(4, 6); let d = dateStr.substring(6, 8); return new Date(y, m - 1, d); } /** * 문ìì´ì ê°ë¡, ì¸ë¡ 길ì´ë¥¼ ë°ííë í¨ì * @param { Integer } fontSize */ String.prototype.getMeasureText = function (fontSize) { let $temp = document.getElementById('measureTextWidth'); let type = typeof (fontSize); if (type === 'undefined' || type === 'boolean' || type === 'object' || type === 'function' || type === 'symbol') { throw Error('ì§ìíì§ ìë íìì
ëë¤.'); } fontSize = '' + fontSize; $temp.innerText = this; $temp.style.fontSize = fontSize.indexOf('em') > -1 ? fontSize : (fontSize + 'em'); return { width: ($temp.clientWidth + 1), height: ($temp.clientHeight + 1) }; } /** * 공백 ì ì²´ ì ê±° */ String.prototype.trimAll = function () { return this.replace(/(\\s+)/g, ''); }; /** * ì¢
ì± ë°ì¹¨ì ë°ííë í¨ì (ì¼ë¡ / ë¡ ê²°ì ì ëìì¤) * false ë 'ë¡', true ë 'ì¼ë¡' * @param {*} word */ function checkBatchimEnding(word) { const t = [ '', 'ã±', 'ã²', 'ã³', 'ã´', 'ãµ', 'ã¶', 'ã·', 'ã¹', 'ãº', 'ã»', 'ã¼', 'ã½', 'ã¾', 'ã¿', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
', 'ã
' ]; if (typeof word !== 'string') { return null; } let lastLetter = word[word.length - 1]; let unicode = lastLetter.charCodeAt(0); if (unicode < 44032 || unicode > 55203) { return null; } let endChar = t[(unicode - 44032) % 28]; let addChar = (endChar !== '' && endChar !== 'ã¹') ? 'ì' : 'ë'; return addChar; } /** * ê¸°ê° ë³ ë ì§ ë¬¸ìì´ì ë°ííë í¨ì * @param {*} period */ function getDateStringByPeriod(str, period, options) { if (period === 0) { return dateParser(str).formatDate('-'); } else if (period === 1) { let date = dateParser(str); let weeks = date.getFirstAndLastOfWeek(options.precision); return weeks.first.formatDate('-') + ' ~ ' + weeks.last.formatDate('-'); } else if (period === 2) { let result = null; if (str.length < 7) { result = str.substring(0, 4) + '-' + str.substring(4, 6); } else { let date = dateParser(str).formatDate(''); result = date.substring(0, 4) + '-' + date.substring(4, 6); } return result; } } /** * ì°í´ë¦ & ë³µì¬ ë°©ì§ í¨ì */ function preventPointerEvents() { $('.prevent-pointer-events') .mousedown(function (e) { if (e.button == 2) { alert('ë´ì©ì ë³µì¬ í ì ììµëë¤.'); return false; } else { return true; } }) .on('contextmenu', function () { return false; }) .on('dragstart', function () { return false; }) .on('selectstart', function () { return false; }); } /** * Canvas DataURL ì Blob ì¼ë¡ ë³ê²½íë í¨ì * @param {*} dataUrl */ function dataURLtoBlob(dataUrl) { let arr = dataUrl.split(','); let mime = arr[0].match(/:(.*?);/)[1]; let bstr = atob(arr[1]); let n = bstr.length; let u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new Blob([u8arr], { type: mime }); } /** * target (input or textarea, ...) ì ê°ì í´ë¦½ë³´ëì ë³µì¬íë í¨ì * @param {*} target * @param {*}
168 callback */ function setClipBoard(value, callback) { try { let text = document.createElement('textarea'); document.body.appendChild(text); text.value = value; text.select(); document.execCommand('copy'); document.body.removeChild(text); return callback(true); } catch (e) { return callback(null); } } /** * ì¿ í¤ ì¤ì í¨ì * @param {*} key * @param {*} value * @param {*} expireDays */ function setCookie (name, value, expireDays) { let expire = new Date(); expire.setDate(expire.getDate() + expireDays); let cookieString = escape(value); cookieString += '; path=/'; cookieString += (expireDays === null ? '' : '; expires=' + expire.toUTCString()); document.cookie = name + '=' + cookieString; } /** * ì¿ í¤ ê°ì ê°ì ¸ì¤ë í¨ì * @param {*} name */ function getCookie (name) { let result = null; document.cookie.split(';').forEach(function (cookie) { let split = cookie.split('='); let n = split[0].replace(/ /g, ''); let v = unescape(split[1]); if (n === name) { result = v; } }); return result; } /** * ì/를 í¬ë§· í¨ì * @param {*} text */ function formatJosa (text) { let lastChar = text.charAt(text.length - 1); let lmn = ['l', 'm', 'n']; let afhiorsuvwxyz = ['a', 'f', 'h', 'i', 'o', 'r', 's', 'u', 'v', 'w', 'x', 'y', 'z']; let bcdegkpt = ['b', 'c', 'd', 'e', 'g', 'k', 'p', 't']; let bckpt = ['b', 'c', 'k', 'p', 't']; let deg = ['d', 'e', 'g']; let aeiou = ['a', 'e', 'i', 'o', 'u']; let lastIndex = ''; if (lastChar < 0xAC00 || lastChar > 0xD7A3) { if ((lastChar > 0x0041 && lastChar < 0x005A) || (lastChar > 0x0061 && lastChar < 0x007A)) { if (lmn.indexOf(lastChar) > -1) { lastIndex = \"ì\"; } else if (afhiorsuvwxyz.indexOf(lastChar) > -1) { lastIndex = \"를\"; } else if (bcdegkpt.indexOf(lastChar) > -1) { if (text.length() < 2) { lastIndex = \"를\"; } else { let beforeLastChar = text.charAt(text.length - 2); if (bckpt.indexOf(lastChar) > -1) { if (lastChar == 'k' && beforeLastChar == 'c') { lastIndex = \"ì\"; } else if (lastChar == 'b' && beforeLastChar == 'm') { lastIndex = \"ì\"; } else if (aeiou.indexOf(beforeLastChar) > -1) { lastIndex = \"ì\"; } else { lastIndex = \"를\"; } } else if (deg.indexOf(lastChar) > -1) { if (lastChar == 'e' && beforeLastChar == 'l') { lastIndex = \"ì\"; } else if (lastChar == 'e' && beforeLastChar == 'n') { lastIndex = \"ì\"; } else if (lastChar == 'e' && beforeLastChar == 'm') { lastIndex = \"ì\"; } else if (lastChar == 'g' && beforeLastChar == 'n') { lastIndex = \"ì\"; } else if (lastChar == 'd' && aeiou.indexOf(beforeLastChar) > -1) { lastIndex = \"ì\"; } else if (lastChar == 'g' && aeiou.indexOf(beforeLastChar) > -1) { lastIndex = \"ì\"; } else { lastIndex = \"를\"; } } } } } else { lastIndex = \"ì/를\"; } } else { // 0 ë³´ë¤ í¬ë©´ ë°ì¹¨ì´ ìì¼ë¯ë¡ \"ì\" // 0 ë³´ë¤ ìê±°ë ê°ì¼ë©´ \"를\" lastIndex = (lastChar - 0xAC00) % 28 > 0 ? \"ì\" : \"를\"; } return lastIndex; } /** * ì¿ í¤ë¥¼ ìì íë í¨ì * @param {*} name */ function deleteCookie (name) { document.cookie = name + '= ; expires=' + new Date().toUTCString() + '; path=/'; } function checkIe () { let agent = navigator.userAgent.toLowerCase(); if ((navigator.appName == 'Netscape' && agent.indexOf('trident') != -1) || (agent.indexOf(\"msie\") != -1)) { return true; } else { return false; } } ","categories": ["posts"], 169 "tags": ["javascript"], 170 "url": "https://subji.github.io/posts/2020/02/20/usefulljavascriptcodes", 171 "teaser":null},{ 172 "title": "Spring Boot (Maven) with React Study day 3", 173 "excerpt":" 목ì === Spring boot ì React 를 íì©íì¬ ê°ë¨í ì¹ íì´ì§ë¥¼ ë§ë¤ì´ë³´ê³ ì íë¤. ì´ì ë´ì© ì´ì ìë ì¤íë§ë¶í¸ì ì¤ì¹íë ë°©ë²ê³¼ ì¤íë°©ë²ì ëí´ì ì´í´ë³´ìë¤. Spring Boot (Maven) with React Study day 2 ì´ë²ìë create-react-app ê³¼ ì¤ì¹ë ì¤íë§ ë¶í¸ ì±ì ë¹ë ë° ë°°í¬ ì°ëì í´ë³´ëë¡ íê² ë¤. 1. ì°ë ë°©ë² ì¤íë§ë¶í¸ì static ëë í 리 ìì .html íì¼ì´ ìë¤. í´ë¹ íì¼ì
173 리ì¡í¸ì íì¼ë¡ ëì²´í´ì£¼ë ë°©ë²ì´ ìë¤. ì¦, SPA ì¸ ë¦¬ì¡í¸ë¥¼ ë¹ëí´ì ë§ë¤ì´ì§ js, css, html íì¼ë¤ì ì¤íë§ë¶í¸ì ì ì 리ìì¤ ëë í 리ì ë³µì¬í´ì£¼ë ê²ì´ë¤. ê·¸ë¬ë©´ ì¤íë§ë¶í¸ììë classpath 를 íì¼ë©° ìì°ì¤ë 리ì¡í¸ íì¼ì ì¤íí ê²ì´ë¤. 2. 구ê¸ë§ ë´ê° ì°¾ì ë°©ë²ì¼ë¡ë ëëµ 2ê°ì§ ì ëê° ììë¤. 첫ë²ì§¸ë webpack ì¼ë¡ ë§ë¤ì´ì§ 리ì¡í¸ íë¡ì í¸ë¥¼ ì¤íë§ë¶í¸ íë¡ì í¸ ë´ ë³ëì ëë í 리ì ëê³ Maven ëë Gradle ìì ì¤íë§ë¶í¸ì ë¹ëì ëìì 리ì¡í¸ ì±ë ë¹ëí´ì ìë£ë íì¼ë¤ì ë³µì¬í´ì£¼ë ë°©ë²ì´ ìë¤. ëë²ì¨°ë Maven ì ê²½ì°ì¸ë°, frontend-maven-plugin, maven-antrun-plugin ì íì©íë ë°©ë²ì´ë¤. ì¬ê¸°ìë Maven ì¼ë¡ íë¡ì í¸ë¥¼ ìì±íì¼ë¯ë¡ ëë²ì§¸ ë°©ë²ì ì¬ì©í´ë³´ëë¡ íì. ë¤ì íë¡ì í¸ììë ê¼ Gradle ë ì¬ì©í´ë³´ì. 3. ì¤íë§ë¶í¸ & 리ì¡í¸ ë¹ë frontend-maven-plugin Version : Recently Version ë§í¬ë¥¼ íµí´ í´ë¹ ë©ì´ë¸ íë¬ê·¸ì¸ì ìµì ë²ì ì ì¬ì©íê±°ë ìíë ë²ì ì ì¬ì©íì. <plugin> <groupId>com.github.eirslett</groupId> <artifactId>frontend-maven-plugin</artifactId> <version>1.7.6</version> <!-- frontend-maven-plugin Version --> <configuration> <workingDirectory>frontend</workingDirectory> <!-- React JS ê° ì¤ì¹ë ëë í 리 --> <installDirectory>target</installDirectory> <!-- war & jar ê° ìì±ëë ë©ì´ë¸ ë¹ëì íê² ëë í 리 --> </configuration> <executions> <execution> <id>install node and npm</id> <goals> <goal>install-node-and-npm</goal> <!-- node & npm ì¤ì¹ --> </goals> <configuration> <nodeVersion>v12.16.1</nodeVersion> <!-- ì¤ì¹í nodeJs ì ë²ì --> <npmVersion>6.14.0</npmVersion> <!-- ì¤ì¹í npm ì ë²ì --> </configuration> </execution> <execution> <id>npm install</id> <!-- npm install ë¡ package.json ì 모ëì ì¤ì¹íë¤. --> <goals> <goal>npm</goal> </goals> <configuration> <arguments>install</arguments> </configuration> </execution> <execution> <id>npm run build</id> <!-- React Js Build --> <goals> <goal>npm</goal> </goals> <configuration> <arguments>run build</arguments> </configuration> </execution> </executions> </plugin> ìì ì½ë를 ìì±íê³ í°ë¯¸ë ëë cmd 를 ì´ê³ âmvn clean installâ ì í´ë³´ì. ìë 그림과 ê°ì ì§íì¬íì´ íìëë¤. ê·¸ë¦¬ê³ ëì 리ì¡í¸ê° ì¤ì¹ë ëë í 리를 ë³´ë©´ âbuildâ ë¼ë ëë í ë¦¬ê° ìê¸°ê³ ê·¸ìì js, css, html ì´ ìì±ëìì ê²ì´ë¤. ì´ë ê² ëë©´ ì´ì ë§ì§ë§ ë¨ê³ë§ ë¨ìë¤. ìë ì½ë를 pom.xml ì ì¶ê°í´ì£¼ì. <plugin> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <phase>generate-resources</phase> <!-- 리ìì¤ ìì± --> <configuration> <target> <copy todir=\"${project.build.directory}/classes/public\"> <!-- ë³µì¬í ëë í 리 ì¤ì --> <fileset dir=\"${project.basedir}/frontend/build\"/> <!-- ê°ì ¸ì¬ ëë í 리 ë° íì¼ --> </copy> </target> </configuration> <goals> <goal>run</goal> <!-- ë³µì¬ ì¤í --> </goals> </execution> </executions> </plugin> ìì ì½ë를 ìì±íê³ ë¤ì âmvn clean installâ ì ì¤íí´ë³´ì. ê·¸ë¬ë©´ ë¤ìê³¼ ê°ì´ target ëë í 리 ìëì ìì±ë ê²ì´ë¤. ì´ì 모ë ê² ë¤ ëìë¤. target ìëì ìì±ë .war íì¼ì
173 âjava -jar *.warâ ë¡ ì¤ííì. java -jar *.war ì´ì ì¤íë 주ìë¡ ì ìí´ë³´ë©´ ë¤ì íë©´ì´ ëì¨ë¤! ì 리 Spring Boot ì React ì°ë ë°©ë² React 를 ë¹ëí´ì Spring Boot Target ëë í 리ì ë³µì¬ ë§ë¬´ë¦¬ ê°ë¨íê² ë¦¬ì¡í¸ì ì¤íë§ë¶í¸ë¥¼ ì°ëí´ë³´ìë¤. ì´ë¥¼ ì¬ì©í´ì ë¤ììë 리ì¡í¸ë¥¼ ê°ë°íê³ ì¤íë§ë¶í¸ë API ë¡ íì©íì¬ ë¹ ë¥¸ê°ë°ì ëìì´ ë ê±° ê°ë¤. ","categories": ["posts"], 174 "tags": ["spring boot","maven","react"], 175 "url": "https://subji.github.io/posts/2020/02/26/react-with-spring-3", 176 "teaser":null},{ 177 "title": "JSP ë ëë§ ì¤ë¥ (java.lang.NoSuchMethodError: org.springframework.web.servlet.tags.form.InputTag.setReadonly(Ljava/lang/String;)V)", 178 "excerpt":"| 문ì íë¡ì í¸ë¥¼ ì ë°ì´í¸íê³ ë°°í¬íìëë°, í¹ì íì´ì§ë§ ìë¤ì´ê°ì§ë¤.. ëêµ°ë¤ë í´ë¹ íì´ì§ë ê±´ë린ì ë ììë¤. ë ê±°ì íë¡ì í¸ë¼ íëíë ë¤ ì´í´ë´ì¼ íë ìí©.. ë¤ìê³¼ ê°ì ìë¬ ë°ì | ì²´í¬ë¦¬ì¤í¸ 컨í¸ë¡¤ë¬ìì ë·°ë¡ ë§¤íì´ ì못ëìë ? - NO 뷰리졸ë²ë¥¼ ì¤ì íë ê³¼ì ìì ì¤ë¥ê° ìë ? - NO ë·° íì¼ì ìì¹ê° ì못ëìë ? - NO í.. ëëì²´ ëê¹.. íê³ í¬ê¸°íë©´ì ë¡ê·¸ë ì½ì´ë³´ì íëë°..!? ë¡ê·¸ ë§ì§ë§ 4ë¬¸ì¥ ì ë를 ë³´ë©´ configuration.jsp ë¼ë íì¼ìì input íê·¸ì readonly ìì±ì´ ì못ë¬ë¤ê³ ëìììë¤! ë¤ì 구ê¸ë§í´ì ë¤ìê³¼ ê°ì´ ëª ì¾í í´ëµì ì»ìë¤. | ê²°ë¡ íê·¸ì readonly ìì±ì´ ì못ëìë ê² readonly ë readonly=âtrueâ ë¡ ì¤ì í ê² ê·¸ì¸ì ìì±ì ì¤ì íë©´ ìë¬ ë°ì! ì½ë를 ë³´ëê¹ ë¡ ëì´ììë¤.. í.. ë ê±°ì ì½ë ⦠그ëë ê³ ì³ì ë¤íì´ë¤. ","categories": ["posts"], 179 "tags": ["jsp"], 180 "url": "https://subji.github.io/posts/2020/02/27/fomtinputreadonly", 181 "teaser":null},{ 182 "title": "Spring Boot ê° ìë íë¡ì í¸ìì Actuator ì¬ì©í기 (How to use Actuator without Boot", 183 "excerpt":"| 문ì ë ê±°ì íë¡ì í¸ë¤ì 모ëí°ë§ ìì¤í ì ë§ë¤ëì¤ Spring Boot ììë ê°ë¨í ì¤ì ë§ì¼ë¡ í´ë¹ ì´í리ì¼ì´ì ì ì 보를 ê°ì ¸ì¤ë Actuator 를 ì ì©ìí¤ê³ ì íìë¤. íì§ë§ Actuator ë Spring Boot íë¡ì í¸ì ì¼íì´ìëê².. ê·¸ë¼ ê¸°ì¡´ì ë ê±°ì ìì¤í ìë ì´ë»ê² ì ì©ì í´ì¼íëê±´ê°.. | ê³¼ì 구ê¸ë§ììë ìëê² ìë¤. ë¼ë íì ì ê°ê³ ì´ì¬í ì°¾ìë³´ìë¤. 먼ì pom.xml ì actuator Dependency 를 ì¶ê°íê³ .. xml ì¤ì ì¼ë¡ë ë무 ì´ë ¤ì°ëê¹ ê´ë¦¬ê° í¸í ìë° ì¤ì ì¼ë¡ í´ë´ì¼ê² ë¤.. ì´ì¬í ì°¾ê³ í ì¤í¸íê³ íì¸í ê²°ê³¼ ë¤ìê³¼ ê°ì´ ì¬ì©íë©´ ëë¤ë걸 ììë¤. POM.xml ì ìì¡´ì±ì ì¶ê° Java Configuration ì ì¬ì©í´ì ìíë EndPoint 를 ì¤ì í´ì£¼ë ê°ì²´ë¥¼ ìì± Security ì¸ì ë¤ë¥¸ ë³´ì ì²ë¦¬ë¥¼ ìì ì¤ì´ìë¤ë©´ /health, /metrics ë±ì ì ê·¼ì íì© | ê²°ë¡ POM.xml <!-- actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-actuator</artifactId> <version>1.2.1.RELEASE</version> </dependency> Java @Configuration @Import({ \tEndpointAutoConfiguration.class, // Endpoint ìë ì¤ì ê°ì²´ \tPublicMetricsAutoConfiguration.class, // Metrics ê°ì²´ \tHealthIndicatorAutoConfiguration.class // Health ê°ì²´ }) public class ActuatorConfig { \t @Bean @Autowired public EndpointHandlerMapping endpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints) { return new EndpointHandlerMapping(endpoints); } /** * Metrics ì ê·¼ ê°ì²´ Bean ë±ë¡ */ @Bean @Autowired public EndpointMvcAdapter metricsEndPoint(MetricsEndpoint delegate) { return new EndpointMvcAdapter(delegate); } /** * Health ì ê·¼ ê°ì²´ Bean ë±ë¡ */ @Bean @Autowired public EndpointMvcAdapter healthEndPoint (HealthEndpoint delegate)\t{ return new EndpointMvcAdapter(delegate); } } ","categories": ["posts"], 184 "tags": ["spring"], 185 "url": "https://subji.github.io/posts/2020/02/27/howtouseactuatorwithoutboot", 186 "teaser":null},{ 187 "title": "React WebFonts ì ì©í기 (Applied WebFonts on React)", 188 "excerpt":"| 문ì 기존 íë¡ì í¸ìì Web Fonts 를 ì¬ì©íê³ ììë¤. 리ì¡í¸ë¡ ì ííë ëì¤ ì´ê¸°ì ì ì ê³¼ì ìì ì¹í°í¸ ì ì©ì ìì´ ì
188¤ë¥ê° ë°ìíìë¤. 기존 íë¡ì í¸ììë ì¹í°í¸ë¥¼ ë¶ë¬ì¤ëë° ë³ë¤ë¥¸ ì ì½ì´ ììì§ë§ 리ì¡í¸ììë ë°ë¡ ì²ë¦¬ë¥¼ í´ì¤ì¼íëê² ê°ìë¤. ê·¸ëì 구ê¸ë§ì íµí´ ì°¾ìë³´ìëë° ëªê°ì§ ë°©ë²ì´ ììë¤. | ì´ë»ê²? 첫ë²ì¨° ë index.html ì css íì¼ì ë§í¬í´ì£¼ê³ ë¤ì ì¹í°í¸ë¥¼ ëª ìí´ì£¼ë ë°©ë²ì´ë¤. <link rel=\"stylesheet\" href=\"../fonts/fonts.css?family=Titillium+Web:300,400,700\" media=\"all\"> ì½ëìì ë³´ë¯ì´ index.html ì ìí¬í¸íë css íì¼ ë¤ì í°í¸í¨ë°ë¦¬ë¡ ì¹í°í¸ë¥¼ íë¼ë¯¸í°ë¡ í´ì£¼ìë¤. ë¤ì ì«ìë í°í¸ì ì¬ì´ì¦ë¥¼ ì미íë¤. ìì ë°©ë²ì íì¤í ê°ë¨í ë°©ë²ì´ë¤. íì§ë§ ë¤ìê³¼ ê°ì ì´ìê° ìë¤ê³ íë¤. í´ë¹ ë¼ì¸ì ëí´ì í¼í¬ë¨¼ì¤ì ê´ë ¨ë ì´ìì´ë¤. Render Blocking CSS ê·¸ëì ëë²ì§¸ ë¡ ë¤ìê³¼ ê°ì ë°©ë²ì ì¬ì©íë¤. ì´ ë°©ë²ì ìì²´ ë¡ì»¬ì ì¤ì¹ë ìì²´ ì¹í°í¸ë ì¸ë¶ ì¹í°í¸ë¥¼ 모ë ì¬ì©ê°ë¥íë¤. Web Font Loader 먼ì ë¼ì´ë¸ë¬ë¦¬ë¥¼ ì¤ì¹í´ì£¼ì. ì¤ì¹í ëë íì package.json ì ì ì¥ëê² âsave ìµì ì ë£ì´ì£¼ì. npm install --save webfontloader ë¤ìê³¼ê°ì´ index.js ë± ìíë ì¤í¬ë¦½í¸ íì¼ìì ìì±íë¤. import WebFont from 'webfontloader'; WebFont.load({ google: { families: ['Titillium Web:300,400,700', 'sans-serif'] } }); ì¹í°í¸ë¡ë를 ìí¬í¸íì¬ ì¹í°í¸ë¥¼ ë¶ë¬ì¤ë ë¡ì§ì´ë¤. ììë 구ê¸ì ì¹í°í¸ë¥¼ ë¶ë¬ì¤ë©° ë¼ì´ë¸ë¬ë¦¬ì ê¹íë¸ íì´ì§ë¥¼ ë³´ë©´ ì ì ìë¯ì´ ì¬ë¬ê°ì§ ì¹í°í¸ë ë¶ë¬ì¬ì ìë¤. WebFont.load({ google: { families: ['Titillium Web:300,400,700', 'sans-serif'] }, custom: { families: ['curstom-bold'], urls: ['../fonts/fonts.css'] } }); ì¬ì©ì ì ì ì¹í°í¸ë ììì²ë¼ ì¤ì í ì ìë¤. Before After ","categories": ["posts"], 189 "tags": ["react"], 190 "url": "https://subji.github.io/posts/2020/03/02/howtoappliedwebfontsinreact", 191 "teaser":null},{ 192 "title": "React Image Import íë ë°©ë² (How to import image on React JS)", 193 "excerpt":"리ì¡í¸ë¡ íë¡ì í¸ë¥¼ ë§ë¤ë©´ì ì´ë¯¸ì§ë¥¼ ì´ë»ê² ë¶ë¬ìì¼ íëëì ëí ê³ ë¯¼ì´ ììë¤. ì²ììë js, css ì²ë¼ ìí¬í¸ë¬¸ì ì¬ì©í´ì ë£ì´ë ë´¤ì§ë§ ì´ë ê²íë¤ê°ë ì´ë¯¸ì§ê° íëê°ë ìëë¼ì ì½ëê° ë³µì¡íê³ ë¶í¸í´ì§ê±° ê°ìë¤. ê·¸ëì ì°¾ìë³´ë public ëë í 리를 ìëì¼ë¡ 리ì¡í¸ ì±ìì ê²ìíëê±° ê°ìë¤. ê²°ê³¼ì ì¼ë¡ public ëë í 리 ìëì images ëë í 리를 ë§ë¤ê³ í´ë¹ìì¹ë¥¼ ìëê²½ë¡ë¡íì¬ ë¶ë¬ì¬ ì ììë¤. <img src=\"/images/logo.png\" /> ìì ì½ëë public ëë í 리 ìëì images ëë í ë¦¬ê° ìì ê²½ì°ì ìëíë¤. ","categories": ["posts"], 194 "tags": ["react"], 195 "url": "https://subji.github.io/posts/2020/03/03/howtoimportimageonreact", 196 "teaser":null},{ 197 "title": "React props Default value ì ì© ë°©ë² (How to set default value of props React JS)", 198 "excerpt":"props ì ëíì¬ ê¸°ë³¸ê°ì ì§ì í´ì¼ í íìê° ììëê° ìë¤. ê·¸ë´ë ë¤ìì²ë¼ ì¬ì©í ì ìë¤. ì¬ì©ë°©ë² Component.defaultProps = { propsOne: 'Test', ... }; export default Component; ","categories": ["posts"], 199 "tags": ["react"], 200 "url": "https://subji.github.io/posts/2020/03/03/howtovalidatepropertiesdefault", 201 "teaser":null},{ 202 "title": "React props type Validation ë°©ë² (How to validate props type React JS)", 203 "excerpt":"props 를 ë°ì¼ë©´ì íì ì²´í¬ë¥¼ í íìê° ììëê° ìë¤. ê·¸ë´ëë ê°ë¨íê² ë¤ìê³¼ ê°ì´ ì¬ì©í ì ìë¤. props-type import import PropTypes from 'prop-types'; ê°ë¥í ì í¨ íì ë° ìì± ë°©ë² Component.propTypes = { anyProp: PropTypes.any, // íì ìê´ X boolProp: PropTypes.bool, // Boolean íì numberProp: PropTypes.number, // Number íì stringProp: PropTypes.string, // String íì funcProp: PropTypes.func, // Function íì arrayProp: PropTypes.array, // Array íì objectProp: PropTypes.object, // Object íì symbolProp: PropTypes.symbol // Symbol íì }; export default Component; ","categories": ["posts"], 204 "tags": ["react"], 205 "url": "https://subji.github.io/posts/2020/03/03/howtovalidatepropertiestype", 206 "teaser":null},{ 207 "title": "unclosed regular expression ìë¬ ì²ë¦¬ ë°©ë²", 208 "excerpt":"리ì¡í¸ì±ì ë§ë¤ë¤ê° ì±ì ìí¥ì 미ì¹ë ì
208¤ë¥ë ìëì§ë§ jshint ê° ì½ëê³¼ì ìì ìë¬ë¡ ì²ë¦¬íë ê²½ì°ê° ìë¤. ë³´íµ jsx 를 ì¬ì©í ë ì기ë ê±° ê°ìë° ìëí°ë IDE ìì jshint ì ì ì ì¤ì ì í ì ìì§ë§ ê°ë¨íê² ì£¼ìì²ë¦¬ë§ì¼ë¡ë í´ê²°ì´ ê°ë¥íë¤. jshint ì ìì¸ë¥¼ ì¶ê°í´ì£¼ë ë°©ë²ì¸ë° ìíë ì½ëìì ì, ìëì ìëì ê°ì 주ìì ì¶ê°í´ì¤ë¤. return ( /* jshint ignore:start */ <div> </div> /* jshint ignore:end */ ); ","categories": ["posts"], 209 "tags": ["react"], 210 "url": "https://subji.github.io/posts/2020/03/09/unclosedregularexpressionerror-react", 211 "teaser":null},{ 212 "title": "WSL ìì lxd package broken ì¼ë¡ ì¸í´ apt upgrade ìë ë", 213 "excerpt":"ìëì° ìë¸ ìì¤í 리ë ì¤ (WSL) ì snap ì´ ì°ëì´ ìëë¤. ì´ëë apt update && apt upgrade 를 ì¤ííëë° ìëì ê°ì ìë¬ê° ë°ìíìë¤. Reading package lists... Done Building dependency tree Reading state information... Done You might want to run 'apt --fix-broken install' to correct these. The following packages have unmet dependencies: lxd : Depends: lxd-client (= 3.0.3-0ubuntu1~18.04.1) but 1:0.7 is installed lxd-client : Depends: lxd (>= 1:0.7) but 3.0.3-0ubuntu1~18.04.1 is installed E: Unmet dependencies. Try 'apt --fix-broken install' with no packages (or specify a solution). ë´ì©ì ë³´ë©´ lxd, lxd-client ë ê°ê° 3.0.3, 1:0.7 ë²ì ì´ìì ì§ìíëë° íì¬ ì¤ì¹ë ë²ì ì ê·¸ ì´íì¬ì ìëë¤ëê²ì´ë¤. ê·¸ëì ë¡ê·¸ì ì¶ë ¥ëëë¡ fix íë¼ë¯¸í°ë¥¼ ì¤íí´ë³´ìì§ë§ Interacting with snapd is not yet supported on Windows Subsystem for Linux. This command has been left available for documentation purposes only. ìì ê°ì ë©ì¸ì§ë¥¼ ì¶ë ¥íë¤. ë´ì©ì WSL ììë snap ì ì§ìíì§ ìëë¤ë ì기ê°ë¤. ê·¸ëì ì´ë»ê² ê°ì ë¡ lxd, lxd-client 를 ì¤ì¹íë ë°©ë²ì´ ììê¹íê³ ì°¾ìë³´ë¤ê° WSL ê³µì ê¹íë¸ì ëêµ°ê°ê° í´ê²°ë°©ë²ì ìì±í´ëìë¤. sudo dpkg --force depends -P lxd sudo dpkg --force depends -P lxd-client dpkg ë í¨í¤ì§ì ê´ë ¨ë ëª ë ¹ì´ë¡ ì¤ì¹, ìì ë±ì íë¼ë¯¸í°ë¡ ëê³ ì¬ì©í ì ìë¤. âforce ìµì ì ê°ì ìµì ì´ë¤. -P ìµì ì lxd, lxd-client ì í¨í¤ì§ì í´ë¹ í¨í¤ì§ì ì¤ì íì¼ì ìì íë¤ë ë»ì´ë¤. (Purge) depends-version, depends ìµì ì âforce ì ì£¼ë¡ ê°ì´ì´ë¤ê³ íëë° ì²«ë²ì§¸ì ê²½ì° ë²ì ìì¡´ì± ë¬¸ì 를 ê²½ê³ ë¡ ëì´ê°ê³ ëë²ì§¸ ìµì ì 모ë ìì¡´ì± ë¬¸ì 를 ê²½ê³ ë¡ ëì´ê°ë¤ë ë»ì´ë¤. ì ëª ë ¹ì´ë¤ì ì¤ííë apt update && apt upgrade ê° ìëë¤! ","categories": ["posts"], 214 "tags": ["wsl"], 215 "url": "https://subji.github.io/posts/2020/03/12/wslbrokenlxdwithsnap", 216 "teaser":null},{ 217 "title": "ì¸ë¶ Jar ì¶ê° ë°©ë²", 218 "excerpt":"ì¤íë§ ë¶í¸ë¥¼ í¨í¤ì§íê³ ë°°í¬íë ê³¼ì ìì ì¸ë¶ ojdbc 를 ì¬ì©í´ì¼ íëë° ë°°í¬íì ì ì©ì´ ìëì ì¤íì´ ìë¬ë¤. ë¡ì»¬ììë mvn install:install-file ì ì¬ì©í´ì ë¡ì»¬ PC ì 리í¬ì§í 리ìì ì¬ì©íëë° ë°°í¬í ìë²ì ë¡ì»¬ 리í¬ì§í 리ìë ojdbc ê° ììë¤.. ëí í¨í¤ì§ ê³¼ì ìì ojdbc íì¼ë ëë½ëìë¤. ëê°ì§ ë°©ë²ì´ ìëë°, íëë ìë²ì ë¡ì»¬ 리í¬ì§í 리ì íì¼ì ì¤ì¹íë ë°©ë² ë¤ë¥¸ íëë í¨í¤ì§í ë íë¡ì í¸ ëë í 리ìì ìì²´ 리í¬ì§í 리를 ë§ëë ê²ì´ë¤. íë¡ì í¸ ë¦¬í¬ì§í 리를 ë§ëë ë°©ë²ì ê°ë¨íë¤. ë¤ìê³¼ ê°ì´ lib ìëì ì¸ë¶ jar íì¼ì ì®ê²¨ëëë¤. ê·¸ë¦¬ê³ pom.xml ì ë§ë¤ì´ëì ëë í 리를 리í¬ì§í ë¦¬ë¡ ì§ì íë¤. <repositories> <repository> <id>local-repository</id> <name>local repository</name> <url>file://${project.basedir}/lib</url> </repository> </repositories> ë§ì§ë§ì¼ë¡ ì¸ë¶ ojdbc ìì ì ì¸í ìëì ê°ì ë´ì©ì 주ìì²ë¦¬íê³ ë¤ì í¨í¤ì§íë¤. <scope>system</scope> \t<systemPath>${basedir}/src/lib/ojdbc7.jar</systemPath> ","categories": ["posts"], 219 "tags": ["maven"], 220 "url": "https://subji.github.io/posts/2020/03/16/externalojdbcpackaging", 221 "teaser":null},{ 222 "title": "Type safety: Unchecked cast from Object to List<>", 223 "excerpt":"List list = (List) otherList; ìì Type safety: Unchecked cast from Object to List<> ê²½ê³ ê° ë°ìíìë¤. ë³ìì ì íì íì¤íê² í
223ì§ ëª»í´ ë°ìíë ê²½ê³ ì´ë¤. Java ë ì¼ë°ì ì¼ë¡ ì»´íì¼ì í ë ë³ìì ì íì ìê³ ìì´ì¼ íë¤. ê·¸ë¬ë¯ë¡ ë¶íì¤í ì íì ë³íì ê²½ê³ ë¥¼ ë°ììí¨ë¤. ì´ë¥¼ í´ê²°íë ë°©ë²ì ììë¡ í´ê²°íë ë°©ë²ê³¼ ì¡°ê¸ì ë 근본ì ì¼ë¡ í´ê²°íë ë°©ë²ì´ ìë¤. ììì ì¼ë¡ í´ê²°íë ë°©ë²ì ë¤ìê³¼ ê°ë¤. @SuppressWarnings() // ì»´íì¼ë¬ê° ì¼ë°ì ì¼ë¡ ê²½ê³ íë ë´ì©ì ì ì¸í ë ì¬ì© íì§ë§ ì´ë ì´ëê¹ì§ë ììë°©í¸ì¼ë¿ ì´ë¥¼ í´ê²°í기 ìí´ì ëª íí ì íì ì í´ì£¼ì´ì¼íë¤. List<?> result = (List<?>) results.values; for (Object object : result) { if (object instanceof MyObject) { tempList.add((MyObject) object); } } ","categories": ["posts"], 224 "tags": ["java"], 225 "url": "https://subji.github.io/posts/2020/03/17/TypesafetyUncheckedcastfromObjecttoList", 226 "teaser":null},{ 227 "title": "Java Capitalize", 228 "excerpt":"ìë°ìì âcommandâ => âCommandâ ë¡ ë³ê²½íë ë°©ë² public String capitalize (String text) { if (text == null) { return text; } return text.substring(0, 1).toUpperCase() + text.substring(1); } ","categories": ["posts"], 229 "tags": ["java"], 230 "url": "https://subji.github.io/posts/2020/03/17/javacapitalize", 231 "teaser":null},{ 232 "title": "Git Push ì·¨ìíë ë°©ë²", 233 "excerpt":"ì못 Push í ê¹ì ì·¨ìíë ¤íë¤. íì§ë§ ê·¸ ì ì ê¼ ê°ì íìê³¼ ììí´ì¼íë©° ì기 ë§ìëë¡ í´ë²ë¦¬ë¤ê° ì¼ì´ 커ì§ë¤.. ë°©ë²ì ê°ë¨íë¤. ë§ì ì·¨ìì§ë§ íì¬ Branch ì HEAD ê°ì ì´ëìí¤ë ê²ì´ë¤. ë íë 주ìí ì ì 기존ì ë§ë¤ì´ëì ì½ëê° HEAD ê°ì ì´ëì¼ë¡ ë³ê²½ëë¤ëê²ì´ë¤. ìì ì¤ì´ë íì¼ì´ ìì¼ë©´ ë°ëì ë°±ì ì ë§ë¤ì´ëê³ Push ì·¨ì를 íì. $ ~ git reset --hard HEAD^ or git reset --hard [commit id] $ ~ git push --force ","categories": ["posts"], 234 "tags": ["git"], 235 "url": "https://subji.github.io/posts/2020/03/20/git-reset-pssh", 236 "teaser":null},{ 237 "title": "D3 ì¬ë¬ ë²ì ì¬ì©íë ë°©ë² (How to use multiple version d3.js)", 238 "excerpt":"D3 를 ì¬ì©íëë° ë ê±°ì íë¡ì í¸ìì v3 ê³¼ v4 를 ê°ì´ ì¬ì©íê³ ììë¤. 기존 ë ê±°ì íë¡ì í¸ë ë¤ë¥¸ jsp ìì ê°ê° ë¤ë¥¸ ë²ì ì ì¬ì©íê³ ìì´ì ì¤ë¥ê° ììë¤. íì§ë§ ê°ì íì´ì§ìì v3 ê³¼ v4 를 ì¨ì¼íë ìí©ì´ì¤ë v3 -> v4 ìì ë²ì ì ëë©´ì ë°ìíë í¸íì± ì´ìê° íë°íë¤. ê·¸ëì 2ê°ë¥¼ ê°ê° ì¬ì©íë ë°©ë²ì ìê°í´ë´¤ë¤. ì ìì¼ë¡ ë¶ë¬ì¤ê±°ë ì§ìì¼ë¡ ë¶ë¬ì¤ê±°ë ìê´ìì´ ë¤ìê³¼ ê°ì´ í´ì£¼ì. <script type=\"text/javascript\" src=\"d3.v3.js\"></script> <script> d3v3 = d3; window.d3 = null; </script> <script type=\"text/javascript\" src=\"d3.v4.js\"></script> <script> d3v4 = d3; window.d3 = null; </script> ","categories": ["posts"], 239 "tags": ["javascript","d3js"], 240 "url": "https://subji.github.io/posts/2020/03/23/d3usemultipleversion", 241 "teaser":null},{ 242 "title": "Git add -P ì commit -v ì¬ì©", 243 "excerpt":"ì¼ë°ì ì¼ë¡ git add . ëë git add âall ì ì¬ì©í´ì íë²ì ë³ê²½ ë´ì©ì ì¤í ì´ì§ììì¼ë¡ ì¬ë¦°ë¤. íì§ë§ ì´ë¤ ë³ê²½ì¬íì ì¤í ì´ì§ì ë°ìëë©´ ìëê±°ë í¹ì ë°ììí¤ê³ ì¶ì§ ìì ê²½ì°ê° ìë¤. ê·¸ë´ë ìì ì¸ê¸í ëª ë ¹ì´ë ìííë¤. ê·¸ëì ìê¸°ê° ìíë ë³ê²½ë´ì©ë§ ë°ìíë ë°©ë²ì´ ìë¤. ìë ëª ë ¹ì´ë¥¼ ì ë ¥íì $ ~> git add -p ì 그림과 ê°ì´ ì¶ê°/ìì /ìì ê° ë ë¶ë¶ì´ íì´ë¼ì´í ëì´ íìëë¤. ê·¸ë¦¬ê³ ë³ê²½ë´ì©ì ë°ìí ì§ ìí ì§ë¥¼ 묻ë ê³³ìì ìíëëë¡ ì íí ì ìë¤. 주ì ì í ìµì ì ë´ì©ì ë¤ìê³¼ ê°ë¤. ë¤ì ë´ì©ìì hunk ë ë³ê²½ë´ì©ì ë¨ì를 ê°ë¦¬í¨ë¤. y: íì¬ hunk 를 ì¤í ì´ì§ ììì¼ë¡ ì´ë n: íì¬ hunk 를 ì¤í ì´ì§ ììì¼ë¡ ë³´ë´ì§ ìëë¤. q: add ê³¼ì ì ì¢ ë£íë¤. ì´ì¸ìë ì¬ë¬ ìµì ì´ ìë¤. ìì¸í ë´ì©ì ? 를 ì ë ¥íì¬ íì¸í´ë³´ì. ê·¸ë¦¬ê³ ë¤ìì commit -v ì ëí ë´ì©ì¸ë° ììì ì¤í ì´ì§ì¼ë¡ ì¶ê°ë ë´ì©ì ë¤ìíë² íì¸ í ì ìë¤. $ ~> git commit -v 를 ì ë ¥íë©´ 기본 ìëí° íë©´ì´ ì´ë¦¬ë©´ì ë³ê²½ë ë´ì©ì´ ì¶ë ¥ëë¤. git diff âstaged ë¼ë ë¹ì¤ë¬´ë¦¬í ëª ë ¹ì´ë ìëë° ì´ê²ë íì¸í´ë´ì¼ ê² ë¤. ","categories": ["posts"], 244 "tags": ["git"], 245 "url": "https://subji.github.io/posts/2020/03/27/gitaddpandcommitv", 246 "teaser":null},{ 247 "title": "nohup ëª ë ¹ì´ .log íì¼ìì´ ì¤í", 248 "excerpt":"ì¼ë°ì ì¼ë¡ ëª ë ¹ì´ ëì â&â 를 ë¶ì¬ë 백그ë¼ì´ë ì¤íì´ ëë¤. ê·¸ë ì§ë§ ssh ë± ì¸ì ì ëëë¼ë ê³ì 백그ë¼ì´ëìì ì ì§íë ¤ë©´ nohup ì ì¬ì©íê² ëë¤. nohup ëª ë ¹ì´ë¥¼ ì¤ííë©´ out íì¼ì´ ì기ëë° ì´ íì¼ì
248 ììê¸°ê² íë ë°©ë²ê³¼ 백그ë¼ì´ë ëª ë ¹ì´ ê°ê°ì ì미를 ììë³´ì. ëª ë ¹ì´ ëì & : ëª ë ¹ì´ë¥¼ 백그ë¼ì´ë ìì ì¼ë¡ ì¤í 2>&1 : stderr (íì¤ìë¬) 를 stdout (íì¤ì¶ë ¥) ì¼ë¡ ì´ëí©ëë¤. (&1 ë¶ë¶ì í¹ì íì¼ ê²½ë¡ë¡ ë°ê¾¸ì´ ë¡ê·¸ë¥¼ ì¶ë ¥í ì ìë¤.) /dev/null : stdout (íì¤ì¶ë ¥) ì /dev/null ë¡ ì´ë, ì§ì´ë¤ë ìë¯¸ë¡ ë³´íµ ìê³ ìì nohup : stty ê° ë긴 íìë íë¡ì¸ì¤ë¥¼ ì¢ ë£ìí¤ì§ ìì # nohup.out íì¼ ìì± ìì´ ë°±ê·¸ë¼ì´ë ìì ì¤í $ nohup example.sh 1> /dev/null 2>&1 & ","categories": ["posts"], 249 "tags": ["linux"], 250 "url": "https://subji.github.io/posts/2020/04/12/nohupwithoutlog", 251 "teaser":null},{ 252 "title": "Comparison method violates its general contract! ìë¬", 253 "excerpt":"ê°í¹ ì ë ¬ì íê³ ì í ë a > b ? 1 : -1 ê³¼ ê°ì´ ì¸ëê° ìë¤. java 9 ì´ì ë²ì ììë ìì ê°ì´ ì°ë©´ == ì í´ë¹íë ê²½ì°ê° ëª ìëì´ìì§ ì기ë문ì ì¤ë¥ê° ë°ìíë¤. ë°©ë²ì ì¬ë¬ê°ì§ê° ìë¤. 첫째ë ìë° ë²ì ì ì¬ë¦¬ë ê²ì´ë¤. íì§ë§ ë²ì ì íì ììë ë´ê° ë§ëë¡ ì¬ë¦´ìë ìì¼ë.. ë째ë ì¡°ê¸ ì½ëëì´ ë§ìì§ì§ë§ ë¤ìê³¼ ê°ì´ ìì±íë ë°©ë²ì´ ìë¤. if (a > b) { return 1; } else if (a < b) { return -1; } else if (a == b) { return 0; } í ëê³³ì´ë ì ë ê² ì¸ ì ìì§ ì¬ë¬ê³³ì´ë©´ ìê°ë½ ìíê±° ê°ë¤. ë§ì§ë§ì ì¡°ê¸ ë ê°ë¨íê² ìì±íë ë°©ë²ì´ë¤. return Long.compare(a, b); ëí ê°ì²´ë¥¼ ë¹êµíë©´ ë ë«ë¤. ","categories": ["posts"], 254 "tags": ["java"], 255 "url": "https://subji.github.io/posts/2020/04/14/javacomparezeroerror", 256 "teaser":null},{ 257 "title": "Java Equals '==' ì°¨ì´", 258 "excerpt":"ë³´íµ String ê°ì²´ì ê²½ì° equals 를 ì¬ì©íê³ int ë³ìì ê²½ì° == ì ì¬ì©íë¤. ì´ì ë 무ìì¼ê¹? ê·¸ ì°¨ì´ë ê°ì²´ì ê°ì ìë¤. ì ìì¸ String ê°ì²´ ë¹êµì ê²½ì° ê°ì´ ìë ê°ì²´ë¥¼ ë¹êµíë¤. ê°ì²´ë¥¼ ë¹êµíë¤ë ê²ì Call By Reference ë¡ ì°¸ì¡°ê°ì ë¹êµíë ê²ì´ë¤. íìì¸ int ì ê²½ì° Wrapping ê°ì²´ê° ìë Primitive íì ì´ë¯ë¡ Call By Value ë¡ ê°ì ë¹êµíê² ëë¤. ì를ë¤ì´ String a = \"aaa\"; String b = a; String c = \"aaa\"; a == b // True b == c // False ììì ë³´ë©´ ë¶ëª a (âaaaâ) ì ê°ì b ë¡ ë³µì¬í´ì£¼ìì¼ë ë¹ì°í c (âaaaâ) ì ê°ìê±°ë¼ ìê°í ì ìë¤. íì§ë§ ë¤ë¥´ë¤. a ì c ì ê°ì ê°ë¤. íì§ë§ ê·¸ ê°ì ê°ì§ê³ ìë ê°ì²´ê° ë¤ë¥´ë¤. ë¤ë§ a ê°ì²´ë¥¼ b ê°ì²´ë¡ ë³µì¬í´ì£¼ìì¼ë¯ë¡ a ì b ë ê°ì ê°ì²´ì´ë¤. String ì Primitive íì ì´ ìë ê°ì²´ ì´ë¯ë¡ a ê°ì²´ì ê° (âaaaâ) 를 ë¹êµíë ê²ì´ ìë ê·¸ ê°ì ê°ì§ ê°ì²´ë¥¼ ë¹êµíë ê²ì´ë¤. ì´ë ë¹êµíê² ëëê² ì£¼ìê°ì¸ë°, ê°ì²´ë ê·¸ ê°ìê° ìë¡ ë¤ë¥¸ 주ìê°ì ê°ì§ê² ëë¤. ê·¸ë¬ë¯ë¡ a 를 í ë¹ë°ì b ë a ì ê°ì 주ìê°ì ê°ì§ë¯ë¡ â==â ì°ì°ìë¡ ë¹êµí ë 주ìê°ì¼ë¡ ë¹êµí´ ê°ê² ëëê²ì´ë¤. ê·¸ëì ê°ì²´ì ê°ì ë¹êµíë ë©ìëì¸ equals 를 ì¨ì¼ ì ííê² ê°ì²´ë¥¼ ë¹êµí ì ìë¤. ë¤ìì¼ë¡ int íì ê²½ì° Integer ë¼ë Wrapping ê°ì²´ê° ìì§ë§ int í ìì²´ë ìì íì (Primitive Type) ì´ê¸°ì ë¨ìí ê°ì ë¹êµí ì ìë ê²ì´ë¤. ","categories": ["posts"], 259 "tags": ["java"], 260 "url": "https://subji.github.io/posts/2020/04/27/javaequals", 261 "teaser":null},{ 262 "title": "URLDecoder: Illegal hex characters in escape (%) pattern", 263 "excerpt":"Ajax ìì²ì ë³´ë´ë ì¤ âURLDecoder: Illegal hex characters in escape (%) patternâ ë¼ë ìë¬ê° ë°ìíë¤. ë¬´ì¨ ìë¬ì¸ê° íê³ ë³´ë % ê³¼ 16ì§ì 문ìì´ì ê´í ë´ì©ì¸ê±° ê°ë¤. ì°¾ìë³´ë URL Decode ê³¼ì ììë % ë¤ì ì¤ë 문ìì´ì 16ì§ìë¡ ì¸ìíëë° ì´ ë¬¸ìì´ì´ ë³íì´ ëì§ ìììë ë°ìíë ì
263¤ë¥ë¼ê³ íë¤. URL ìì %ê° ë¶ì 문ìì´ì Escape 문ìì´ì´ë¼ê³ íë¤. ì Escape 문ìì´ì ì¬ì©íëê±´ê°? ë¼ë ì§ë¬¸ìë ë¤ìê³¼ ê°ì´ ê°ë¨íê² ëµí ì ìë¤. ì를ë¤ì´ GET ë°©ìì¼ë¡ íë¼ë¯¸í°ë¥¼ ë³´ë¼ ë ë¤ìê³¼ ê°ì´ íë¼ë¯¸í° ê°ì 공백 ê°ì´ íë ìë¤ê³ ì¹ì ex) ?key=valu e ì´ ê²½ì° ìì²ì ë°ë ê³³ììë ì¨ì íê² âvalu eâ ë¼ë ê°ì ë°ìì¤ì§ 못íë¤. ì¦ URL ì í¬í¨ë í¹ìí 문ìë¤ì ëí´ì ì¸ìì íì§ ëª»íë ê²ì´ë¤. ê·¸ëì í¹ì문ì를 ì¬ì©íë¤ëê²ì ìë ¤ì£¼ì´ì¼ íëë°, ì´ë ì¬ì©íëê² Escape 문ìì´ë¤. ì ììì âvalu eâ 를 ì¸ìí´ì£¼ê¸° 공백ì ëíë´ë Escape 문ìì´ì¸ â%20â ì 공백ë¶ë¶ì ë°ê¿ë£ëë¤. ex) ?key=valu%20e ê·¸ë¬ë©´ URL Decoder ììë ìì°ì¤ë 공백ì¼ë¡ ì¸ìí ê²ì´ë¤. ìë¬ì¸ â%â ì ëí´ìë ìë íìì ëìë¯ì´ â%25â ë¡ ë°ê¿ì£¼ë©´ ìë¬ê° ëì§ ìì ê²ì´ë¤. Escape í¹ì문ì í 공백 %20 ` %60 - %2D < %3C [ %5B ~ %7E _ %5F > %3E ] %5D ! %21 = %3D . %2E { %7B @ %40 + %2B , %2C } %7D # %23 \\ %5C ? %3F ( %28 $ %24 | %7C ) %29 % %25 / %2F ^ %5E : %3A & %26 ; %3B * %2A ' %27 \" %22 ","categories": ["posts"], 264 "tags": ["javascript"], 265 "url": "https://subji.github.io/posts/2020/04/28/urldecodepercenterror", 266 "teaser":null},{ 267 "title": "Can't exec \"locale\": No such file or directory at /usr/share/perl5/Debconf/Encoding.pm", 268 "excerpt":"WSL ì ì¬ì©íë©´ì 19.10 ìì 20.04 ë¡ íì¬ë¦¼ì íëë° ì¤ê°ì ì¤ìë¡ êº¼ì ê·¸ë°ì§ system 쪽ìì ì¶©ëì´ ë°ìíì¬ ì 목과 ê°ì ì
268¤ë¥ê° ë°ìíìë¤. ê¹¨ì§ íì¼ë¤ì 복구íê±°ë ì§ì°ê³ ìë¡ ì¤ì¹ë¥¼ í´ì¼ëë¤. 첫ë²ì§¸ë¡ ì°¾ì ë°©ë²ì ë¤ìê³¼ ê°ë¤. $ apt-get download libc-bin $ dpkg -x libc-bin*.deb unpackdir/ $ sudo cp unpackdir/sbin/ldconfig /sbin/ $ sudo apt-get install --reinstall libc-bin $ sudo apt-get install -f ëª ë ¹ì´ë ì¤íëëë° ë³íê° ìë¤. ì¤ë¥ë ê·¸ëë¡ ë°ìì¤ì´ë¤.. ê·¸ëì ì°¾ì ë¤ë¥¸ ë°©ë² $ sudo mv /var/lib/dpkg/info /var/lib/dpkg/info_old ãããã $ sudo mkdir /var/lib/dpkg/info ãããããããããããã $ sudo apt-get update && sudo apt-get -f install $ sudo mv /var/lib/dpkg/info/* /var/lib/dpkg/info_old ããã $ sudo rm -rf /var/lib/dpkg/info ãããããããããããã $ sudo mv /var/lib/dpkg/info_old /var/lib/dpkg/info ã ëª ë ¹ì´ë¥¼ ë³´ë 기존ì dpkg ë´ì©ë¤ì ë°±ì dpkg ë´ì©ë¤ì ë´ì ëë í 리 ìì± í¨í¤ì§ ì ë°ì´í¸ & ê°ì ì¤ì¹ ìë¡ ë°ì í¨í¤ì§ë¥¼ ë°±ì í¨í¤ì§ ëë í 리ì ë®ì´ìì°ê¸° ìë¡ ë§ë ëë í 리 ìì ë°±ì ëë í 리를 ìë í¨í¤ì§ ëë í ë¦¬ë¡ ë³ê²½ ê·¸ë¥ ì½ê² ë§í´ ë¤ì ìë¡ ë°ìê²ì´ë¤..ã ","categories": ["posts"], 269 "tags": ["linux"], 270 "url": "https://subji.github.io/posts/2020/05/06/fixedlinuxerrorlocaleperl5", 271 "teaser":null},{ 272 "title": "Windows10 ì¬ë¬ê°ì VPN ì¤ì íë ë°©ë²", 273 "excerpt":"ìì ì íë¤ë³´ë©´ ì¬ë¬ê°ì VPN ì ì¬ì©í´ì¼ í ëê° ìë¤. ë§¤ë² íëì ì°ê²°ì ëê³ ë¤ì ì°ê²°íê³ ë¥¼ ë°ë³µí ìê° ìë ë ¸ë¦ì´ë¤. ê·¸ëì ì´ë²ìë ì¬ë¬ê°ì VPN ì ì°ê²°íë ë°©ë²ì ììë³´ì. 먼ì TAP-Windows ì´ëí°ë¥¼ ì¶ê°í´ì¤ì¼íë¤. TAP-Windows ì´ëí°ë ê°ì ë¤í¸ìí¬ ì»¤ë ì¥ì¹ë¡ íëì¨ì´ ë¤í¸ìí¬ ì´ëí°ê° ìë ìíí¸ì¨ì´ ì°¨ììì ì§ìëë¤. Open VPN ì ê²½ì° í´ë¹ ì´ëí°ë¥¼ ì¬ì©íëë° ê¸°ë³¸ì ì¼ë¡ íëì ì´ëí°ê° ì¶ê°ëì´ìê³ ì¬ë¬ê°ì VPN ì ì¬ì©í기 ìí´ìë TAP-Windows 를 ëë ¤ì£¼ë©´ëë¤. ê·¸ ë°©ë²ì ìëì ê°ë¤. 먼ì ìëì ê²½ë¡ë¡ ì´ëíë¤. C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\TAP-Windows\\Utilities ê·¸ë¬ë©´ ìëì ê°ì´ TAP-Windows 를 ì¶ê° / ìì í ì ìë ë°°ì¹ íì¼ì´ ë³´ì¸ë¤. ê°ì TAP ì´ëí°ë¥¼ ì¶ê°í´ì¼ íë âAdd a new TAP virtual ethernet adapterâ íì¼ì
273 ì°í´ë¦íì¬ ê´ë¦¬ìë¡ ì¤íìí¤ì. ë¤ìê³¼ ê°ì ì§í ìí© íë©´ì´ ëì¤ë©° âê³ìíë ¤ë©´ ì무 í¤ë ë르ììì¤â¦â ë¬¸êµ¬ê° ëì¤ë©´ ì무ë²í¼ì´ë ëë¬ì ì¢ ë£íë¤. ì¬ê¸°ê¹ì§ ë¬ì¼ë©´ ê°ìì TAP-Windows ì´ëí°ê° ì¶ê°ëê²ì´ë¤. ì´ì íì¸íë¬ âì ì´í - ë¤í¸ìí¬ ë° ì¸í°ë· - ë¤í¸ìí¬ ì°ê²°â ì¼ë¡ ì´ëí´ë³´ì. ë³´ë©´ âë¡ì»¬ ìì ì°ê²° 1 ~ 4â ë¶ë¶ì´ ìë¡ê² ì¶ê°ë TAP ì´ëí°ì´ë¤. ì´ì VPN íì¼(.opvn) ì ì´ì´ ë¤ìê³¼ ê°ì ëª ë ¹ì´ë¥¼ ì¶ê°í´ì£¼ì. dev-node \"TAP-Windows ì´ë¦\" # ì¬ê¸°ìë ë¡ì»¬ ìì ì°ê²° 1 ~ 4 ë§ì§ë§ì¼ë¡ VPN ì°ê²° í´ì£¼ìë ë°©ìëë¡ ì°ê²°í´ì£¼ë©´ ì¬ë¬ê°ì VPNì ì¬ì©í ì ìê² ëë¤. ","categories": ["posts"], 274 "tags": ["vpn","windows"], 275 "url": "https://subji.github.io/posts/2020/05/06/howtomultiplevpnforwindows", 276 "teaser":null},{ 277 "title": "CRLF (Carriage Return & Line Feed) ë?", 278 "excerpt":"Git add 를 íë¤ê° ë¤ì ê²½ê³ ê° ì¶ë ¥ëìë¤. âhe file will have its original line endings in your working directory warning: CRLF will be replaced by LF inâ CRLF ë ê°í문ìì¸ë° 무ì¸ê° ì못ëê±´ê° ì¶ìë¤. ì°¾ìë³´ë í° ë¬¸ì ë ìëìê³ OS ë³ë¡ CRLF 를 íì íë 기ì¤ì´ ì¡°ê¸ì© ë¬ë¼ì ì긴 ê²½ê³ ìë¤. ì´ ê²½ê³ ë ê°ë¨íê² ë¤ì ëª ë ¹ì´ë¥¼ ì ë ¥íë©´ ìì ê°ë¥íë¤. git config --global core.autocrlf false ê·¸ë¼ ë³¸ë¡ ì¼ë¡ ë¤ì´ê°ì ììì ì¸ê¸í CRLF ë 무ìì¼ê¹? ê°ë¨íê² ê°í문ìë¼ê³ í ì ìë¤. ë³´íµ ê¸ì ì°ë¤ë³´ë©´ ë¤ì íì¼ë¡ ë´ë ¤ì ì°ë ê²½ì°ê° ìë¤. ì´ë ë¤ìíì¼ë¡ ëì´ê°ë íì를 ê°íì´ë¼ê³ íëë°, ì»´í¨í°ë ì¬ëì²ë¼ ìëì¼ë¡ ììì ë¤ì ì¤ì ë§ë¤ì´ë´ì§ 못íë¤. ê·¸ëì ì ë ¥í´ì£¼ë ì¬ëì´ ì»´í¨í°ìê² ìë ¤ì£¼ì´ì¼ íë¤. ì´ê²ì´ ë°ë¡ ê°í문ìì¸ë° ASCII ì½ëìë 10, 13 ë²ì¼ë¡ ëª ìëì´ ìë¤. CRLF ë Carriage Return ê³¼ Line Feed ë¡ ì´ë£¨ì´ì§ ì¤ìë§ì¸ë°, Line Feed ë Enter í¤ ì ë ¥ ì¦, ë¤ìì¤ë¡ ëì´ê° ë¬ë¼ë ëª ë ¹ë¬¸ìì´ë¤. Carriage Return ì 커ìì ìì¹ë¥¼ íì¬ ì¤ì 맨 ì²ìì¼ë¡ ì´ëìí¤ë ëª ë ¹ë¬¸ì´ë¤. ì´ ëê°ì§ ëª ë ¹ë¬¸ìë¤ì íì기ìì ì ëíë¤ê³ íë¤. ìíì íì기를 ë³´ë©´ 주ì¸ê³µì´ íì를 ì¹ë¤ê° ì¼ìª½ ëë ì¤ë¥¸ìª½ì ë ë²(?) ê°ì 모ìì ë´ë¦¬ê±°ë ì¬ë¦¬ê±°ë íë¤. ì´ ëìì´ ë°ë¡ íì기ìì ì¬ì©ëë Line Feed ìë¦¼ì¸ ê²ì´ë¤! ëí ê°ì ì¥ë©´ìì ì¢ ì´ê° ì¼ìª½ì¼ë¡ ì ì ì´ëíë©´ì ê¸ì ìì±íëë°, ììì ì²ë¼ ë ë²ë¥¼ ë´ë¦¬ê³ ìë¡ì´ ì¤ì ì²ìì¼ë¡ ì´ëí기 ìí´ ëë¤ë¥¸ ì´ë¤ ëìì íë¤. ì´ê²ì´ ë°ë¡ Carriage Return ì´ë¤. ","categories": ["posts"], 279 "tags": ["computer theory"], 280 "url": "https://subji.github.io/posts/2020/05/18/carragereturnandlinefeed", 281 "teaser":null},{ 282 "title": "Oracle OVER ëª ë ¹ì´", 283 "excerpt":"쿼리 ìì ì íë¤ë³´ë©´ SELECT ì ìì COUNT() ê°ì ê°ì´ ì¡°íí´ì ë´ë³´ë´ê³ ì í ëê° ìë¤. ì´ì ìë ë¤ìê³¼ ê°ì´ ìë¸ì¿¼ë¦¬ë¥¼ ì¬ì©í´ì ìì±íìë¤. SELECT TITLE, ( SELECT COUNT(*) FROM T_TEST_ONE ) FROM T_TEST_TWO ê·¸ë°ë° ì¤ë¼í´ììë ì¢ ë ê°ë¨íê² íííë ë°©ë²ì´ ììë¤. ë°ë¡ OVER() í¨ìë¤. OVER í¨ìë GROUP BY ëë ORDER BY ì ê°ì´ 그룹 ì¡°í를 í ë ì´ì©ëë ìë¸ì¿¼ë¦¬ë¥¼ ê°ì í기 ìí´ ëì¨ í¨ìì´ë¤. COUNT(), SUM(), AVG(), RANK(), ROW_NUMBER() ë±ì ì§ê³í¨ì ëë ë¶ìí¨ìì ê°ì´ ì¬ì©ëë¤. ìëë ì¼ë°ì ì¼ë¡ ë°ìí ì ìë ì¤ë¥ ìí©ì´ë¤. SELECT COL_ONE, COUNT(*) FROM T_TEST_ONE 무ìì´ ì못ëììê¹? COUNT() í¨ìë ì§ê³í¨ìë¤. ì¦, ì 체를 그룹ì¼ë¡ íê±°ë í¹ì Column ì 그룹ì¼ë¡ ë§ë¤ì´ ì¬ì©í ì ìë¤. ìì ì½ëììë COL_ONE ì´ë¼ë Column ê³¼ í ì´ë¸ ì 체를 그룹ì¼ë¡ íë COUNT í¨ì를 ì¡°ííê³ ìë¤. ì¦, ì§ê³í¨ì를 ì¬ì©íë SELECT 문ììë GROUP BY ë¡ ì§ì ëì´ ìì§ ìì Column ì ì¡°íí ì ìë¤. ë§ì¼ COL_ONE ì´ë¼ë Column ì ì¡°ííê³ ì¶ì¼ë©´ ë¤ìê³¼ ê°ì´ ìì±í´ì¼ íë¤. SELECT COL_ONE, COUNT(*) FROM T_TEST_ONE GROUP BY COL_ONE ì, íì¸í´ë³´ì! ì¤íê²°ê³¼ë ì¤ë¥ìì´ ëì¤ëê² ê°ë¤. íì§ë§ 문ì ë GROUP BY ë¡ ë¬¶ì¬ì§ COL_ONE ë³ COUNT() í¨ìì ê°ì´ ì§ê³ ëë¤ë ê²ì´ë¤. ë´ê° ìíë ê²ì COUNT() í¨ìë ì ì²´ì ê°ì COL_ONE ì ê°ê°ì ê°ì ë´ë³´ë´ë ê²ì´ìë¤. ê·¸ëì ì¬ì©í ì ìë ê²ì´ ìë¸ì¿¼ë¦¬! ëë OVER() í¨ìì´ë¤ OVER() í¨ìì ì¬ì©ë²ì ê°ë¨íë¤. # COL_ONE ê³¼ í ì´ë¸ ì ì²´ì COUNT() ê° ì¡°í SELECT COL_ONE COUNT(*) OVER() FROM T_TEST_ONE # ì¤ë³µ ì ì¸ SELECT COL_ONE COUNT(*) OVER(DISTINCT COL_TWO) FROM T_TEST_ONE # COL_TWO ì ë ¬ SELECT COL_ONE COUNT(*) OVER(ORDER BY COL_TWO) FROM T_TEST_ONE # GROUP BY SELECT COL_ONE COUNT(*) OVER(PARTITION BY COL_THREE) FROM T_TEST_ONE ìë¸ì¿¼ë¦¬ ì¬ì©ë ì¢ì§ë§ OVER() í¨ìì ê°ì´ ì¬ì©í기 í¸í ííë ì¬ì©í´ë³´ì. ","categories": ["posts"], 284 "tags": ["db","oracle"],
285 "url": "https://subji.github.io/posts/2020/05/18/oracleoverfunction", 286 "teaser":null},{ 287 "title": "MyBatis DBMS ë³ LIKE ì ", 288 "excerpt":"DBMS ë³ë¡ MyBatis ìì LIKE ì ì ì¬ì©íë ë°©ë²ì´ ì¡°ê¸ ë¤ë¥´ë¤. ê° ì¬ì©ë°©ë²ì ìëì ê°ë¤. MySql SELECT * FROM TABLE WHERE COLUMN LIKE CONCAT('%', #{ Keyword }, '%') Oracle SELECT * FROM TABLE WHERE COLUMN LIKE '%' || #{ Keyword } || '%' MsSql SELECT * FROM TABLE WHERE COLUMN LIKE '%' + #{ Keyword } + '%' ","categories": ["posts"], 289 "tags": ["sql","mybatis"], 290 "url": "https://subji.github.io/posts/2020/05/19/mybatislike", 291 "teaser":null},{ 292 "title": "Git ìë¬ error: pathspec { branch ëª } did not match any file(s) known to git.", 293 "excerpt":"Repository ì ìë¡ì´ ë¸ëì¹ë¥¼ ìì±íê³ ì¬ì©íëì¤ ë¤ë¥¸ í¼ììì ìë¡ ìì±ë ë¸ëì¹ë¥¼ ë¶ë¬ì¤ëë° ì 목과 ê°ì ì¤ë¥ê° ë°ìíìë¤. ë¤ë¥¸ í¼ìì ìë ë¸ëì¹ë ìë¡ ìì±ë ë¸ëì¹ë¥¼ ê°ì§ê¸° ì ì ë¸ëì¹ë¡ ì´ë¥¼ ì ë°ì´í¸í´ì¤ì¼ íë¤. ë°©ë²ì ë¤ì ëª ë ¹ì´ë¥¼ ì¤íí´ë³´ì git remote update git fetch git checkout {branch} ","categories": ["posts"], 294 "tags": ["git"], 295 "url": "https://subji.github.io/posts/2020/05/20/gitdidnotmatchanyfilknowntogit", 296 "teaser":null},{ 297 "title": "Headless ë", 298 "excerpt":"Headless ë? Headless ë ì§ìì í´ë³´ë©´ ë¨¸ë¦¬ê° ìë¤ë? ë§ì¸ë°.. ë¨¸ë¦¬ê° ìë¤ëê² ëëì²´ 무ì¨ë§ì¼ê¹? ì¼ë°ì ì¼ë¡ ì¬ì©ìë¤ì´ ì¹íì´ì§ë¥¼ ì ìí ë ë¸ë¼ì°ì (í¬ë¡¬, IE, ì£ì§, ì¬í리 ë±ë±) ì ì¬ì©íë¤. ì´ë ì¬ì©ëë ë¸ë¼ì°ì ê° ìì ë§í Head 를 ê°ë¦¬í¤ë ë¶ë¶ì´ë¤. ë¤ìë§í´ Headless ë ë¸ë¼ì°ì ê° ìë íí ê°ë°ìë¤ ì¬ì´ìì ì¬ì©ëë Command Line ìì íì¸ê°ë¥í ë°©ë²ì ê°ë¦¬í¨ë¤. Headless 를 ì¬ì©íë ì´ì ë ëê¹? Headless ê° ì£¼ìì¬ì©ëë ììì ë¤ìíë¤. ê·¸ì¤ììë ëíì ì¼ë¡ í¬ë¡¤ë§ìì ë§ì´ ì¬ì©ëëë°, Headless 를 ì¬ì©íë©´ ì¬ì©ì ì¸ì¦ë±ì ìë°ì¤í¬í¬ë¦½í¸ë¡ ì´ë¤ì§ë ëìì ì¬ì©í´ë³¼ ì ìë¤ë ì ì´ë¤. íê°ì§ ì를 ë¤ë©´ ì주 ì¬ì©íë curl ì´ë ëª ë ¹ì´ë¡ ì´ë í¹ì íì´ì§ë¥¼ ê°ì ¸ì¨ë¤ê³ íì. ë§ì¼ í´ë¹ íì´ì§ê° ì¸ì¦ì íê³ ë íì ì ê·¼ì´ ê°ë¥íë¤ë©´ curl ì ê²°ê³¼ë ë¨ìí ë¡ê·¸ì¸ íì´ì§ë¥¼ ê°ì ¸ì¬ê²ì´ë¤. íì§ë§ Headless 를 ì¬ì©íë©´ ì¸ì¦ ëìì ì¬ì©íì¬ ë¡ê·¸ì¸ í ìíë íì´ì§ì ì 보를 ê°ì ¸ì¬ ì ìì ê²ì´ë¤. ì´ì¸ìë ì¤í¬ë¦°ì· 캡ì³, PDF íì¼ ìì± ë±ìì ë§ì´ ì°ì¸ë¤. Headless ì ì¥ì ? ë¨ì ? ê°ë°ì 측면ìì ë´¤ìë Headless ë íì©ë°©ìì´ ì ë§ ë§ì ë³´ì¸ë¤. ê·¸ë°ë° ê¼ ì¢ìì ë§ ìë ê²ì ìëë¤. ì¼ë°ì ì¼ë¡ ì¬ì©ìê° ë¸ë¼ì°ì ìì ì ê·¼í´ì ì¬ì©íë ë°©ìì´ HttpClient ë°©ìì¸ë°, ì´ë 주ì ì ë ¥ê³¼ ëìì HTML Contents 를 ë¶ë¬ì¤ê² ë©ëë¤. íì§ë§ Headless ì ê²½ì° ë¡ì»¬ ëë ìë²ì 백그ë¼ì´ëìì ì¤ì ë¸ë¼ì°ì 를 ì¬ì©íë ë°©ìì´ê¸° ë문ì ìëì ì¸ ì¸¡ë©´ìì 기존ì HttpClient ë°©ìì ë¹í´ ë®ë¤ê³ ë³¼ ì ììµëë¤. Headless ë¼ì´ë¸ë¬ë¦¬ ë리 ìë ¤ì ìë ê²ì PhantomJS ì´ë¤. íì§ë§ Chrome ìµì ë²ì ììë headless-mode 를 ì¬ì©í ì ìê² ëìë¤. ìëì°ë 60, ê·¸ì¸ OS ë 59 ë²ì ì´ììì ì¬ì©í ì ìë¤. ë¤ë§ PhantomJS ì ê²½ì° OS ì 구ì ë°ì§ ìì ì¬ì©í기 ì©ì´íë¤. ","categories": ["posts"], 299 "tags": ["headless"], 300 "url": "https://subji.github.io/posts/2020/06/01/headless", 301 "teaser":null},{ 302 "title": "Blocking & Non-Blocking, Syncronous & Asyncronous", 303 "excerpt":"Blocking & Non-Blocking ê³¼ Syncronous & Asyncronous 를 ê°ë¨í ììë³´ê³ ì°¨ì´ì ì íì¸í´ë³´ì. Blocking ì ì´ë¤ ìì²ì í´ë¼ì´ì¸í¸ê° 주ììë, ìë²ìì ìë£ í¹ì ì¤ë¥ ì²ë¦¬ë¥¼ ë°íí기 ì ê¹ì§ í´ë¼ì´ì¸í¸ê° ë¤ë¥¸ í¸ì¶ì í ì ìì´ ë기íë ìíì´ë¤. Non-Blocking ì ì´ë¤ ìì²ì í´ë¼ì´ì¸í¸ê° 주ììë, ìë²ìì ìë£ í¹ì ì¤ë¥ì ìê´ìì´ í´ë¼ì´ì¸í¸ì ë¤ë¥¸ í¸ì¶ì í ì ìê² í´ì£¼ë ê²ì´ë¤. Syncronous ë C, Java ì ê°ì íë¡ê·¸ëë°ìì ë³¼ ì ìë ìì°¨ì íë¡ê·¸ëë° ë°©ìì´ë¼ê³ ìê°íë©´ ì½ë¤. ì²ì í¸ì¶ë í¨ì ëë ì½ëê° ìë£ë기 ì ê¹ì§ ë¤ì í¨ì ëë ì½ëê° ì¤íëì§ ìë ë°©ìì´ë¤. Asyncronous ë Syncronous ì ë¬ë¦¬ í¨ì ëë ì½ëì ìë£ ì¬ë¶ì ìê´ìì´ ë¤ì í¨ì ëë ì½ëê° ì¤íëë ë°©ìì´ë¤. ","categories": ["posts"], 304 "tags": ["javascript"], 305 "url": "https://subji.github.io/posts/2020/06/03/blockingnonblockingaysncsync", 306 "teaser":null},{ 307 "title": "Javascript ë¹ë기ë?", 308 "excerpt":"ë¹ë기ë? ìì´ë¡ë Asyncronous, í¹ì í¨ì ëë ì½ëì ì¤í ìë£ì ìê´ìì´ ë¤ì ì½ë ë° í¨ì를 ì¤ííë ë°©ìì´ë¤. ë°ëë§ë¡ë Syncronous, ë기 ë°©ìì´ ìë¤. ë기방ìì ê°ê¹ì´ Java 를 ë³´ë©´ ì ì ìë¤. ë¤ì ì½ë를 íì¸í´ë³´ë©´ ì¶ë ¥ì´ println() í¨ìì í¸ì¶ ììëë¡ ëì¤ëê²ì ë³¼ ì ìë¤. ì´ì²ë¼ ìì°¨ì ì¼ë¡ ì½ëê° ì¤íëë ë°©ìì ë기 ë°©ìì´ë¼ê³ íë¤. public static void main (String[] args) { System.out.println(\"1\"); System.out.println(\"2\"); } // 1 // 2 ê·¸ë¬ë©´ ë¹ë기 ë°©ìì ì´ë»ê² ëìí ê¹? ë¤ì ìì 를 ë³´ì. console.log('1'); setTimeout(function () { console.log('2'); }, 1000); console.log('3'); // 1 // 3 // 2 ì ì½ëë ìë°ì¤í¬ë¦½í¸ìì ëíì ì¸ ë¹ë기 ì²ë¦¬ í¨ìë¡
308 ë³¼ ì ìë setTimeout 를 ì¬ì©í ìì ì´ë¤. ê·¸ë ë¤ë©´ ì ìì ììì ì¶ë ¥ ììë ì´ë»ê² ë ê¹? ì¼ë°ì ì¸ ê²½ì°ë¼ë©´ ë¹ì°í ìììë¶í° ìëë¡ ì½ì´ë´ë ¤ê°ë©° 1 -> 2 -> 3 ì¼ë¡ ìê°ë ê²ì´ë¤. íì§ë§ ë¹ë기 ë°©ìììë ê·¸ë ì§ ìë¤. 첫ë²ì§¸ 1 ì´ ì¶ë ¥ëê³ 2ê° ì¶ë ¥ë기ì ê¹ì§ì 1ì´ë¥¼ 기ë¤ë ¤ì£¼ì§ ìê³ ë¤ì 3ì 먼ì ì¶ë ¥íë¤. ì´ë ê² ììì ìê´íì§ ìê³ ì½ë를 ì¤ííë ë°©ìì ë¹ë기방ìì´ë¼ê³ íë¤. ë¹ë기 ì²ë¦¬ë¥¼ ë기? ì ì¼ë¡ ì¬ì©íê³ ì¶ìë ìì ë§í ë¹ë기 ë°©ìì ë기방ìì¼ë¡ ì¬ì©íê³ ì¶ìë ì¬ì©í ì ìë ëªê°ì§ ë°©ë²ì´ ìë¤. ì²«ì§¸ë¡ ì½ë°±ì ì¬ì©íë ë°©ìì´ë¤. ìë°ì¤í¬ë¦½í¸ìì í¨ìë ì¼ê¸ê°ì²´ë¡ì íë¼ë¯¸í°ë¡ ì¬ì©í ì ìë¤. ê·¸ëì í¨ì í¸ì¶ì´ íë¼ë¯¸í°ë¡ ì½ë°±í¨ìë¼ë ëë¤ë¥¸ í¨ì를 í¬í¨ìì¼ ë기ìì¼ë¡ ì¬ì©íë ë°©ë²ì´ìë¤. JQuery í¨ìë¡ ìµìí Ajax 를 íì©í ìì ì½ë를 ë³´ì. ì°¸ê³ ë¡ Ajax ë í´ë¼ì´ì¸í¸ìì ìë²ë¡ XMLHttpRequest 를 ë¹ëê¸°ë¡ ì²ë¦¬íë íµì ë°©ìì´ë¤. // 1. function main () { let result = null; $.get('/test', function (res) { result = res; }); return result; } console.log(main()); // 2. function main (callback) { $.get('/test', function (res) { callback(res); }); } main(function (res) { console.log(res); }); 1ë²ê³¼ 2ë² ìì 를 ë¹êµí´ë³´ì. 1ë²ì ë¹ë기방ìì¼ë¡ ì²ë¦¬ëë ì½ë를 ê·¸ëë¡ ì¬ì©í ê²ì´ë¤. âconsole.log(main())â ì ê²°ê³¼ë ì´ë»ê² ëì¬ê¹? ìë¶ ê°ì´ ëì¬ê¹? ìëë©´ null ê°ì´ ëì¬ê¹? ëµì null ê°ì´ ëì¨ë¤. ì´ì ë ë¹ë기 ë°©ììì ì°¾ì ë³¼ ì ìë¤. $.get ì¼ë¡ í¸ì¶í í¨ì를 ìë°ì¤í¬ë¦½í¸ë í´ë¹ ìì²ì´ ìë£ë ëê¹ì§ 기ë¤ë ¤ì£¼ì§ ìê³ ë°ë¡ ë¤ì ì½ëì¸ âreturn resultâ 를 ì¤ííë¤. ê·¸ëì ê²°ê³¼ì ì¼ë¡ ë§ì§ë§ ë¡ê·¸ììë null ì´ ì¶ë ¥ëë ê²ì´ë¤. ì´ë¬í 문ì ì ì í´ê²°íê³ ì íë ë°©ìì¤ì íëê° ì½ë°±í¨ì를 ì´ì©í ë°©ìì´ë¤. ê°ì Ajax ìì ì¸ 2ë²ì ë³´ë©´ ì´ë¦ì´ ìë í¨ì를 íë¼ë¯¸í°ë¡ main í¨ìì ì ë¬í´ì£¼ê³ ìë¤. ìë°ì¤í¬ë¦½í¸ìì í¨ìë ì¼ê¸ê°ì²´ì´ë¯ë¡ íë¼ë¯¸í°ë¡ ì¬ì©ì´ ê°ë¥íë¤. ì´ ì¤í ìì를 1ë² ìì ì ë¹êµí´ë³´ì. main í¨ì í¸ì¶ -> $.get ìì² -> ê²°ê³¼(result) ë°í -> ê²°ê³¼(result) ì¶ë ¥ -> $.get ìì² ìëµ? (ììê° ë§ì§ë§ì´ ë ì§ ì¸ì ê° ë ì§ ëª¨ë¥¸ë¤.) main í¨ì í¸ì¶ -> $.get ìì² -> ìëµ -> ì½ë°±í¨ìë¡ ë°í ìì를 ë³´ë©´ 2ë²ì ê²½ì° ì¼ë°ì ì¼ë¡ ìê°í기 ì¬ì´ ì²ë¦¬ ë°©ìì¸ ìììë¶í° ìëë¡ ì½ì´ë´ë ¤ê°ë ë°©ìì´ë¤. ë°ë©´ì 1ë²ì ê²½ì°ë ëíµ ì´í´ê° ì ê°ì§ ìë ë°©ìì´ë¤. ì½ë°± ë°©ìì ë¹ë기방ìì ì¡°ê¸ì´ëë§ ë기ì ê°ê¹ê²? ì¬ì©í ì ìëë¡ ëìì¤ë¤. íì§ë§ ê¼ ì¢ìì ë§ ìëê±´ ìëë¤. ì½ë°±ì ë¨ì ? ì½ë°±í¨ìì ê²½ì° ë¹ë기방ìì ì¡°ê¸ì´ëë§ í´ìí ì ìë ë°©ë²ì´ë¤. íì§ë§ ê°ì¥ í° ë¬¸ì ë¡ ê¼½íëê²ì´ ì½ë°±ì§ì¥ì´ë¼ê³ ë¶ë¦¬ë 문ì ì´ë¤. ë¤ì ìì ë¡ íì¸í´ë³´ì. function main (callback) { callback(); } main(function () { main(function () { main(function () { ... }); ... }); }); ìì ììë ê°ì í¨ì를 ê³ì í¸ì¶íìì§ë§ ê°ê° ë¤ë¥¸ í¨ì를 í¸ì¶í ìë ìë¤. ê·¸ë¼ ì´ê² ì ì§ì¥ì´ë¼ê³ ë¶ë¦¬ëë? ì´ì ë ê°ë¨íë¤. ì§ê¸ì´ì¼ ëªê° ìëë í¨ì를 ì½ë°±ì¼ë¡ í¸ì¶í기 ë문ì ìì보기 ì½ì§ë§ ë§ì½ 10ê°ê° ëì´ê°ê³ ë 20ê°ê° ëì´ê°ê³ íë¤ë³´ë©´ ìì보기 íë ì½ëê° ë ê²ì´ë¤. ëêµ°ë¤ë ëë²ê¹ ìë ì´ë ¤ìì´ ìë¤. ì´ë¥¼ í´ìí ì ìë ë°©ë²ì´ ëªê°ì§ ë ìëë° ì²«ì§¸ë¡ ìì ë¹ë기 ì²ë¦¬ë°©ìì ë기방ìì¼ë¡ í´ê²°íê³ ì ëìì ì¤ ëªê°ì§ ì¤ìì Promise ì Async/Await ê° ìë¤. Promise ? ì¡°ê¸ ë¯ì ? ì©ì´ë¤. Promise? ì½ì? ë ì½ìíë¤ëê±°ì§? ì´ì ì ë´¤ë¯ì´ ì½ë°±í¨ì를 ì¬ì©í ë¹ë기 ì²ë¦¬ììë ì½ë°±ì§ì¥ì ë¹ ì ¸ ì½ë를 ëë²ê¹ í기ë ì½ê¸°ë ì´ë ¤ìì§ë¤ë ë¨ì ì´ ììë¤. ì´ë¥¼ í´ê²°í기 ìí´ì ì¬ì©ëëê²ì´ Promise ë¼ë í¨ìì´ë¤. Promise ë ì´ 3ê°ì§ì ìíê° ìë¤. ë기 (Pending) ì´í (Fullfill) ì¤í¨ (Reject) ë기 ìí new Promise() ë¡ ì ì¸ë ìíì´ë¤. ```ts new Promise(); new Promise(function (resolve, reject) { }); 2. ì´í ìí - ì½ë°±í¨ì를 ì¤íìì¼ì£¼ë ë¨ê³ì´ë¤. ```ts new Promise(function (resolve, reject) { // ì´í ì½ë°± í¨ì í¸ì¶ resolve(); }); function main() { return new Promise(function (resolve, reject) { resolve('Success'); }); } main() // ì´í ì½ë°± í¨ì .then(function (result) { console.log(result); }); ì¤í¨ ìí ì´í ìíë Resolve í¸ì¶, ì¤í¨ë Reject í¨ì를 í¸ì¶íë¤. ```ts new Promise(function (resolve, reject) { // ì´í ì½ë°± í¨ì í¸ì¶ resolve(); // ì¤í¨ ì½ë°± í¨ì í¸ì¶ reject(); }); function main() { return new Promise(function (resolve, reject) { resolve(âSuccessâ); reject(âErrorâ); }); } main() // ì´í ì½ë°± í¨ì .then(function (result) { console.log(result); }) // ì¤í¨ ì½ë°± í¨ì .catch(function (error) { console.error(error); }); ì ìì ë¤ ì²ë¼ ì±ê³µì ì´íì ëìíê³ ì í ëìë resolve í¨ì를 í¸ì¶íê³ then ë©ìëìì ì²ë¦¬íë©°, ì¤í¨ë¥¼ ëìíê³ ì í ëìë reject í¨ì를 í¸ì¶íê³ catch ìì í´ë¹ ì¤í¨ ì´ì 를 íì¸íë¤. ì´ë ê² ê°ë¨íê² ìì 를 ììë³´ìë¤. ì¡°ê¸ ë ììë³´ìë©´, íê°ì Promise 를 ì¬ë¬ê°ì then ì ì¬ì©íë ë°©ë²ê³¼ ì¬ë¬ê°ì Promise 를 ì°ê²°íë ë°©ë²ì´ ìëë° ê·¸ ìë ë¤ìê³¼ ê°ë¤. ```ts function main () { return new Promise(function (resolve,reject) { resolve(); }); } // íê°ì Promise ì ì¬ë¬ê°ì then ì ì¬ì©íë ë°©ë² main().then(function () { return result; }).then(function (result) { return result; }) function subPromise () { return new Promise(function (resolve, reject) { resolve(); }); } main().then(subPromise).then()... ë§ì§ë§ì¼ë¡ Promise ììì ìë¬ì²ë¦¬ë catch() í¨ì를 ì¬ì©íëë¡ íì. Async/Await ì½ë°±í¨ì, Promise ì ê°ì´ ë¹ë기 ì²ë¦¬ ë°©ë²ì´ë¤. ES6 ìì ìë¡ì´ ì¶ê°ëìë¤. ì¬ì©ë²ì ë§¤ì° ê°ë¨íë¤. íê°ì§ë§ 주ìíë©´ ëëë° await ë¡ ë°ë í¨ìë ë°ëì Promise ê°ì²´ë¡ 리í´ì í´ì¤ì¼íë¤ë ê²ì´ë¤. ìì 를 íëë³´ì. function returnResult () { return new Promise (function (resolve, reject) { resolve('Success'); }); } async function main () { let result = await returnResult(); console.log(result); } ëì ë°©ìì ê°ë¨íë¤. Promise ì then ì await ê° ì²ë¦¬í´ì£¼ë ê²ì´ë¤. async/await ë°©ìì ì£¼ë¡ ì¬ë¬ê°ì ë¹ë기를 ì²ë¦¬í ë ë§ì´ ì°ì¸ë¤. ìì¸ì²ë¦¬ë 기본ì ì¸ try/catch 를 ì¬ì©íë¤. ","categories": ["posts"], 309 "tags": ["javascript"], 310 "url": "https://subji.github.io/posts/2020/06/03/javascriptasyncronous", 311 "teaser":null},{ 312 "title": "SyntaxError: Cannot use import statement outside a module", 313 "excerpt":"NodeJs ì Typescript 를 ì¬ì©í´ì ê°ë°ì íë ëì¤ ì 목과 ê°ì ì
313¤ë¥ë¥¼ ë§ì£¼íê² ëìë¤. module ë°ìì ì¶ê°ë ì½ë를 ì¬ì©í ì ìë¤ë ë§ì¸ê±° ê°ë¤. ê·¸ëì ì²«ì§¸ë¡ tsconfig.json ì ì´í´ë³´ìë¤. module: commonjs 를 ë³´ë©´ commonjs 를 íì©í´ì module ì ì¬ì©íê³ ììë¤. commonjs ë¼ í¨ì 기ìµì´ ê°ë¬¼ê°ë¬¼íì§ë§ require(ââ) ë¡ ì ì¸í´ì ì¸ë¶ ë¼ì´ë¸ë¬ë¦¬ë¥¼ ê°ì ¸ì¤ë ë°©ë²ì¸ê±¸ë¡ ìê³ ìë¤. ê·¸ë¬ë©´ ëê° ë¬¸ì ì¼ê¹..? nodejs ê³µì문ì를 ë³´ë©´ ë¤ìê³¼ ê°ì 문ì¥ì´ ìë¤. If the nearest parent package.json lacks a \"type\" field, or contains \"type\": \"commonjs\", .js files are treated as CommonJS. If the volume root is reached and no package.json is found, Node.js defers to the default, a package.json with no \"type\" field. package.json ìì âtypeâ íëì ë³ëì ê°ì´ ìê±°ë âcommonjsâ ë¡ ì¤ì ëì´ìì¼ë©´ 기본 모ëë¡ âcommonjsâ 를 ì¬ì©íë¤ê³ ëììë¤. ê·¸ë¼ ì´ë»ê² import 문ì ì¬ì©í ì ììê¹? ê·¸ ëµì ì½ê² ì°¾ì ì ììë¤. package.json ìì âtypeâ: âmoduleâ ë¡ ì¤ì íë©´ ëëê²ì´ìë¤. 문ì í´ê²°! ","categories": ["posts"], 314 "tags": ["javascript","typescript","node"], 315 "url": "https://subji.github.io/posts/2020/06/03/nodetypescripterror1", 316 "teaser":null},{ 317 "title": "WSL1 cannot read realtime clock: Invalid argument ì¤ë¥", 318 "excerpt":"WSL ì ì¬ì©íë¤ê° sleep ëª ë ¹ì´ìì ë¤ìê³¼ ê°ì ì
318¤ë¥ê° ë°ìíìë¤. âsleep:cannot read realtime clock: Invalid argumentâ ì¤ì ìê°ì´ ìë§ì¶°ì ¸ì ì¸ê°? ì¶ì´ ì°¾ìë³´ìë¤. WSL 20.04 ì´ì ë²ì ìì ë°ìíë ë²ê·¸ë¡ 20.04 ë¡ ì ë°ì´í¸ íë ê³¼ì ëë ê·¸ ì´ì ë²ì ìì ë°ìíë ë²ê·¸ìë¤. ê·¸ëì 20.04 ë¡ ì¤ì¹ëë©´ì ê¼¬ì¸ ë¼ì´ë¸ë¬ë¦¬ë¤ì ì¬ ì¤ì¹ íê³ sleep ëª ë ¹ì´ë ì¬ ë³µì¬ í´ì£¼ìë¤. # ì못 ì¤ì¹ë ë¼ì´ë¸ë¬ë¦¬ ìì wget https://launchpad.net/~rafaeldtinoco/+archive/ubuntu/lp1871129/+files/libc6_2.31-0ubuntu8+lp1871129~1_amd64.deb sudo dpkg --install libc6_2.31-0ubuntu8+lp1871129~1_amd64.deb sudo apt-mark hold libc6 sudo apt --fix-broken install sudo apt full-upgrade # sleep ëª ë ¹ì´ ë³µì¬ sudo -i sudo mv /bin/sleep /bin/sleep~ ; touch /bin/sleep ; chmod +x /bin/sleep ","categories": ["posts"], 319 "tags": ["wsl","linux"], 320 "url": "https://subji.github.io/posts/2020/06/09/wslrealteimclock", 321 "teaser":null},{ 322 "title": "Apache Kafka ì¤ì¹", 323 "excerpt":"Apache Kafka ì¤ì¹ ë°©ë² kafka ë Scala ë¡ ìì±ëì´ ììµëë¤. ë°ë¼ì ìë°ê° ì¤ì¹ëìëì§ íì¸ í ë¤ìì ì§íí´ì¼ë©ëë¤. kafka í´ë¬ì¤í° ê´ë¦¬ë±ì ì´ì ë¡ zookeeper ê° ì íëì´ì¼ í©ëë¤. ë¤íì´ë zookeeper ë kafka ì¤ì¹íì¼ì í¬í¨ëì´ ììµëë¤. ëì¤ì ì¤íí ëë zookeeper 를 먼ì ì¤ííê³ kafka 를 ì¤íí´ì¤ëë¤. 1) Windows Kafka ê³µì ííì´ì§ìì ë°ì´ë리 íì¼ì ë°ìµëë¤. ìì¶ í´ì í ì¬ì©ìê° ìíë ëë í ë¦¬ë¡ ì´ëìí¤ê³ bin ëë í 리 ìì kafka-server-start.bat ì¼ë¡ ì¤íí©ëë¤. 2) Linux (Ubuntu) # Kafka ë¤ì´ë¡ë wget https://www.apache.org/dist/kafka/2.5.0/kafka_2.13-2.5.0.tgz # íì¼ ìì¶ í´ì tar zxf kafka_2.13-2.5.0.tgz # kafka ëë í 리 ì´ë (ì íì¬í) mv kafka_2.13-2.5.0 /usr/local/kafka 리ë ì¤ì ê²½ì° ì¤íí기 ì½ê² systemctl ëª ë ¹ì´ë¡ ë±ë¡ì í´ì¤ëë¤. # zookeeper ì¤í ëª ë ¹ì´ë¥¼ ë±ë¡ vim /etc/systemd/system/zookeeper.service [Unit] Description=Apache Zookeeper server Documentation=http://zookeeper.apache.org Requires=network.target remote-fs.target After=network.target remote-fs.target [Service] Type=simple ExecStart=/usr/local/kafka/bin/zookeeper-server-start.sh /usr/local/kafka/config/zookeeper.properties ExecStop=/usr/local/kafka/bin/zookeeper-server-stop.sh Restart=on-abnormal [Install] WantedBy=multi-user.target # kafka ëª ë ¹ì´ ë±ë¡ vim /etc/systemd/system/kafka.service [Unit] Description=Apache Kafka Server Documentation=http://kafka.apache.org/documentation.html Requires=zookeeper.service [Service] Type=simple Environment=\"JAVA_HOME={java home directory}\" ExecStart=/usr/local/kafka/bin/kafka-server-start.sh /usr/local/kafka/config/server.properties ExecStop=/usr/local/kafka/bin/kafka-server-stop.sh [Install] WantedBy=multi-user.target # ìë¡ ë§ë zookeeper, kafka ëª ë ¹ì´ ë±ë¡ sudo systemctl daemon-reload # zookeeper ì¤í sudo systemctl start zookeeper # kafka ì¤í sudo systemctl start kafka ì´ë ê² ì¤ì¹ ë° ì¤íì í´ë³´ììµëë¤. ","categories": ["posts"], 324 "tags": ["kafka"], 325 "url": "https://subji.github.io/posts/2020/06/11/kafkahowtoinstallUbuntu", 326 "teaser":null},{ 327 "title": "Spring Security ê¶íë³ Maximum Sessions ê²°ì ë°©ë²", 328 "excerpt":"ì¤íë§ ìí리í°ë¥¼ ì¬ì©íë¤ë³´ë©´ ì¤ë³µ ë¡ê·¸ì¸ì ë§ê±°ë í´ì í´ì¼ëë ê²½ì°ê° ììµëë¤. ì´ê²½ì°ìë ì¼ë°ì ì¼ë¡ maximumSessions() ì maxSessionPreventsLogin() ì ì¬ì©í곤 í©ëë¤. ê·¸ë°ë° ì¡°ê¸ ë ìì¸íê² ì¤ì ì íê³ ì¶ìëê° ììµëë¤. ì를ë¤ì´ ê´ë¦¬ìì ê²½ì°ìë ì¤ë³µë¡ê·¸ì¸ì íì©íê³ ê·¸ì¸ ì¬ì©ìì ê²½ì°ìë ì¤ë³µë¡ê·¸ì¸ì ë§ë ê²ê³¼ ê°ì´ ë§ì´ì£ . ê·¸ë°ë° ì ì¤ì ë§ì¼ë¡ë ì ì²´ì ì¼ë¡ ì¤ë³µë¡ê·¸ì¸ì ë§ëë¤ê±°ë í¹ì í¹ì ì¸ì ìë§í¼ íì´ì¤ë¤ê±°ë ê·¸ê²ë ìëë©´ ìì ì¤ë³µë¡ê·¸ì¸ì íì©íë¤ê±°ë íë ê² ë°ìë ìì´ë³´ì ëë¤. ê·¸ëë í¹ì ë¤ë¥¸ 무ì¸ê° ë°©ë²ì´ ìì§ ììê¹í´ì ì°¾ìë³´ë âConcurrentSessionControlAuthenticationStrategyâ ë¼ë í´ëì¤ë¥¼ ì¬ì©íë©´ ëë¤ë êµ°ì. ì¤íë§ ìí리í°ì 기본 Maximum Session ì¤ì ì ì ìì¼ë¡ ì ì©ëê² ëì´ììµëë¤. íì§ë§ ìì ë§í âConcurrentSessionControlAuthenticationStrategyâ ì âgetMaximumSessionsForThisUserâ ë©ìë를 ì¬ì©íë©´ ì주 ì½ê² ê¶í ëë ì¬ì©ìì ì ë³´ì ë°ë¼ì ì¸ì ì를 ì¡°ì íëê² ê°ë¥íë¤ê³ í©ëë¤. ëë¶ì´ ì¸ì ì ì± ?ì ëµ? ì ëí´ì ì¬ì©ìê° ììë¡ ì íê³ ì¶ìëìë âSessionAuthenticationStrategyâ ì 구í체를 ìì±íê±°ë ììì ì¸ê¸í âConcurrentSessionControlAuthenticationStrategyâ ì ë©ìëì¸ âgetMaximumSessionsForThisUserâ 를 ì¤ë²ë¼ì´ëíì¬ êµ¬ííë©´ ëê² ìµëë¤. ìëë getMaximumSessionsForThisUser 를 Override íë ìì ì ëë¤. public class CustomConcurrentSessionControlAuthenticationStrategy extends ConcurrentSessionControlAuthenticationStrategy { // ìµë ì¸ì ì ê¶íë³ë¡ ì ì© protected int getMaximumSessionsForThisUser(Authentication authentication) { boolean admin = // Check authentication.getAuthorities() for the admin role return admin ? 8 : 1; } } ììì ë§ë MaximumSession ë©ìëì 구í체를 Bean ì¼ë¡ ë±ë¡í©ëë¤. @Bean public CustomConcurrentSessionControlAuthenticationStrategy sessionControlStrategy() { return new CustomConcurrentSessionControlAuthenticationStrategy(new SessionRegistryImpl()); } ë±ë¡í Bean ì SessionManager ì SessionAuthenticationStratege ì ë±ë¡í©ëë¤. sessionManagement().sessionAuthenticationStrategy(se
328ssionControlStrategy()); ê¶íë³ë¡ ìµë ì¸ì íì© ì를 ì¤ì íë ë²ì ììë³´ììµëë¤. ì¶ì² : Spring Security concurrent session control per User group or per single user ","categories": ["posts"], 329 "tags": ["spring security"], 330 "url": "https://subji.github.io/posts/2020/06/16/springsecuritymaximumsessionbyroles", 331 "teaser":null},{ 332 "title": "MSA Spring Cloud Config", 333 "excerpt":"MSA ë§ì´í¬ë¡ìë¹ì¤ ìí¤í ì³ì ì¤ìë§ì ëë¤. ì´ë²ìë MSA ì ê°ë ì´ ìë ì¤ìµ ë´ì©ì¼ë¡ Spring Cloud Config 를 ì¬ì©íì¬ MSA ë´ì ì ì²´ì ì¸ ì¤ì ì ê´ë¦¬íë ìë¹ì¤ë¥¼ ì¤ìµí´ë³´ê³ ì í©ëë¤. 1. ì¤ì¹ Spring initializer ìì Spring Cloud Config, Spring Security 를 ì¶ê°í´ì ë¤ì´ë¡ëí©ëë¤. 2. Config ìë² íì±í Configuration ìë²ìì ì리ë Annotation ì Main Class ì ìì±í©ëë¤. @SpringBootApplication @EnableConfigServer public class ConfigApplication { public static void main(String[] args) { SpringApplication.run(ConfigApplication.class, args); } } 3. 기본 Application.yml & Application.properties ì¤ì ííë¤ 8888 ë²ì Configuration ìë²ì í¬í¸ë¡ ë§ì´ ì¬ì©í©ëë¤. ë±í ì´ì ë ìëê² ê°ìµëë¤. server: port: 8888 ë¤ìì¼ë¡ Config íì¼ë¤ì ì¬ë¦´ Git 주ì를 ì ë ¥í©ëë¤. spring: cloud: config: server: git: uri: ${Git (Github / others) Url} ì´ë ê² íì¼ë©´ Git ì Commit ì í´ì ì¬ë ¤ì¤ëë¤. ì¬ê¸°ìë¶í°ë ì íì¬íì ëë¤. # Config íì¼ì ì°¾ì ìì¹ë¥¼ ì§ì í©ëë¤. spring: cloud: config: server: native: search-locations: classpath:/shared # ììì ì ì´ë server ì profile ì¸ native 를 íì±íí©ëë¤. spring: profiles: active: native \t\t # ëì¤ì ì¸ì¦ & ì¸ê° ìë²ì¶ê°ì íìí Security ì¤ì ì ëë¤. spring: security: user: name: ${username} password: ${password} ì´ë ê² ìì±íë©´ Config ìë²ì ê°ë¨í ì¤ì ì ëì ëë¤. ì¤ííê³ http://localhost:8888 ë¡ ì ìíë©´ Security ì¤ì ì í ê²½ì°ìë ìì´ë/ë¹ë°ë²í¸ë¥¼ 묻ë ì°½ì´ ëì¤ê³ ê·¸ ì¸ìë ì무ê²ë ëì¤ì§ ììµëë¤. ì´ì ììì ìë¹ì¤ê° ì¬ì©í Config íì¼ì ë§ë¤ì´ë´ ëë¤. ììì classpath ìë shared ëë í 리를 ì¤ì íì¼ ê²½ë¡ë¡ ì§ì í´ì£¼ìì¼ë ê·¸ ê²½ë¡ì íì¼ì íë ë§ë¤ì´ì¤ëë¤. ì´ë, íì¼ì´ë¦ì´ ì¤ìí©ëë¤. {application name}-{profiles name}.yml ë¡ ìì±íëê²ì´ ì¢ìµëë¤. Config ìë²ê° Config íì¼ì ë°íí ë ë³´íµ ì£¼ìì Context 를 ì°¾ì ë°ííê² ë©ëë¤. ì를ë¤ì´ http://config:8888/service/dev ë application ì´ë¦ì´ service, í´ë¹ ì´í리ì¼ì´ì ì profiles ì´ë¦ì´ dev ì¸ íì¼ì ì°¾ì í´ë¹ ì¤ì ì ë°íí©ëë¤. ê·¸ë¬ë©´ ììì ìë¹ì¤ì í¬í¸ë§ ë°ííë ê°ë¨í íì¼ì ë§ë¤ì´ì¤ëë¤. ì ë service-dev.yml ë¡ ìì±íììµëë¤. # service-dev.yml server: port: 8090 ê·¸ë¦¬ê³ Config ìë²ì ë´ì©ì Commit íê³ ì¤íí´ë´ ëë¤. ì´ë ê²íë©´ ê°ë¨íê² Config ìë² ì¸í ì ëì´ ë¬ìµëë¤. 4. í ì¤í¸ curl -X GET http://locahost:8888/service/dev ë¡ íì¸í´ë´ ëë¤. 5. Config Client ì¸í Spring initializer ìì Spring Config Client, Spring Security 를 ì¶ê° í ë¤ì´ë¡ë í©ëë¤. ê·¸ë¦¬ê³ application.properties 를 bootstrap.yml ë¡ ë³ê²½í©ëë¤. ê·¸ ì´ì ë bootstrap ì ê²½ì° application.yml ì´ì ì ë¶ë¬ì¤ê¸° ë문ì ëë¤. ì´ì ë¨ìê±´ ì¤ì ì ëë¤. spring: application: name: service profiles: active: dev cloud: config: uri: http://localhost:8888 fail-fast: true password: password username: user ì´ì ì ë§íë¤ìí¼ application ì´ë¦ê³¼ profiles ì´ë¦ì ìì±íìê³ , Config ìë²ì 주ì를 ìì±í´ì£¼ììµëë¤. ì´ì Config Server 를 ì¤íí ìíë¡ Service 를 ì¤íí´ë´ ëë¤. ê·¸ë¦¬ê³ http://locahost:8090 ì¼ë¡ ì ìí´ë´ ëë¤. ì ìì´ ìëë©´ ì¤ì ì ì ê°ì ¸ì¨ ê²ì ëë¤. ","categories": ["posts"], 334 "tags": ["msa"], 335 "url": "https://subji.github.io/posts/2020/06/17/msa2", 336 "teaser":null},{ 337 "title": "MSA (Micro Service Architecture) ë", 338 "excerpt":"1. MSA ë? ëê·ëª¨ ìë¹ì¤ìì ë§ì´ ì¬ì©ëë MSA ê° ë¬´ìì¼ê¹? ë§ ê·¸ëë¡ íì´í´ë³´ë©´ ìì ìë¹ì¤ë¡ ì´ë£¨ì´ì§ 구조ì´ë¤. ìì ìë¹ì¤ë¡ ì´ë£¨ì´ì§ 구조ë ë 무ìì¼ê¹? MSA ë SOA (Service Oriented Architecture) ë¼ë 구조ì ìì? ì미ë¼ê³ ë íë¤. SOA ë 기ì (Enterprise) ë¨ììì ì¬ì©ëìë ìë¹ì¤ 기ë°ì¼ë¡ ì´ë£¨ì´ì§ ìíí¸ì¨ì´ 구조ì´ë¤. MSA ë SOA ê°ë ì 기ë°ì¼ë¡ ëê
338·ëª¨, ëì©ë ìë¹ì¤ë¥¼ ì´ì©í기 ìí SOA ë³´ë¤ë ì¡°ê¸ ê°ë²¼ì´ 구조ë¼ê³ ë³¼ ì ìë¤. MSA ë ìì ìë¹ì¤ë¡ ì´ë£¨ì´ì§ 구조ë¼ê³ íìë¤. ì´ ê°ë
ì´ ë¯ì ì´ì ë 무ìì¼ê¹? ê·¸ ì´ì ë ê°ê¹ì´ìì ì°¾ì ì ìë¤. ìì§ë ë§ì´ ì¬ì©íë ì¹ ìë¹ì¤ ê°ë° 구조를 ë³´ì. íëì íë¡ì í¸ë¥¼ ìì±íê³ ê·¸ íë¡ì í¸ ìì ê¸°ë¥ A, ê¸°ë¥ B, ê¸°ë¥ â¦, ì ëë¤. ëí íëì íë¡ì í¸ìë íëì DB ê° ì°ê²°ëì´ ìì¼ë©°, WAS ìì íëë¡ êµ¬ì±ëì´ ìë¤. ìµìí ì´ êµ¬ì¡°ë¥¼ 모ëë¦¬í± êµ¬ì¡° (Monolithic Architecture) ë¼ê³ íë¤. 모ëë¦¬í± êµ¬ì¡° (Monolithic Architecture) ê·¸ë¬ë©´ 기존ì ì ì¬ì©íë 모ëë¦¬í± êµ¬ì¡°ë¥¼ ì êµ³ì´ MSA 를 ëì
íë ¤íê³ ë ì´ë¯¸ ì¬ì©íë 기ì
ë¤ì´ ë§ì 걸ê¹? 모ëë¦¬í± êµ¬ì¡°ì ë¨ì ì ë³´ë©´ ì ì ìë¤. 모ëë¦¬í± êµ¬ì¡°ë íëì íë¡ì í¸ ìì ì¬ë¬ 기ë¥(ìë¹ì¤) ê° ë¤ì´ê° ìë¤ê³ íë¤. ë§ì½ ì¬ê¸°ì ì주 ë°°í¬ëë íë¡ì í¸ì ê²½ì°ë ì´ë»ê² ë ê¹? ìë¹ì¤ A ìì ì½ë를 ì¡°ê¸ ìì íë¤ê³ í´ë³´ì 모ëë¦¬í± êµ¬ì¡°ì ê²½ì° íë¡ì í¸ ì 체를 ë¤ì ë°°í¬í´ì¼ ëë¤. ì¦, ë í´ë ì 체를 ë¤ì ììí´ì¼ ëë¤ë ê²ì´ë¤. ëí ì¬ë¬ ìë¹ì¤ë¤ì´ ìë¡ ê°ì Dependency ê° ëì ì ì기 ë문ì ìë¹ì¤ A ì 기ë¥ì ë³ê²½í ê²½ì° ì´ë¤ ìë¹ì¤ì ìí¥ì ë¯¸ì¹ ìë ìë¤. ê°ì ì´ì ë¡ ì´ë í ìë¹ì¤ì ìë¡ì´ ë²ì ì ê¸°ë¥ ë° ë¤ë¥¸ 구조를 ì ì©í기ë ì´ë ¤ìì§ë©°, ìë¹ì¤ì íì¥ë©´ìë ë¤ë¥¸ ìë¹ì¤ë¤ê³¼ì Dependency 를 ê³ ë ¤í´ì¼í기 ë문ì ì´ë µë¤ë ë¨ì ì´ìë¤. ì´ë¬í 모ëë¦¬í± êµ¬ì¡°ì ë¨ì ë¤ì í´ê²°íê³ ì ì¬ì©ëë ê²ì´ MSA 구조ì´ë¤. ìì 모ëë¦¬í± êµ¬ì¡°ì 그림과ë ì¡°ê¸ ë¤ë¥¸ 구조ì´ë¤. MSA 구조 (Micro Service Architecture) 2. MSA ì¥ë¨ì MSA ë íëì ìë¹ì¤ë¥¼ ìµìì 기ë¥ë§ì ìííë ë¨ìë¡ ëëê³ ì´ì ì°ê²°ëë DB, WAS 를 ê°ê° íëì© ëë ì¬ë¬ê°ì íë¡ì í¸ë¡ ì´ë£¨ì´ì§ 구조ì´ë¤. ê·¸ë¬ë©´ MSA ë ì´ë¤ ì¥ì ë¤ì´ ìì¼ë©° ë ì´ë¤ ë¨ì ë¤ì´ ììê¹. MSA ì ì¥ì ì 모ëë¦¬í± êµ¬ì¡°ì ë¨ì ì ë°ëëë ë´ì©ì´ ë§ë¤. 첫쨰, ë°°í¬ ìê° ë¨ì¶. íëì ìë¹ì¤ë ìµìíì ë¨ìë¡ ì´ë£¨ì´ì ¸ ìë¤. ê·¸ë¬ë©´ ë¹ì°í ì¬ì´ì¦ë ê·¸ìë§ê² ìë¤. ì¦, ë¹ëí ë´ì©ì´ ì ì¼ë ë¹ëì ìëë ë¹ ë¥´ë¤. ë¹ëê° ë¹ ë¥´ë©´ ë°°í¬ ëí ì¤ëê±¸ë¦¬ì§ ìëë¤. ë쨰, íëì 기ì ì ì½ë§¤ì´ì§ ìëë¤. ê°ê°ì ìë¹ì¤ë ìë¡ ë¤ë¥¸ íë¡ê·¸ëë° ì¸ì´ë¡ 구ì±í´ë ëê³ ìëë©´ ìë¡ ë¤ë¥¸ 구조를 ì¬ì©í´ë ëë¤. ì´ë¥¼ ì¼ì»¬ì´ í´ë¦¬ê¸ë 구조ë¼ê³ íë¤. MSA ììë ì´ë¬í ì ì´ ë¤ë¥¸ ìë¹ì¤ì ìí¥ì 미ì¹ì§ ìëë¤. ìëíë©´ ìë¹ì¤ë¤ ê°ê°ì´ ìë ìë¹ì¤ì ë´ë¶ 구조를 ìì§ë 못í ë¿ëë¬ ìíìë ì기 ë문ì´ë¤. âí´ë¦¬ê¸ë (Polyglot) : ìë¡ ë¤ë¥¸ íë¡ê·¸ëë° ì¸ì´ì 기ì ì ì¬ì©í 구조â ì
쨰, íì¥ì±. 모ëë¦¬í± êµ¬ì¡°ììë ìë² ê³¼ë¶íì ë°ë¥¸ ìë¹ì¤ íì¥ìì íëì íë¡ì í¸ë¥¼ ì¬ë¬ê° ìì±íì¬ L4 ì¤ìì¹ë¥¼ ì´ì©í´ ë¡ë ë°¸ë°ì±ì 구ì±íë¤. íì§ë§ MSA ììë ë¤ë¥´ë¤. ìë¹ì¤ A ì ë§ì ê³¼ë¶íê° ìì¼ë©´ ìë¹ì¤ A ë§ ì¬ë¬ê°ë¡ 구ì±íë©´ ëë¤. ë¡ë ë°¸ë°ì± 구ì±ì ëì¤ì Ribbon ì´ë¼ë 기ì ì ì¬ì©íì¬ Client 쪽ìì 구ì±í ì ìê³ ê¸°ì¡´ì L4 ì¤ìì¹ë¥¼ ì´ì©í´ì 구ì±í´ë ëë¤. ì´ì¸ìë ì¬ë¬ê°ì§ ì¥ì ì´ ìì§ë§ ì ì¸ê°ì§ë ì¬ë¬ ë¸ë¡ê·¸, ì±
ë±ìì ëì¤ë ëíì ì¸ ì¥ì ë¤ì´ë¤. MSA ë ì¥ì ë§ ìë ê²ì¼ê¹? ë¬¼ë¡ ë¨ì ë ì¡´ì¬íë¤. MSA ì ë¨ì ì ë°ëë¡ ëª¨ëë¦¬í± êµ¬ì¡°ì ì¥ì ì´ ë ìë ìë¤. 첫째, íµí© í
ì¤í¸ì ì´ë ¤ì 모ëë¦¬í± êµ¬ì¡°ììë íëì íë¡ì í¸ì ì ì²´ ìë¹ì¤ê° ë¤ì´ê° ì기 ë문ì í´ë¹ íë¡ì í¸ ë´ìì í
ì¤í¸ ì½ë를 ìì±í´ì í
ì¤í¸ë¥¼ ì½ê² í ì ìë¤. ë°ë©´ì MSA 구조ììë ìë¹ì¤ê° ê°ê° ê°ë³ë¡ ì¡´ì¬í기 ë문ì íë¡ì í¸ ì ì²´ì íµí© í
ì¤í¸ë¥¼ íê¸°ê° ì´ë ¤ì´ ë¶ë¶ì´ ìë¤. ë째, ì±ë¥ 모ëë¦¬í± êµ¬ì¡°ë ìë¹ì¤ë¤ì´ í íë¡ì í¸ ìì ìì¼ë¯ë¡ ìë¡ Function ë
338ë Method 를 í¸ì¶í´ì 참조í ì ìë¤. ë°ë©´ì MSA ë ë¶ë¦¬ë ìë¹ì¤ 구조ì´ê¸° ë문ì REST, Message ë±ì 기ì ë¡ ë´ì©ì ì£¼ê³ ë°ìì¼íë¤. ì§ ìì ìë ê°ê²ì ê°ì ì§ì 물건ì ì¬ì¤ë ê²ê³¼ ì§ ìì ìëë° êµ³ì´ ë°°ë¬ì ì´ì©í´ì ë°°ë¬ ì§ì°ë±ë±ì ìí©ì ê³ ë ¤í´ê°ë©° 물건ì ë°ë ê²ì ì°¨ì´ì¼ê¹? ì
쨰, í¸ëìì
ê´ë¦¬ MSA ë ê°ê° ìë¹ì¤ë¤ì í¸ì¶í´ê°ë©° í¸ëìì
ì ì§ííë¤. MSA 구조ì í¸ëìì
ì 그림ìì ë³´ë©´ ìë¹ì¤ A -> ìë¹ì¤ B ë¡ ê°ë í¸ëìì
A ê° ìê³ , ìë¹ì¤ C ìì ìë¹ì¤ B ë¡ ê°ë í¸ëìì
B ê° ìë¤. ê°ê°ì í¸ëìì
ì ìë¡ ë³ê°ì í¸ëìì
ì´ê¸° ë문ì ì´ë¥¼ ê´ë¦¬í´íëë° ì´ë ¤ìì´ ìë¤. ë³´íµ ëª¨ëë¦¬í± êµ¬ì¡°ììë íëì ë°ì´í°ì ì´ëì ë°ë¼ íëì í¸ëìì
ì¼ë¡ 구ì±ë기 ë문ì ê´ë¦¬ê° ì½ì§ë§ MSA 구조ììë ê·¸ë ì§ ìë¤. ì´ë¥¼ í´ê²°í기 ìí´ ë³´ì í¸ëìì
, ë¶ì° í¸ëìì
ë±ì 기ì ì´ ìë¤. 3. MSA êµ¬ì± MSA ë ì´ë»ê² 구ì±ëì´ ììê¹? ì¬ë¬ ê¸ì ë´¤ì§ë§ ë± ì´ê±°ë¤! ë¼ê³ ë´ë¦° ì ìë 구ì±ì ììë¤. ë¤ë§ ì´ëì ë ì¤ë³µëë ê°ë
ì ììë¤. 모ëë¦¬í± êµ¬ì¡°ì í
338êµ¬ì± Gateway ìì íì í목ì¼ë¡ Security 를 ê°ì§ê³ ìë¤. Security ë ì¸ì¦ & ì¸ê° ìí ì ìííë©° Gateway ììë ì¸ì¦ & ì¸ê° ë´ì©ì ë°íì¼ë¡ ë§ë ìë¹ì¤ë¥¼ í¸ì¶ ëë ì ê·¼ ë¶ê° ëìì ìííë¤. Discovery ìì MSA ì 구ì±ë ìë¹ì¤ë¤ì ë±ë¡íë¤. ë±ë¡ë ìë¹ì¤ë¤ì Health Check ë° ë¤ë¥¸ ìë¹ì¤ì í¸ì¶ ìì²ì ìíë ìë¹ì¤ë¥¼ ì°¾ëë° ëìì ì¤ë¤. CI/CD ìì ìë¹ì¤ì ì§ìì ì¸ ë°°í¬ ë° íµí©ì ê´ë¦¬íë¤. Testing ìì ìë¹ì¤ ê°ê°ì í
ì¤í¸ ëë ì ì²´ì ì¸ í
ì¤í¸ë¥¼ ê´ë¦¬ ë° ì¤ííë¤. Communication ìì ìë¹ì¤ë¤ê°ì íµì ì²ë¦¬ë¥¼ ë´ë¹íë¤. íµì ì²ë¦¬ë AMQP ì ê°ì ë©ì¸ì§ íë¡í ì½ì´ ë ìëìê³ JSON ì ì¬ì©íë REST ë°©ìì¼ ìëìë¤. Observing ìì ìë¹ì¤ë¤ ê°ê°ì 모ëí°ë§ì ë´ë¹íë¤. MSA 구ì±ì ê°ë¨íê² ìì±í´ ë³´ìë¤. ë¤ì ë§íì§ë§ MSA 구ì±ìë ì ëµì´ ìì¼ë©° ëëí ë§ì´ ì¬ì©ëë ë¶ë¶ì ì°¸ê³ íì¬ ìì±í ê²ì´ë¤. MSA êµ¬ì± ê´ë ¨í´ìë ê°í¸ëìì ì¸ê¸í 구ì±ë ìê³ , microservice.io ë¼ê³ Chris Richardson ì´ë¼ë ë¶ì´ ì´ìíë íì´ì§ììë íì¸ ê°ë¥íë¤. ê·¸ì¸ìë ê²ìì íµí´ ì¬ë¬ ì¬ëë¤ì´ ë¤ìíê² êµ¬ì±í MSA 구조를 ë³¼ ì ìë¤. ì¬ë¬ ê³³ì ì°¸ê³ íì¬ ìì ì´ ì´ìí ìë¹ì¤ì ë§ë 구조를 구ì±íëë¡ íì. 4. MSA ëì
ì´ë ê² ë³´ë©´ í루빨리 MSA 구조를 ëì
í´ì¼ë§ í ê² ê°ë¤. íì§ë§ ë 무조건ì ì¸ê±´ ìëë¤. 모ëë¦¬í± êµ¬ì¡°ë ì ë ëì êµ¬ì¡°ê° ìëë¤. ê·¸ë ë¤ê³ MSA 구조 ëí ì ë ì¢ì êµ¬ì¡°ê° ìëë¤. ìë¡ ë¤ë¥¸ 구조ë¼ê³ ë³´ëê² ë§ëê² ê°ë¤. ì를ë¤ì´ í¬ì§ ìì ê²½ë ìë¹ì¤ì ê²½ì°ìë ì¤íë ¤ 모ëë¦¬í± êµ¬ì¡°ê° ì ì©í ì ìë¤. ì´ë ì±ë¥ ìì ì´ì ì ê°ì ¸ì¬ìë ì기 ë문ì´ë¤. ê°ì¸ì ì¼ë¡ë MSA ë 모ëë¦¬í± êµ¬ì¡°ì ê°ì´ ì´ì¸ë¦¬ë ê² ê°ê¸°ëíë¤. MSA ì ìì ìë¹ì¤ ë¨ì íëê° ëª¨ëë¦¬í± êµ¬ì¡°ë¡ ì¤ê³ ë ìë ìì§ ììê°? MSA 구조ë íì êµ¬ì± ëí ì¤ìíë¤. 모ëë¦¬í± êµ¬ì¡°ì í
338 구ì±ì ë³´ë©´ ëë¶ë¶ 기í/ê°ë°/ëìì¸/ì´ì ë±ì¼ë¡ ì´ë£¨ì´ì ¸ ìë¤. 모ëë¦¬í± êµ¬ì¡°ì íêµ¬ì± ë°ë©´ì MSA 구조ì í 구ì±ì ê°ê°ì ìë¹ì¤ì 7~10 ëª ì ëì ì¸ìì¼ë¡ 구ì±ëë©°, ê·¸ ììì 기í/ê°ë°/ëìì¸/ì´ìì¼ë¡ ì´ë£¨ì´ì§ë¤. ìµì¢ ì ì¼ë¡ ì´ë¬í ìê·ëª¨ì ì¸ìì¼ë¡ 구ì±ë íì´ íëì ìë¹ì¤ë¥¼ ë´ë¹íë©° ê·¸ íë¤ì´ ì¬ë¬ê°ë¡ 구ì±ëì´ ìë¤. MSA 구조ì íêµ¬ì± ë¬¼ë¡ ì´ê² ì ëµì ìë ê²ì´ë¤. ëê·ëª¨ ì¸ìì¼ë¡ ì´ë£¨ì´ì§ íëì íì¼ë¡ MSA 를 ì§íí ìë ìê³ ìê·ëª¨ì ì¸ìì¼ë¡ 구ì±ë ì¬ë¬ê°ì íì¼ë¡ 모ëë¦¬í± êµ¬ì¡°ë¥¼ ì§í í ìë ìë¤. íì§ë§ ìì ëì¨ ê·¸ë¦¼ë¤ì ê° ìíí¸ì¨ì´ 구조ì ê·¸ëë§? ìµì íë 구ì±ì´ ìëê¹ ì¶ë¤. 5. ë ë¯ì ë´ì©ì ì¸ì ë ì²ìì´ ì´ë µë¤. íì§ë§ 찬찬í íëì© ì ì©í´ê°ë©° ì´í´íë©´ ë ê¸ë°©ì´ë¤. ì¤ë§í¸í° ë³´ê¸ì¼ë¡ ì¸í°ë· ì¬ì©ë¥ ì´ ì¦ê°íê³ ìë ì¸ììì ì´ë í ìë¹ì¤ëì§ ë§ì ì¬ì©ëì 기ë¡í ìë ìë¤. 무조건ì ì¼ë¡ ë³´ë¥í ë§í ë´ì©ì ìëë¤. ìì ì´ ê°ë°í ìë¹ì¤ ëë íì´ ê°ë°í ìë¹ì¤ê° 커ì§ê²ì´ë¼ê³ ë¯¿ê³ MSA 를 ê³µë¶í´ë³´ì! ","categories": ["posts"], 339 "tags": ["msa"], 340 "url": "https://subji.github.io/posts/2020/06/19/msa1", 341 "teaser":null},{ 342 "title": "Non-ASCII character 'ë' in file run.py on line {N}, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details", 343 "excerpt":"íì´ì¬ìì 주ìë± íê¸ì ì¬ì©í ë ë°ìíë ì¸ì½ë©ê´ë ¨ ì¤ë¥ì´ë¤. ì¼ë¨ ìë¬ë©ì¸ì§ì ëì¨ëë¡ http://python.org/dev/peps/pep-0263/ ì ì ìí´ë³´ì. Problem í목ì ë¤ì¬ë¤ë³´ì. In Python 2.1, Unicode literals can only be written using the Latin-1 based encoding \"unicode-escape\". ì¬ê¸°ê° íµì¬ì´ë¤. íì´ì¬ 2.x ë²ì ëììë ì ëì½ë 리í°ë´ì Latin-1 ê¸°ë° ì¸ì½ë© âunicode-escapeâ ë¡ ìì¬ì ¸ ìë¤ê³ ëììë¤. ì°ë¦¬ê° ì´ íê¸ì Latin-1 기ë°ììë ì§ìëì§ ìëë¤. í´ê²°ë°©ë²ì ê·¸ ë°ë¡ ìëì ì ëììë¤. 기본 ì¸ì½ë©ì íì´ì¬ íì¼ì ì ìíë¼ê³ ëì´ìë¤. ì ì©ë°©ë²ì ë¤ìê³¼ ê°ë¤. íì´ì¬ íì¼ ì ì¼ ìì ë¤ì Comment 를 ì¶ê°íë¤. íê¸ì ì§ìí´ì¼ íë¯ë¡ UTF-8 ì ì¬ì©íë¤. #!/usr/bin/python # -*- coding: <encoding name> -*- #!/usr/bin/python # -*- coding: utf-8 -*- ","categories": ["posts"], 344 "tags": ["python"], 345 "url": "https://subji.github.io/posts/2020/06/19/python2.xencoding", 346 "teaser":null},{ 347 "title": "TDD(Test Driven Development) ë (1)", 348 "excerpt":"ìë ë´ì©ì âí ì¤í¸ 주ë ê°ë°: TDD ì¤ì²ë²ê³¼ ë구â(https://repo.yona.io/doortts/blog/issue/1)를 ë³´ê³ ì£¼ì ë´ì©ì ê°ì¸ì ì¼ë¡ ì 리íì¬ ìì±íìµëë¤. TDD ? í ì¤í¸ 주ë ê°ë°ì ì¤ìë§ì´ë¤. ë¹ ë¥¸ ê°ë°ì ì§ííë©° ë§¤ë² í´ì¼ì§ë§ ìê°íê³ ë§ì ì¤ì²íì§ë 못íë¤. í ì¤í¸ 주ë ê°ë°ì´ ëê¹? ë§ ê·¸ëë í´ìí´ë³´ìë©´ í ì¤í¸ë¥¼ ì°ì ì íë ê°ë°ì´ë¼ê³ í ì ìë¤. âê·¸ëì ê·¸ê² ëëì²´ 무ì¨ë§ì¸ë°..â ìë°ë¥¼ ìë¡ ë¤ë©´ ì¼ë°ì ì¼ë¡ í´ëì¤ë¥¼ ìì±íê³ íë¡í¼í°ì ë©ìë를 ìì±íê³ ì½ë를 ì¤ííë¤. ê·¸ë¦¬ê³ ì½ìì´ë ë¤ë¥¸ ë°©ë²ì¼ë¡ í´ë¹ ì½ëê° ì ì¤íì´ ëìëì§ ëì¼ë¡ íì¸íë¤. í ì¤í¸ 주ë ê°ë°ì ì¡°ê¸ ë¤ë¥´ë¤. ê°ì¥ í° ì°¨ì´ì ì ì½ë를 ìì±í기ì ì í ì¤í¸ ì½ë를 먼ì ìì±íë¤ë ê²ì´ë¤. í ì¤í¸ ì½ë를 먼ì ìì±íë¤ëê² ë¬´ì¨ ì미를 ê°ì§ê¹? ì¢ ë ê¹ëíê³ ë¨ë¨í ì½ë를 ìì±íê² ëë ë°ê±°ë¦ì´ ë ì ìë¤. ê·¸ë ë¤ê³ í ì¤í¸ ì½ë를 먼ì ìì±íì§ ìê³ ìì±í ì½ëê° ì못 ë¬ë¤ë ê²ì ìëë¤. ë¤ë§ í ì¤í¸ ì½ë를 먼ì ìì±í¨ì¼ë¡ì¨ ì¤ë ì´ì ì´ ì¡°ê¸ ë í¬ë¤ë ì ì´ë¤. ê·¸ë ë¤ë©´ í ì¤í¸ ì½ë를 ìì±íë©´ ì»ë ì´ì ì´ ëê° ìì¼ë©° ë ë¨ì ì ëê° ììê¹? TDD ì¥ë¨ì ? TDD ì ì¥ì ì ìì ì¸ê¸ íìë ê¹ëíê³ ë¨ë¨í ì½ë를 ë§ë¤ ì ìë¤ë ì ì´ë¤. ëí ê°ë° ì½ëì ìì í QA ë° ê¸°í í ì¤í¸ë¥¼ ìííë ê³¼ì ìì ì ì§ë³´ìê° ì©ì´íë¤ë ì ì´ë¤. ì를ë¤ì´ë³´ì. ê²°ì íë¡ì í¸ìì ê²°ì ì¤í ì½ë ë¶ë¶ì ì¡°ê¸ ìì íë¤ê³ íì. ì½ë ìì ì´ ëë íìë ë¤ì í ì¤í¸ê° ì§íëì´ì¼ íë¤. ì´ë ìì ë ë¶ë¶ììë§ í ì¤í¸ê° ì§íëëê² ìëë¼ ìì ë ë¶ë¶ê³¼ ì°ê²°ë 기존ì ì¤í ê³¼ì ìì²´ë í ì¤í¸ê° ëì´ì¼ íë¤. ë§ì½ ìì ë ë¶ë¶ì´ ë¤ë¥¸ ì½ëì ì¢ ìì ì¸ ê´ê³ì ìë¤ë©´ ë ê·¸ ë¶ë¶ìì ì¤ë¥ê° ë°ìíë¤ë©´ ê³¼ì° ë¹ ë¥´ê² ì°¾ì ë¼ ì ììê¹? íì¤íê² ì°¾ìë¼ ì ììê¹? ì¬ê¸°ì í ì¤í¸ ì½ëì ì¥ì ì´ ë°íëë¤. ê°ë°ìë ìì ë ì½ë를 í ì¤í¸ í기 ìí´ ê¸°ì¡´ì í ì¤í¸ ì½ë를 ì¤ííë¤. í ì¤í¸ ì½ëê° ì ì²´ ìë£ëë©´ ì±ê³µì¸ ê²ì´ê³ , ì¤í¨ ëë ë¶ë¶ì´ ìì¼ë©´ 그곳ì ë¤ì íì¸íë©´ëë¤. ì¦, íë¡ì í¸ì ì ì§ë³´ìì ì©ì´íë¤. ëí í ì¤í¸ ì½ëë ìë리ì¤ë¥¼ ë°íì¼ë¡ ìì±íê² ëëë°, ì´ë ì¶í ì¸ìì¸ê³ ë° ì½ë ì¤ëª ìì ì ì©í ìë£ê° ëë¤. ì ì§ì¬ì§ í ì¤í¸ ìë리ì¤ë ë¤ë¥¸ ê°ë°ìë¤ìê² ì¢ì 매ë´ì¼ì´ ë ì ìë¤. ë§ì ìì´ ìì±ë ì½ë를 ì¬ê¸°ì 기 ì°¾ìê°ë©° ë°ë¼ê°ëê²ì´ ë¹ ë¥¼ ê² ê°ìê°? ìëë©´ ëêµ°ê°ê° ë§ë¤ì´ëì 매ë´ì¼ì ë°ë¼ê°ë©° íì¸íë ê²ì´ ë¹ ë¥¼ ê² ê°ìê°? ë§ì§ë§ì¼ë¡ ìíí¸ì¨ì´ ì¤ê³ì ì¢ì ìí¥ì 미ì¹ë¤. ì를ë¤ì´ í´ëì¤ë¥¼ íë ì¤ê³í ëë ë³ì ëª , ë©ìë ëª , íë¼ë¯¸í°, Arguments ë±ìì ë¤ìíë²ë³´ê² ëê³ ë ê¹ëíê² ìì±ëëë¡ ëìì¤ë¤. ì´ì¸ìë ì¬ë¬ ì¥ì ì´ ììê²ì´ë¤. ë¤ìì ë¨ì ì ê¼½ìì¼ íëë° ì¬ì¤ ë¨ì ì´ë¼ê³ í ê² ê°ì¸ì ì¼ë¡ ì¼ì ì´ ì§§ì íë¡ì í¸ììë ë§ì ìê°ì í ë¹í기 ì´ë µë¤ë ì ? ë¿ì´ë¤. íì§ë§ ì´ì ëì ìì ë¨ì ì¼ë¡ ììì ë§í í° ì¥ì ë¤ì í¬ê¸°í기ì ìì½ë¤. TDD ìì± ë°©ë² TDD ì ìì±ë°©ë²ì í° íìì ë¤ìê³¼ ê°ë¤. 먼ì ìë리ì¤ë¥¼ íë ì ííë¤. QA ë° ê¸°ííìì ë§ë¤ì´ ëì ìë리ì¤ê° ìì¼ë©´ 그걸 ë°ë¼ê°ë©´ ëë¤. íì§ë§ ê·¸ë ì§ ìë¤ë©´ ê°ë°ìê° ì¤ì¤ë¡ì í ì¤í¸ ìë리ì¤ë¥¼ ìì±í´ì¼ íë¤. ìë리ì¤ë¥¼ íë ì ííì¼ë©´ ê·¸ ìë리ì¤ì ëí 결과를 ìì±íë¤. ê²°ê³¼ë ì±ê³µ ëë ì¤í¨ê° ë ì ìë¤. ìëë©´ í ì¤í¸ ì½ë ìì²´ì ì¤ë¥ê° ë ì ìë¤. ê°ê¸ì ì¤ë¥ë ë§ë¤ì§ ìëë¡ ìì±íì. ê²°ê³¼ì ì±ê³µì ì íí ìë리ì¤ê° ë§ë ê°, ìëë, ì측ë ê°ë±ì ì¶ë ¥íë¤ë ê²ì ì미íê³ , ì¤í¨ë ë§ì§ ìë, ìëíì§ ìì ê°, ì측ì ì¤í¨ í ê°ë±ì´ ë ê²ì´ë¤. ëì¼ë¡ 리í©í ë§ì´ ìë¤. ìë리ì¤ë¥¼ ì ííê³ ê²°ê³¼ê°ì íë¨íë ì½ë를 ë§ë¤ìë¤ë©´ ì´ë¥¼ ë°íì¼ë¡ ê°ë° ì½ë를 리í©í ë§í´ì¼ íë¤. ë TDD ë ê°ë ì´ë¤. ì´ë¤ íë ììí¬ë ë¼ì´ë¸ë¬ë¦¬ë¥¼ ì¬ì©íë í¹ì 기ì ì´ ìëë¼. í´ë¹ 기ì ì ë¶ìì ì¼ë¡ ì¬ì©íë ìíí¸ì¨ì´ ì¤ê³ ê°ë ì´ë¼ê³ ìê°íë¤. ","categories": ["posts"], 349 "tags": ["tdd"], 350 "url": "https://subji.github.io/posts/2020/06/22/tdd-java-1", 351 "teaser":null},{ 352 "title": "TDD(Test Driven Development) - JUnit 5 (2)", 353 "excerpt":"ìë ë´ì©ì âí ì¤í¸ 주ë ê°ë°: TDD ì¤ì²ë²ê³¼ ë구â(https://repo.yona.io/doortts/blog/issue/1)를 ë³´ê³ ì£¼ì ë´ì©ì ê°ì¸ì ì¼ë¡ ì 리íì¬ ìì±íìµëë¤. JUnit ? Java ìì ì¬ì©ëë Testing íë ììí¬ì´ë¤. ë¨ì í ì¤í¸ë¥¼ í ë ì¬ì©ëë ë구ì´ë©° í ì¤í¸ ê²°ê³¼ì ììê°ì íì¸ìì¼ì£¼ë Assert ì í ì¤í¸ ì¤íì ëì주ë Test Runner ë±ì¼ë¡ 구ì±ëì´ ìë¤. xUnit ì´ë¼ê³ í´ì ìë° ì¸ì ë¤ë¥¸ ì¸ì´ìì ì¬ì©ëë í ì¤í¸ ëêµ¬ê° ìë¤. ì를ë¤ì´ C ì ê²½ì° CUnit C++ ì ê²½ì° CppUnit ë±ì´ ìë¤. JUnit, Hamcrest ì¤ì JUnit 4 ë²ì ììë ë¨ì¼ jar ë§ ì¬ì©ëììëë° JUnit 5 ë¡ ëì´ì¤ë©´ì ì¬ë¬ê°ì jar ë¡ ë¶ë¦¬ëìë¤ê³ íë¤. JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage JUnit Platform : JVM ììì ì¤íëë í ì¤í¸ íë ììí¬ ì´ë¤. JUnit ê³¼ Client Build Tool ì¬ì´ìì ê°ë ¥í ì¸í°íì´ì¤ë¥¼ ì ê³µíë¤. ëí JUnit íë«í¼ ììì ì¤íëë í ì¤í¸ íë ììí¬ë¥¼ ê°ë°í기 ìí TestEngine API 를 ì ê³µíë¤. ì´ë¥¼ ì¬ì©í´ì ì¨ëíí° í ì¤í¸ ë¼ì´ë¸ë¬ë¦¬ë¥¼ JUnit ì ì§ì íë¬ê·¸ì¸ í ì ìë¤. JUnit Jupiter : @BeforeEach, @AfterEach, ë±ì 5ë²ì ìì ìë¡ì´ ì¶ê°ë íë¡ê·¸ëë° ëª¨ë¸ê³¼ íì¥ ëª¨ë¸ë¤ì í¬í¨íê³ ìë¤. JUnit 4 ìì ì¶ê°ëê±°ë ë³ê²½ë Annotation ì ë¤ìê³¼ ê°ë¤. @TestFactory : ëì í ì¤í¸ë¥¼ ìí í ì¤í¸ í©í 리 ë©ìë를 ê°ë¦¬í¨ë¤. @DisplayName : í ì¤í¸ í´ëì¤ ëë ë©ìëì ì¬ì©ì ì ì ì´ë¦ì íìíë¤. @Nested : í´ë¹ Annotation ì´ íìë í´ëì¤ê° ì¤ì²©ë ë¹ ì ì ì¸ í ì¤í¸ í´ëì¤ìì ëíë¸ë¤. @Tag : íí°ë§ í ì¤í¸ë¥¼ ìí í그를 ì ì¸ @ExtendWith : ì¬ì©ì ì ì íì¥ëª ì ë±ë¡íë ë° ì¬ì© @BeforeEach : í ì¤í¸ ë©ìë ì´ì ì ì¤íëë ë©ìë(@Before) @AfterEach : í ì¤í¸ ë©ìë íì ì¤íëë ë©ìë(@After) @BeforeAll : íì¬ í´ëì¤ì 모ë ë©ìë ì´ì ì ì¤íëë ë©ìë(@BeforeClass) @AfterAll : íì¬ í´ëì¤ì 모ë ë©ìë ì´íì ì¤íëë ë©ìë(@AfterClass) @Disable : í ì¤í¸ í´ëì¤ ëë ë©ìë를 í ì¤í¸ìì ì ì¸ ëë ë¹íì±í íëë° ì¬ì©(@Ignore) JUnit Vintage : TestEngine ìì JUnit 3 & JUnit 4 ë²ì ì í ì¤í¸ ì½ë를 ì¤íí´ì¤ë¤. JUnit 5 Features : JUnit Jupiter ìì í ì¤í¸ ì½ë를 ìì± JUnit 4 ë²ì ê³¼ Jupiter íµí© í ì¤í¸ ì¤í JUnit Jupiter 를 ìí íì¥ ëª¨ë¸ ì ê³µ JUnit Platform ì¤í API, JUnit Platform Test Kit ì ê³µ JUnit 5 ì ëëµì ì¸ ë³ê²½ì ê³¼ í¹ì§ì ììë³´ìë¤. ì´ì Maven ëë Gradle ìì Dependency 를 ì¶ê°í´ë³´ì. Maven <dependencies> <!-- https://mvnrepository.com/artifact/org.junit. jupiter/junit-jupiter --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.6.2</version> <scope>test</scope> </dependency> </dependencies> Gradle // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter testCompile group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.6.2' JUnit 5 ìì import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.logging.Logger; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class DemoApplicationTests { private static final Logger log = Logger.getLogger(DemoApplicationTests.class.getName()); // BeforeAll ì íì¬ í ì¤í¸ í´ëì¤ ëë ì ì²´ í ì¤í¸ ë©ìë ì¤íì´ì ì ì¤íëì¼ íë¯ë¡ static ì¼ë¡ ìì±íë¤. @BeforeAll static void setup() { log.info(\"@BeforeAll - executes once before all test methods in this class\"); } @BeforeEach void init() { log.info(\"@BeforeEach - executes before each test method in this class\"); } // assertEquals ì¬ì© @Test @DisplayName(\"1 + 1 = 2\") void addTwoNumbers ()\t{ Calculator calculator = new Calculator(); assertEquals(2, calculator.add(1, 1), \"1 + 1 should equal 2\"); } // í ì¤í¸ í목 ì´ë¦ ì¤ì @DisplayName(\"Single test successful\") @Test void testSingleSuccessTest() { log.info(\"Success\"); } // JUnit4 ë²ì ì @Ignore ì ëì¼í @Disabled ì¬ì© @Test @Disabled(\"Not implemented yet\") void testShowSomething() { } // Java 8 ì´ìì ë²ì ììë Lambda ìì ì¬ì©í ì ìë¤. @Test void lambdaExpressions() { List<Integer> numbers = Arrays.asList(1, 2, 3); assertTrue(numbers .stream() .mapToInt(i -> i) .sum() > 5, () -> \"Sum should be greater than 5\"); } // ì Lambda ìì ì¬ì©íë©´ lazy ë¡ë©ì ì´ì©íì¬ ì¢ì í¼í¬ë¨¼ì¤ë¥¼ ë³´ì¼ ì ìë¤. // íì§ë§ ìëì²ë¼ ì¬ì©í ê²½ì° ë³´ë¤ ë ì íí ìì¹ì ì¤ë¥ë¥¼ íì¸í ì ìë¤. @Test void groupAssertions() { int[] numbers = {0, 1, 2, 3, 4}; assertAll(\"numbers\", () -> assertEquals(numbers[0], 1), () -> assertEquals(numbers[3], 3), () -> assertEquals(numbers[4], 1) ); } @Test void trueAssumption() { // True ê²½ì°ë¥¼ ê°ì íë í ì¤í¸ 문 assumeTrue(5 > 1); assertEquals(5 + 2, 7); } @Test void falseAssumption() { // False ì¼ ê²½ì°ë¥¼ ê°ì íë í ì¤í¸ 문 assumeFalse(5 < 1); assertEquals(5 + 2, 7); } @Test void assumptionThat() { String someString = \"Just a string\"; // assumingThat ì ê²½ì° ì²«ë²ì§¸ ì¸ìì¸ ê°ì ë¬¸ì´ ë§ì§ ìì ê²½ì° ì¤íëì§ ìëë¤. // ë§ì½ 첫ë²ì§¸ ì¸ìì ê°ì ë¬¸ì´ ë§ë¤ê³ íë©´ ë¤ì í ì¤í¸ ë¬¸ì´ ì¤íëë¤. assumingThat(someString.equals(\"Just a string\"), () -> assertEquals(2 + 1, 4)); } // ìì¸ì ëí í ì¤í¸ @Test void shouldThrowException() { // ìì¸ì ì¸ë¶ì¬íì ëí í ì¤í¸ Throwable exception = assertThrows(UnsupportedOperationException.class, () -> { throw new UnsupportedOperationException(\"Not supported\"); }); assertEquals(exception.getMessage(), \"Not supported\"); } @Test void assertThrowsException() { String str = null; // ìì¸ì ì íì ëí í ì¤í¸ assertThrows(IllegalArgumentException.class, () -> { Integer.valueOf(str); }); } // ìë ë ìì ë Test suite ìì ì¸ë°, @SelectPackage ê°ì ê²½ì°ìë í ì¤í¸ í¨í¤ì§ë¥¼ í ì¤í¸íë ê²ì´ê³ // @SelectClasses ì ê²½ì°ìë ì íë í´ëì¤ë§ í ì¤í¸íë ê²ì´ë¤. // @RunWith(JUnitPlatform.class) // @SelectPackages(\"com.baeldung\") // public class AllUnitTest {} // @RunWith(JUnitPlatform.class) // @SelectClasses({AssertionTest.class, AssumptionTest.class, ExceptionTest.class}) // public class AllUnitTest {} // ìë ìì ë ëì ì¸ í ì¤í¸ì ê´í ìì ì´ë¤. // ì¼ë°ì ì¼ë¡ ììì ë³´ìë í ì¤í¸ì ê²½ì° ì í´ì§ ì ì ì¸ ê°ì ë¹êµíë í ì¤í¸ë¤ì´ìë¤. // íì§ë§ ìëë ê³ ì ë ê°ì´ ìë ë°°ì´ì ì¡°ííë©° ê°ê°ì ê°ì í ì¤í¸íë¤. private List<String> in = new ArrayList<>(Arrays.asList(\"A\", \"B\", \"C\")); private List<String> out = new ArrayList<>(Arrays.asList(\"aaa\", \"bbb\", \"ccc\")); @TestFactory public Stream<DynamicTest> translateDynamicTestsFromStream() { return in.stream().map(word ->
353 DynamicTest.dynamicTest(\"Test translate \" + word, () -> { int id = in.indexOf(word); assertEquals(out.get(id), translate(word)); })); } private String translate(String word) { if (\"A\".equalsIgnoreCase(word)) { return \"aaa\"; } else if (\"B\".equalsIgnoreCase(word)) { return \"bbb\"; } else if (\"C\".equalsIgnoreCase(word)) { return \"ccc\"; } return \"Error\"; } @AfterEach void tearDown() { log.info(\"@AfterEach - executed after each test method.\"); } @AfterAll static void done() { log.info(\"@AfterAll - executed after all test methods.\"); } } ìì ì½ë를 ë³´ë©´ @Test ë¡ íìë ë©ìë를 í ì¤í¸íë¤. í ì¤í¸ í목ì ì´ë¦ì â1 + 1 = 2â ë¡ ì ì¸íìê³ Equals 를 ì´ì©í´ add(1, 1) ì ê²°ê³¼ê° 2 ê° ë§ëì§ íì¸íë¤. í´ë¹ ìì ë JUnit5 ê³µì ì¬ì´í¸ì ìë¤. ê°ë¨íê² JUnit 5ì í¹ì§ê³¼ ìì 를 ììë³´ìë¤. ë¤ììë Jupiter ì ì¬ë¬ Annotation ê³¼ í ì¤í¸ë¥¼ ì¢ë ì ì°íê² ì¬ì©íëë¡ ëìì¤ Hamcrest 를 ììë³´ì. ","categories": ["posts"], 354 "tags": ["tdd","java","junit"], 355 "url": "https://subji.github.io/posts/2020/06/23/tdd-java-2", 356 "teaser":null},{ 357 "title": "TDD(Test Driven Development) - Hamcrest (3)", 358 "excerpt":"ìë ë´ì©ì âí ì¤í¸ 주ë ê°ë°: TDD ì¤ì²ë²ê³¼ ë구â(https://repo.yona.io/doortts/blog/issue/1)를 ë³´ê³ ì£¼ì ë´ì©ì ê°ì¸ì ì¼ë¡ ì 리íì¬ ìì±íìµëë¤. Hamcrest ? ë¨ìí ê° ë¹êµ, ì°¸/ê±°ì§ ë¹êµ ë±ì ë ë ì¢ ë ì ì°íê² í ì¤í¸ë¥¼ í ì ìê² ì§ìí´ì£¼ë Matcher ë¼ì´ë¸ë¬ë¦¬ì´ë¤. Matcher ë¼ì´ë¸ë¬ë¦¬ë âíí°ë ê²ì ë±ì ìí´ ê°ì ë¹êµí ë ì¢ ë í¸ë¦¬íê² ì¬ì©í ì ìê² ëì주ë ë¼ì´ë¸ë¬ë¦¬.â ë¼ê³ íë¤. ìì ìë JUnit ë¼ì´ë¸ë¬ë¦¬ì ê°ì´ ì°ë©´ ì¢ì ì ëì ì¸ë¶ ë¼ì´ë¸ë¬ë¦¬ìì§ë§ í¸ë¦¬í¨ê³¼ ë§ì ì¬ëë¤ì ì°ìì¼ë¡ ì¸í´ JUnit ë¼ì´ë¸ë¬ë¦¬ì ìì í¬í¨ëìë¤. íì§ë§ JUnit 5 ì ììë Hamcrest ìì ì¬ì©ëë assertThat ë¬¸ì´ ë¹ ì§ê² ëìë¤. JUnit 5 ê³µì문ìì ë³´ë©´ ë¤ìê³¼ ê°ì´ ëììë¤. For example, the combination of matchers and a fluent API can be used to make assertions more descriptive and readable. However, JUnit Jupiterâs org.junit.jupiter.api.Assertions class does not provide an assertThat() method like the one found in JUnit 4âs org.junit.Assert class which accepts a Hamcrest Matcher. Instead, developers are encouraged to use the built-in support for matchers provided by third-party assertion libraries. ìì¸í ì´ì ë ìëììì´ì ì ëª¨ë¥´ê² ë¤ë§ ê²°ê³¼ì ì¼ë¡ assertThat ê³¼ ê°ì 구문ì ì¬ì©íê³ ì¶ì¼ë©´ ì¨ëíí° ë¼ì´ë¸ë¬ë¦¬ë¥¼ ì¬ì©íë¼ë ë§ì´ë¤. ì기를 ì ì´ì´ ëê°ìë©´ ì Hamcrest ê°ì Matcher ë¼ì´ë¸ë¬ë¦¬ê° ì¢ ë ì ì°íê³ ë¬¸ë²ì ì¼ë¡ ì¹ìíëê³ ê¶ê¸í ì ìë¤. ê·¸ë¼ ì ì Hamcrest ìì ì JUnit ìì 를 ë¹êµí´ë³´ì // JUnit assertEquals(true, true); // Hamcrest (Matcher) assertThat(true, is(true)); ë¸ë ëê°ë¿ì¸ ë©ìëì§ë§ ì ë³´ë©´ ì°¨ì´ë¥¼ ëë ì ìë¤. âassertEqualsâ ì ê°ì ê²½ì° íë¡ê·¸ëë° ì ì¼ë¡ë ì¹ìíë¤. ë ëìì§ ìë¤. íì§ë§ ìë âassertThat, isâ ë©ìë를 ë³´ë©´ ëê° ì± ì ì½ë ë¯í ëë? ì´ë¤. ì¦, íê¸ì´ ìë ìì´ë¼ì ì¡°ê¸ ë°ìë¤ì´ê¸° ì´ë µê² ì§ë§ 문ë²ì ì¼ë¡ë ìëìª½ì´ ë ëìë³´ì¸ë¤. ì´ë¬í ì´ì ìì Hamcrest ì ê°ì Matcher ë¼ì´ë¸ë¬ë¦¬ë¥¼ ì¬ì©íë¤ê³ íë¤. Hamcrest ì¤ì Hamcrest 를 ì¬ì©í기 ìí ì¬ì ì¡°ê±´ì´ ëªê°ì§ ìë¤. 첫째ë Hamcrest ë¼ì´ë¸ë¬ë¦¬ë¥¼ ì¶ê°íë ê²ì´ê³ , ë째ë ì¶ê°ë ë¼ì´ë¸ë¬ë¦¬ë¥¼ static ì¼ë¡ import íë ê²ì´ë¤. ë¤ë¥¸ IDE ë í´ë¤ì ì ëª¨ë¥´ê² ì§ë§ ë´ê° íì¬ ì¬ì©íë vscode ììë static ë©ìëì ëí´ìë ìëì¼ë¡ import ê° ì ìë ëê° ìë¤. Maven De
358npendency ì¶ê° <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> <version>2.1</version> <scope>test</scope> </dependency> Static method import // Matcher method import static org.hamcrest.Matchers.is; import static org.hamcrest.MatcherAssert.assertThat; Matcher ë©ìëë is() ì¸ìë ì¬ë¬ê°ê° ìì¼ë íìì ë°ë¼ì ì¶ê°í´ì£¼ëë¡ íì. Hamcrest ì¬ì© Hamcrest ì assertThat ì ì¸ê°ì§ì íë¼ë¯¸í°ë¥¼ ë°ëë¤. Assential ë´ì©ì´ ì¤í¨íì ë ì¶ë ¥ëë ë©ì¸ì§ ì¤ì ë¡ ì¶ë ¥ëë ê° ì¶ë ¥ ìì ê° @Test public void testTemp() { \tassertThat(\"Date test: \", \"2020-06-25\", is(\"2020-06-24\")); } ì ì½ëì ì¤í ê²°ê³¼ë ë¤ìê³¼ ê°ë¤. ë³´ë©´ âExpected: is â2020-06-24â ë¼ê³ 2020-06-24를 ììíë¤. íì§ë§ ì¤ì ê°ì âbut: was 2020-06-25â ë¼ê³ ì¤í¨íë¤ê³ íë¤. is, but, was ë±ì 문구ë ì¢ ë ì¬ëìê² ì¹ìíê² ëê»´ì§ë¤. ì´ê²ì´ Hamcrest 를 ì°ë ì´ì ì´ë¤. ","categories": ["posts"], 359 "tags": ["tdd","java","junit","hamcrest"], 360 "url": "https://subji.github.io/posts/2020/06/29/tdd-java-3", 361 "teaser":null},{ 362 "title": "TDD(Test Driven Development) - Mock (4)", 363 "excerpt":"ìë ë´ì©ì âí ì¤í¸ 주ë ê°ë°: TDD ì¤ì²ë²ê³¼ ë구â(https://repo.yona.io/doortts/blog/issue/1)를 ë³´ê³ ì£¼ì ë´ì©ì ê°ì¸ì ì¼ë¡ ì 리íì¬ ìì±íìµëë¤. Mock ? ìë차를 ë§ë¤ê¸°ì ì ë무를 ì¬ì©í´ì ì¤ì ìëì°¨ì 1:1 ë¹ì¨ë¡ ë§ë¤ëê° ìë¤. ì¤ì ìë차를 ì ìíë ¤ë©´ ë¹ì¼ ì¬ë£ê° ë¤ì´ê°ê¸°ë íê³ ë ê·¸ë° ìí©ì´ ë°ìíë©´ ë¹ì©ì ì¸ ì¸¡ë©´ììë ë§ì ìê³ ë¥¼ ë¤ì´ê¸° ë문ì´ë¤. ê·¸ëì ì´ë¬í ìê³ ë¥¼ ëê³ ì ê°ì´ ë¹êµì ì¼ ë무를 ì¬ì©í´ì ì¤ì ìëì°¨ì ê°ì ë¹ì¨ë¡ ë§ë¤ì´ í ì¤í¸ë¥¼ íë¤. ì´ë ëë¬´ë¡ ë§ë¤ì´ì§ ìëì°¨ 모íì Mock ì´ë¼ê³ íë¤. ì½ê² ë§í´ ê°ì§ ê°ì²´ì´ë¤. Mock ê°ì²´ë ì¤ì ê°ì²´ë¥¼ ë§ëëë° ìê°ì´ ì¤ë 걸리거ë ì¬ë¬ ê°ì²´ê° ìì¡´ì±ì¼ë¡ ë¬¶ì¬ ìì´ êµ¬íì´ íë¤ë ì¬ì©íë¤. ê·¸ë ë¤ë©´ Mock ê°ì²´ê° íìí ëë ì¸ì ì¼ê¹? íê²½ì¤ì ì´ íìí ë ì¼ë°ì ì¼ë¡ DB, WAS ë±ì ì¤ì ì´ íìí ë ì¬ì©íë¤. í¹ì 모ëì´ âìì§â ì¡´ì¬íì§ ìì ë ì를ë¤ì´ A ë¼ë ë©ìëê° ìì§ êµ¬íì´ ìë ìíìì ì´ë¤ ê°ì´ ì¶ë ¥ë ì§ ììì´ ëë©´ Mock ê°ì²´ë¥¼ ë§ë¤ê³ ê²°ê³¼ê°ì¼ë¡ ììì ì¸ ìì ê°ë§ ì¶ê°í´ì ì¬ì©í ì ìë¤. ë¤ë¥¸ í ê°ì íì ë° ì ì± ì´ íìí ë í ê° ì´ì©íë ë¤í¸ìí¬ê° ë¤ë¥¼ ë ì ê·¼ì´ ë¶ê°ë¥í 모ëì ê²½ì° Mock ê°ì²´ë¥¼ ì¬ì©í´ì í ì¤í¸ë¥¼ í ì ìë¤. ì´ì¸ìë í ì¤í¸ìê°ì´ ë무 ì¤ë 걸리거ë, í¹ì ìê°ì´ë ê²½ì°ì ìì¡´ì ì¼ ê²½ì°ìë ì¬ì©íë¤. Mock ê°ì²´ë ë©ì¤ìë¡ì¤ ì ë¼ëë¼ë ì¬ëì´ í ì¤í¸ ëë¸ ì´ë¼ë ì©ì´ë¥¼ ì¬ì©íì¬ í ì¤í¸ ëë¸ì íì ë¶ë¥ë¡ ëëìë¤. Test Double ê·¸ë ë¤ë©´ í ì¤í¸ ëë¸ì íì ë¶ë¥ë¤ì ììë³´ëë¡ íì. (1) Dummy Object ë미 ê°ì²´ë ë§ ê·¸ëë¡ ëª¨ì¡°í, ë¨ìí ê»ë°ê¸°ì í´ë¹íë¤. ì¸í°íì´ì¤ë¥¼ ì¤ë¡ì§ ì¸ì¤í´ì¤í ìí¤ê¸° ìí ì´ì ë¡ë§ ìì±ëë ê°ì²´ì´ë¤. ë미 ê°ì²´ë 기ë¥ì ì¸ ë©´ì 구ííì§ ìê³ ê°ì²´ì ì¸ì¤í´ì¤ íì¸ìë§ ì¬ì©ëë¯ë¡ í ì¤í¸ ì½ë ìì± ìì ì¶ê°ì ì¸ ìì¸ì²ë¦¬ê° íìíë¤. íì§ë§ ì´ë ê² ë¨ìí ì¸ì¤í´ì¤ë§ ê°ì§ê³ ë 기ë¥ì ì¸ í ì¤í¸ë¥¼ í ì ìë¤. ê·¸ëì ë미 ê°ì²´ìì ì¡°ê¸ ë ë°ì ëì´ ë©ìëì 기ë¥ì ì¶ê°í ê°ì²´ë¥¼ Test Stub ì´ë¼ê³ íë¤. class Dummy implements User { public void getDummy() {} } class DummyTest { @Test public void getDummy_ë미ê°ì²´() { // User ì¸í°íì´ì¤ì ê°ì§ 구í ê°ì²´ ì¸ Dummy ê°ì²´ë¥¼ ì¬ì© User user = new Dummy(); } } (2) Test Stub ë미 ê°ì²´ê° ë§ì¹ ì¤ì ì²ë¼ ëìíë ê²ì²ë¼ ë³´ì´ê² ë§ë ê°ì²´ì´ë¤. ë미 ê°ì²´ìì ì°¨ì´ì ì ë미 ê°ì²´ë ë¨ì ì¸ì¤í´ì¤ë§ ë°ííê³ ì¤í ê°ì²´ë ì¸ì¤í´ì¤íë ê°ì²´ê° í¹ì ìí ê°ì ê°ì§ë ê²ì´ë¤. ë¤ìì ë¡ì§ì´ ì¤ì 구íë ê²ì²ë¼ ë³´ì´ë Fake Object ì´ë¤. class Stub implements User { public void getUser() { return \"stub\"; } } class StubTest { @Test public void getUser_ì¤í ê°ì²´() { User user = new Stub(); // ììì ê°ì ë£ì´ ì¤ì ì²ë¼ ëìíê² ë§ë stub ë©ìë í¸ì¶ assertThat(user.getUser(), is(\"stub\")); } } (3) Fake Object Stub ê°ì²´ì ì¡°ê¸ì ë¹ì·íì§ë§ ì¸ì¤í´ì¤í ë ê°ì²´ê° ì¬ë¬ê°ì¸ ê²½ì° ëë ë³µì¡í 구íì´ ë¤ì´ê° ìë íí stub ì ê²½ì°ìë íëì½ë©ë ê°ì´ ë¤ì´ê° ìì´ì íëì ì¸ì¤í´ì¤ë§ì ëííë¤. ì¦ íëì ì¸ì¤í´ì¤í ë ê°ì²´ë¡ë§ ìí ì ìííë¤. íì§ë§ ì´ë¤ ìí©ì´ë ì¡°ê±´ì ìí´ ê°ì´ ë³íê±°ë í ê²½ì°ìë ì¬ë¬ê°ì ì¸ì¤í´ì¤ì ëìí ì ìì´ì¼ íë¤. ì´ë ì¬ì©ëëê² Fake Object ì´ë¤. ì를ë¤ì´ DB 를 ì¡°ííë ë¡ì§ì ê²½ì° ì¤ì DB 를 ì¡°ííë ê²ì´ ìë DB 를 ì¡°ííë ê²ì²ë¼ List ëë Map ë±ì ê°ì²´ë¥¼ ì¬ì©íë ê²ì´ë¤. í¹ì§ì¼ë¡ë ì¸ë¶ ê°ì²´ì ìì¡´ì±ì ë¨ìí íê±°ë ë³µì¡í ë¡ì§ì ë¨ìí íê²ì´ë¼ê³ ë³¼ ì ìë¤. class Fake implements User { public void getUser (String userName) { List<String> userList = new ArrayList<String>(); userList.add(\"a\"); userList.add(\"b\"); userList.add(\"c\"); String targetUser = null; for (String user : userList) { if (user.equals(userName)) { targetUser = user; } } return targetUser; } } class FakeTest { @Test public void getUser_íì´í¬ê°ì²´() { User user = new Fake(); assertThat(user.getUser(\"a\"), is(\"b\")); } } (4) Test Spy í¹ì ê°ì²´ ëë ë©ìëì ì ì í¸ì¶ ì¬ë¶ë¥¼ íì¸ í ì¤í¸ ëë¸ íì ë¶ë¥ ì ì²´ì ì¬ì©ì´ ê°ë¥íë¤. ì를ë¤ì´ Fake ê°ì²´ìì ë§ë ë©ìëê° ëªë² ë¶ë ¸ë를 íì¸í기 ìí´ Fake ê°ì²´ì ë©ìë í¸ì¶ íì를 ë°ííë ì¤íì´ ë©ìë를 ë§ë¤ê³¤ íë¤. ì¬ê¸°ì ì¤ì ê°ì²´ìë ìë ì¤íì´ ë©ìë를 ë§ë¤ì´ë ëëíë ìë¬¸ì´ ëëë° ì´ì§í¼ ê°ì§ ê°ì²´ì´ê¸° ë문ì ìê´ ìë¤ê³ íë¤. í ì¤í¸ ì¤íì´ë êµ³ì´ ì ì¨ì ë§ë¤ íìë ìì¼ë©° ìì¦ ìë Mock ë¼ì´ë¸ë¬ë¦¬ììë ì ë§í´ìë ì§ìì íë¤ê³ íë¤. (5) Mock Object ë¤ì´ê°ê¸° ìì âìí ê¸°ë° í ì¤í¸â ì âíì ê¸°ë° í ì¤í¸â ì ëí´ ììë³´ëë¡ íì. ìí ê¸°ë° í ì¤í¸ì ê²½ì° ë§ ê·¸ëë¡ ê°ì²´ì ê·¸ ë¹ìì ìí를 ê°ì§ê³ í ì¤í¸íë ê²ì´ë¤. ë§ì½ Setter ë¡ ìí를 ë³ê²½íì¼ë©´ Getter ë¡ ë³ê²½ë ìí를 í ì¤í¸í´ì¼íë¤. ë°ë©´ íì ê¸°ë° í ì¤í¸ì ê²½ì° A ë©ìëìì B ë©ìë를 í¸ì¶íë íìì ì§ì¤í´ì í ì¤í¸íë¤. ì¼ë°ì ì¸ í ì¤í¸ ëë¸ì ìí를 기ë°ì¼ë¡ í ì¤í¸ íì§ë§ Mock ì ê²½ì° íì를 기ë°ì¼ë¡ í ì¤í¸ íë¤. ì´ë ê² Mock ê°ì²´ì ëí ê°ëµí ë´ì©ê³¼ Test Double ì ëí´ì ììë³´ìë¤. ë¤ììë Mokito 를 íì©í ìì 를 ììë³´ëë¡ íì. ","categories": ["posts"], 364 "tags": ["tdd","java","junit"], 365 "url": "https://subji.github.io/posts/2020/06/29/tdd-java-4", 366 "teaser":null},{ 367 "title": "TDD(Test Driven Development) JUnit 5 - Mockito (5)", 368 "excerpt":"ìë ë´ì©ì âí ì¤í¸ 주ë ê°ë°: TDD ì¤ì²ë²ê³¼ ë구â(https://repo.yona.io/doortts/blog/issue/1)를 ë³´ê³ ì£¼ì ë´ì©ì ê°ì¸ì ì¼ë¡ ì 리íì¬ ìì±íìµëë¤. Mockito ? í¹ì§ ë° ë기 ìì ë³´ë©´ JMock, EasyMock ë³´ë¤ ê°ë³ê³ ì§ê´ì ì¸ ë¼ì´ë¸ë¬ë¦¬ë¼ê³ ì¤ëª íê³ ìë¤. ëí 기존ì ìì-ì¤í-ê²ì¦ ì´ ìë ë°©ìì¼ë¡ íì ë³´ë¤ ìíì ì¡°ê¸ë ì§ì¤íë¤ê³ ëì´ìë¤. 문ë²ì EasyMock ê³¼ ë¹ì·íë©° ë°ë¼ì EasyMock ììì 리í©í ë§ì í
368° ì´ë ¤ìì´ ìë¤. í ì¤í¸ ì¤íì´ ëí 구íëì´ ìë¤. ìë ëªê°ì§ ì¶ê°ì ì¸ í¹ì§ì´ ìë¤. ì¸í°íì´ì¤ ë§í¼ ê²¬ê³ í Mock ê°ì²´ ê°ë¨í ì´ë ¸í ì´ì ì§ì ex) @Mock ê²ì¦ ìë¬ì ëí ê¹ëí ì¶ë ¥ ì§ì ì ì°í ê²ì¦ ê°ë¥ ex) ìíë ê²ë§ ê²ì¦íê±°ë, í¹ì ê²ì¦ì ì ì¸ ìí¤ë ê² íì ë° ìµì íë² ì¤íì ëí ê²ì¦ ì ê³µ ì¸ì를 ì´ì©í stub ê³¼ ì ì°í ê²ì¦ ì ê³µ ex) 리íë ì ì ì¬ì©í anyObject(), anyString(), refEq() ë± ì¬ì©ì ì ì Matcher í¨ì ëë 기존ì Hamcrest Matcher ì¬ì© ê°ë¥ Mockito ì¤ì¹ Maven <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.10.19</version> <scope>test</scope> </dependency> Gradle testCompile group: 'org.mockito', name: 'mockito-all', version: '1.10.19' Mockito ìì Mockito ë ììì ë§íë¯ì´ ìì-ì¤í-ê²ì¦ì ê°ë¨í ì 차를 ê°ì§ë¤. ê·¸ë¬ë©´ ì´ë° ê³¼ì ì ì´ë»ê² ìì±í´ì¼ í ê¹? 첫ë²ì§¸ë¡ Mock ê°ì²´ë¥¼ ìì±íë¤. List mockedList = mock(List.class); ê·¸ ë¤ìì¼ë¡ë stub ì ì ìíë¤. ëë¶ë¶ when-then ì¼ë¡ ì´ë£¨ì´ì ¸ìë¤. when(mockedList.get(0)).thenReturn(\"item\"); ë§ì§ë§ì¼ë¡ ì¤íì ëí ê²ì¦ì íë¤. ê²ì¦ìë í¸ì¶íì를 ëíë´ë í¨ì(times(), atLeastOnce(), ë±) ê° ìë¤ ëí ëë²ì¨° ì¤ì ê²ì¦ì²ë¼ add í¨ìì ì¸ìë¡ Matcher í¨ì를 ëê²¨ì¤ ì ìë¤. verify(mockedList, times(1)).add(\"item\"); verify(mockedList).add(any()) ê·¸ì¸ìë Mock ê°ì²´ì í¸ì¶ ìì를 ì§ì íë ë°©ë², void ë©ìëì ê²ì¦ë°©ë² ë±ì´ ì°¸ê³ íë íì´ì§ì ëììë¤. Mockito ìì - spy ì¤ì ê°ì²´ë¥¼ Mock ê°ì²´ë¡ ì¬ì©í ì ìê² ëì주ë 기ë¥ì´ë¤. final ë©ìëë spy í¨ìë¡ ë§ë¤ ì ìë¤. ë§ì¹ë©° Mockito ë ìì ëì¨ ê¸°ë¥ ì¸ìë BDD ì§ì, SMART_NULL ì§ìë±ì ë¤ìí 기ë¥ë¤ì´ ìë¤. 차근차근 기본ì ì¸ ê²ë¶í° ì¶ê°íë©° ì¬ë¬ 기ë¥ì ì¬ì©í´ë´ì¼ ê² ë¤. íì§ë§ 무ìë³´ë¤ ì¤ìíê±´ í ì¤í¸ë ìµëí ì격íê³ ì¸ì¸íì§ë§ ê°ë¨íê² ìì±í´ì¼ê² ë¤. ","categories": ["posts"], 369 "tags": ["tdd","java","junit","mockito"], 370 "url": "https://subji.github.io/posts/2020/06/30/tdd-java-5", 371 "teaser":null},{ 372 "title": "ìì¸ë±ì¤", 373 "excerpt":"ìì¸ë±ì¤ íëì row ì 긴 í ì¤í¸ê° ìê³ ì´ë° row ë¤ì´ ìë§, ììë§ê°ë¡ ì´ë£¨ì´ì§ í ì´ë¸ìì âLIKE %â¦%â ê°ì 구문ì ì´ì©í´ì ê²ìì íë¤ê³ í´ë³´ì. ê²°ê³¼ë ì´ë»ê² ë ê¹? LIKE ì ì ì¸ë±ì¤ë¥¼ íì§ ìëë¤ê³ íë¤. ê·¸ë¬ë©´ ê²ì ìë를 ì¬ë¦¬ê³ ì¶ì ëë ì´ë»ê² í´ì¼í ê¹? ë§ì ë°©ë²ì´ ìê² ì§ë§ ì¬ê¸°ì ìê°í ë°©ë²ì ìì¸ë±ì¤ë¼ê³ ë¶ë¦¬ë ë°©ë²ì´ë¤. ìì¸ë±ì¤ë¼ë ë§ì´ ì°¸ ì²ì ë£ê¸°ì ì´ë ¤ì ë³´ì´ëë° ê°ë ì ê°ë¨íë¤. ì를ë¤ì´ âìë ëë ë구ë구ì¼â ë¼ë 문ì¥ì´ ìë¤ê³ íì. ë³´íµì í ì´ë¸ì âìë ëë ë구ë구ì¼â ë¼ë ë´ì©ì´ ë´ê¸´ ì»¬ë¼ ìì²´ì ì¸ë±ì¤ë¥¼ ì¤ì íë¤. íì§ë§ ìì¸ë±ì¤ë í´ë¹ 문ì¥ì ë¶í´í´ì ê°ê° ë¶í´ë ë¨ì´ ëë 문ìë¤ì ì¸ë±ì¤ë¥¼ ì¤ì íë ê²ì´ë¤. ììì ì를ë ë¬¸ì¥ âìë ëë ë구ë구ì¼â ì ê²½ì° 2ê° ë¨ìë¡ ë¬¸ì를 ì른ë¤ê³ íë©´ ìì¸ë±ì¤ í ì´ë¸ì ë¤ìê³¼ ê°ë¤. CREATE TABLE T_INVERSE_INDEX ( original_seq NOT NULL, # ìë 문ì¥ì ìíì¤ ë° ì¸ë±ì¤ ì ë³´ separated_word NOT NULL # ë¶ë¦¬ë 문ì ) ìì¸ë±ì¤ í ì´ë¸ìì ì¤ìíê±´ separated_word column ì´ë¤. ë¶ë¦¬ë 문ìì ì¸ë±ì¤ë¥¼ ê±°ë ê²ì´ë¤.! ì´ë ê² íë©´ âLIKE â%ë구%ââ ë¼ê³ ê²ìì í ë ì본 í ì´ë¸ì´ ìë ìì¸ë±ì¤ í ì´ë¸ìì ì¸ë±ì±ë separated_word column ì ì¡°íí´ì ìë를 ì¬ë¦´ì ìë¤. ì¥/ë¨ì ì¥ì ì ëë ·íê² ì¦ê°íë ìëì¼ ê²ì´ë¤. íì§ë§ ë¨ì ë ìë¤. ìì¸ë±ì±ì ìí í ì´ë¸ì ë°ë¡ ì¶ê°í´ì¤ì¼ íë ê²ê³¼ í ì¤í¸ê° 1000ê°ì ë¨ì
373´ë¡ ì´ë£¨ì´ì ¸ ìê³ ì´ë¥¼ 2ê°ì© ì른ë¤ê³ íë©´ ìì²ë ìì row ê° ì길ê²ì´ë©°, ê·¸ë¡ì¸í´ ìì¸ë±ì¤ í ì´ë¸ì ì©ëì ìì²ëê² ì»¤ì§ ê²ì´ë¤. ì´ì¸ìë ì¬ë¬ ì¥/ë¨ì ì´ ìì ì ìë¤. FULLTEXT INDEX ê²ììë를 ì¬ë¦¬ê³ ì íë ¤ë©´ FULLTEXT INDEX 를 ì¬ì©íë ë°©ë²ë ìë¤ê³ íë¤. ","categories": ["posts"], 374 "tags": ["db","sql"], 375 "url": "https://subji.github.io/posts/2020/07/03/query-inverse-index", 376 "teaser":null},{ 377 "title": "SQL Pivot (ì¬ë¬ê°ì Row 를 íëì Row ì Column ë¤ë¡ ë³ê²½í기)", 378 "excerpt":"Data Pivot ì¬ë¬ê°ì ë¡ì°ë¥¼ ê°ì§ ê°ì íëì ë¡ì°ì ì¬ë¬ê°ì 컬ë¼ì¼ë¡ ë°ê¾¸ë ìì ì´ë¤. 주ì´ì§ í ì´ë¸ì ë¤ìê³¼ ê°ë¤. Table 1 |ID | Name | |â|â| |1 | Jim | |2 | Bob | |3 | John | Table 2 |ID | key | value | |â|â|â| |1 | address | âX Streetâ | |1 | city | âNYâ | |1 | region | âNYâ | |1 | country | âUSAâ | |1 | postal_code | | |1 | phone | â123456789â | ì ëê°ì í ì´ë¸ì ë¤ìê³¼ ê°ì´ ë³´ì¬ì£¼ê³ ì¶ë¤. Result | ID | Name | address | city | region | country | postal_code | phone | |â|â|â|â|â|â|â|â| |1 | Jim | X Street | NY | NY | USA | NULL | 123456789 | |2 | Bob | NULL | NULL | NULL | NULL | NULL | NULL | |3 | John | NULL | NULL | NULL | NULL | NULL | NULL | ë°©ë²ì ì°¾ìë³´ë 3ê°ì§ ì ëê° ìë¤. 첫ë²ì§¸ë GROUP BY ì MAX í¨ì를 ì´ì©í ë°©ë². ë¨, ì´ë°©ë²ì ê°ì 컬ë¼ì ê°ì´ ì¬ë¬ê°ì¼ ê²½ì° ì¤ë¥ë¥¼ ë°ìí ì ìê³ ëí ë무 ë§ì ë¡ì°ë¥¼ ë³ííê³ ì í ëìë ì½ëê° ê¸¸ì´ì§ ì ìë¤. SELECT t1.id, t1.name, MAX(CASE WHEN t2.`key` = 'address' THEN t2.value END) address, MAX(CASE WHEN t2.`key` = 'city' THEN t2.value END) city, MAX(CASE WHEN t2.`key` = 'region' THEN t2.value END) region, MAX(CASE WHEN t2.`key` = 'country' THEN t2.value END) country, MAX(CASE WHEN t2.`key` = 'postal_code' THEN t2.value END) postal_code, MAX(CASE WHEN t2.`key` = 'phone' THEN t2.value END) phone FROM table1 t1 LEFT JOIN table2 t2 ON t1.id = t2.id GROUP BY t1.id, t1.name ëë²ì§¸ ë°©ë²ì 첫ë²ì§¸ ë°©ë²ì ë¨ì ì¤ íëì¸ ê°ì´ ì¬ë¬ê°ë¥¼ ê°ì§ë 컬ë¼ì´ ì¡´ì¬í ê²½ì°ë¥¼ ëë¹í ë°©ë²ì´ë¤. ë¤ë§ ì´ë°©ë²ë ìì ë¡ì°ê° ë§ìì§ìë¡ ì½ëê° ê¸¸ì´ì§ê³ ê´ë¦¬ê° íë¤ë¤ë ë¨ì ì´ ìë¤. SELECT t1.id, t1.name, t2a.value address, t2c.value city, t2r.value region, t2y.value country, t2pc.value postal_code, t2p.value phone FROM table1 t1 LEFT JOIN table2 t2a ON t1.id = t2a.id AND t2a.`key` = 'address' LEFT JOIN table2 t2c ON t1.id = t2c.id AND t2c.`key` = 'city' LEFT JOIN table2 t2r ON t1.id = t2r.id AND t2c.`key` = 'region' LEFT JOIN table2 t2y ON t1.id = t2y.id AND t2c.`key` = 'country' LEFT JOIN table2 t2pc ON t1.id = t2pc.id AND t2pc.`key` = 'postal_code' LEFT JOIN table2 t2p ON t1.id = t2p.id AND t2p.`key` = 'phone'; ë§ì§ë§ ë°©ë²ì ìì ë¨ì ë¤ì 모ë í´ê²°í ì ìë ë°©ë²ì´ë¤. ë¨, ì¬ê¸°ìë íë¡ìì ¸ ëë í¨ì를 ììì¼íë¤ë ì ì ì¡°ê±´ì´ íìíë¤. SET @sql = NULL; SELECT GROUP_CONCAT(DISTINCT CONCAT( 'max(case when t2.`key` = ''', `key`, ''' then t2.value end) AS `', `key`, '`' ) ) INTO @sql from Table2; SET @sql = CONCAT('SELECT t1.id, t1.name, ', @sql, ' from table1 t1 left join table2 t2 on t1.id = t2.id group by t1.id, t1.name;'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; ê·¸ë¬ë©´ ê²°ê³¼ë ë¤ìê³¼ ê°ì´ ëì¨ë¤. Query Result | ID | NAME | ADDRESS | CITY | REGION | COUNTRY | POSTAL_CODE | PHONE | |â-|ââ|âââ-|âââ|âââ|âââ|ââââ-|ââââ| | 1 | Jim | X Street | NY | (null) | (null) | (null) | 123456789 | | 2 | Bob | (null) | (null) | (null) | (null) | (null) | (null) | | 3 | John | (null) | (null) | (null) | (null) | (null) | (null) | ì°¸ê³ : MySQL Pivot ","categories": ["posts"], 379 "tags": ["sql"], 380 "url": "https://subji.github.io/posts/2020/07/03/sql-pivot", 381 "teaser":null},{ 382 "title": "Git Revert & Reset, ê·¸ë¦¬ê³ Working Directory ëë리기", 383 "excerpt":"Intro Git push í ë ì¤ìë¡ ì¬ë¼ê°ì§ ë§ìì¼í ê²ì´ ì¬ë¼ê°ì ëê° ìë¤. ì´ë ì못ë ë´ì©ì ì¬ë¦° í¸ì ì´ì ì¼ë¡ ëìê°ê³ ì¶ìë ì¬ì©íë ê²ì´ Reset & Revert ëª ë ¹ì´ì´ë¤. ì¬ê¸°ì ëë¶ì´ ìì§ staging ììì ì¶ê° ëì§ ìì ë´ì©ì ë¤ì ëë리ë ë°©ë²ë ììë³´ì. Checkout git checkout {branchName} ì¼ë¡ ì주 ì¬ì©ëë ëª ë ¹ì´ì´ë¤. íì§ë§ ì´ ëª ë ¹ì´ë¡ staging ììì ì¬ë¼ê°ì§ ìë ì¦, 커ë°ì íì§ë§ ë³ê²½ì¬íì´ ì¶ê°ëì§ ìììë (git add . ì¬ì© ì ) í´ë¹ ë³ê²½ì¬íì ì·¨ìíê³ ì´ì ì¼ë¡ ëìê° ì ìëë¡ ëì주기ë íë¤. ìì 를 ë³´ì 먼ì 기존 ë´ì©ì ë³ê²½íê³ git add ì´ì ì commit ì 먼ì íì. git commit -m 'Checkout ëë리기 í ì¤í¸' ì´ ìíìì ë³ê²½ì¬í ì´ì ì¼ë¡ ëìê°ê³ ì¶ì¼ë©´ ë¤ì ëª ë ¹ì´ë¥¼ ì ë ¥íì. git checkout -- {fileName}
383 ì ëª ë ¹ì´ë í¹ì íì¼ì ë³ê²½ì¬í ì´ì ì¼ë¡ ë리ë ê²ì´ê³ , íì¬ Working Directory ì 모ë íì¼ ëë í¹ì Directory ë§ ëë¦¬ê³ ì¶ë¤ë©´ ë¤ìì²ë¼ ìì±íì. # Working Directory ë´ ëª¨ë íì¼ git checkout . # í¹ì Directory ì´ë¦ git checkout {DirectoryName} ì ë°©ë²ì Staging ììì¼ë¡ ì¬ë¼ê°ì§ ìì ì¦, ë³ê²½ì¬íì´ ì»¤ë°ì ì¶ê°ëì§ ìë ê±´ì ëí´ìë§ ê°ë¥íë¤. ê·¸ë ë¤ë©´ git add 를 íê³ Staging ììì¼ë¡ ì¬ë¼ê° ë´ì©ì ì´ë»ê² ì·¨ìí ì ììê¹? ë°©ë²ì ë¤ìê³¼ ê°ë¤. ë¤ìì ë³¼ reset ëª ë ¹ì´ë¥¼ ì¬ì©í ì ìë¤. git reset {FileName} ì´ ëª ë ¹ì´ë¥¼ ì¬ì©íë©´ ë³ê²½ì¬íì ì ì§íì± staging ììì ì¬ë¼ê° 커ë°ì ì·¨ìí ì ìë¤. Reset í¹ì 커ë°ì¼ë¡ ëëìê°ë ëª ë ¹ì´. ë¨, ëëìê° ì»¤ë° ì´íì 커ë°ë¤ì íì¤í 리ìì ìì ëë¤. âhard ìµì í¹ì Commit ì¼ë¡ ëë리ë ë° ë³ê²½ë ì¬íë 모ë ìì íë¤. git reset --hard {Commit id} âsoft ìµì í¹ì Commit ì¼ë¡ ëë리ë ë° ì»¤ë° ì´ë ¥ì ìì íê³ , ë³ê²½ì¬íë¤ì ëí´ìë git add ì ì¬ë ¤ëëë¤. git reset --soft {Commit id} âmixed ìµì í¹ì Commit ì¼ë¡ ëë리ë ë° ì»¤ë° ì´ë ¥ì ìì ëê³ , ë³ê²½ì¬íì Commit ìíë¡ ë³ê²½íë¤. git reset --mixed {Commit id} Revert í¹ì 커ë°ì¼ë¡ ëëìê°ë ê²ì Reset ê³¼ ê°ë¤. íì§ë§ ëëìê° ì»¤ë° ì´íì 커ë°ë¤ì ìì ëì§ ìê³ ë¨ììë¤. git revert {Commit id} Reset ê³¼ ë¹ì·íê² í¹ì 커ë°ì¼ë¡ ëëì ê°ì§ë§ ì»¤ë° ì´ë ¥ì ìì íì§ ìê³ Revert ë¼ë ìë¡ì´ ì»¤ë° ì´ë ¥ì ë¨ê¸´ë¤. Reset ì ê²½ì° ì»¤ë°ì´ë ¥ì ìì í기 ë문ì ì¶©ëì´ ë°ìíì§ ìì§ë§ Revert ì ê²½ì° ì»¤ë° ì´ë ¥ì´ ë¨ê¸° ë문ì ì¶©ëì´ ë°ì í ì ìë¤. ì´ ë¶ë¶ì ëí´ìë ì§ì ìì ì í´ì¤ì¼íë¤. Cancel merge ê°ë° ë¸ëì¹ ëë 기í ë¸ëì¹ë¤ì master ë¸ëì¹ë¡ í©ì¹ê¸° ìí´ merge 를 ì¬ì©íë¤. ì´ë í©ì³ì§ë§ ìë ë´ì©ì´ í©ì³ì§ëê° ìëë°, ì´ê²ì ì·¨ìí ëë Revert & Reset ì ì¬ì©íë©´ ëë¤. Reset ì ì¬ì©íë ê²½ì° merge í ë ìµì 커ë°ì í¬ì¸í°ë¥¼ ì§ì íë¤. (ORIG_HEAD) ê·¸ë¬ë¯ë¡ ë³í©ì ì·¨ìí ëë í¬ì¸í°ê° ê°ë¦¬í¤ë ìµì 커ë°ì¼ë¡ ì´ëíë©´ ëë¤. ìµì 커ë°ì´ë merge 를 ìíí ë, git ìì í©ì³ì§ê¸° ì ë§ì§ë§ ì»¤ë° Id 를 ê°ë¦¬í¨ë¤. git reset -merge ORIG_HEAD ìì ì²ë¼ ëª ë ¹ì´ë¥¼ ì¬ì©íë©´ master ë¸ëì¹ì ìµì 커ë°ì¼ë¡ ì´ëíë©° merge ê° ì·¨ìëë¤. Revert 를 ì¬ì©íë ë°©ë²ì ì¡°ê¸ ë¤ë¥´ì§ë§ ê°ë¨í ìµì íëë§ ì¶ê° í´ì£¼ë©´ëë¤. ë¨, ì¬ê¸°ì 주ìí ì ì Rebase ë git ì´ë ¥ì merge ì ëí ë´ì©ì ë¨ê¸°ì§ ìì¼ë¯ë¡ ëë릴 ì ìë¤. Revert ë Reset ì²ë¼ ë§ì§ë§ 커ë°ì´ merge ì¼ëë§ ì¬ì©í ì ìë ê²ì´ ìë í¹ì Merge ì ëí´ìë ì·¨ìí ì ìë¤. ë¤ì ìµì ì ì¶ê°íì¬ ì¬ì© í ì ìë¤. git revert -mainline {1/2} {To Cancel Merge Commit ID} mainline ì merge ì·¨ì í ì´ë쪽ì¼ë¡ ê°ê²ì´ë? ì¦, master ê° 1, ìì ë¸ëì¹ê° 2ë¼ê³ í ë -mainline ë¤ì 1 ëë 2 ìµì ì¼ë¡ ëìê°ê³ ì íë 쪽ì¼ë¡ ëìê° ì ìê² í´ì¤ë¤. ì«ìë git ê·¸ëí 그림ìì ì¼ìª½ë¶í° 1ë¡ ë§¤ê¸´ë¤. End reset ì ì격 ì ì¥ì를 ê´ë¦¬í ì ìë¤. ìëíë©´ 커ë°ì´ë ¥ì´ ìì ë기 ë문ì´ë¤. ê·¸ëì ì격 ì ì¥ìì í¸ì ë기 ì ì ì¬ì©í´ì¼íë¤. revert ë 커ë°ì´ë ¥ì´ ì¡´ì¬í기 ë문ì í¸ìê° ë ìíììë ì¬ì©í ì ìë¤. ","categories": ["posts"], 384 "tags": ["git"], 385 "url": "https://subji.github.io/posts/2020/07/06/gitrevertresetandturnbackcheckout", 386 "teaser":null},{ 387 "title": "MSA ì¸ì¦ ìë¹ì¤ Keycloak ìê°", 388 "excerpt":"Keycloak ì´ë? Redhat ìì ê°ë°í ìë¹ì¤ë¥¼ ëìì¼ë¡ íë ì¸ì¦ & ê¶í ë¶ì¬ ì¤íìì¤ì´ë¤. 기ë°ì JBoss ì WildFly ë¡ êµ¬ì±ëì´ ìë¤. SSO, ìì ë¡ê·¸ì¸, LDAP, RDBMS ë±ì User Federation ë ì§ìíë¤. ê·¸ ì¸ìë adapter, SPI, REST API ë±ì ì ê³µíë¤. 기존ì OAuth 를 Spring Security ë¡ ì¬ì©íë ¤ íì§ë§ oauth provider 쪽ìì ìì ê° ë§ì´ ìì´ì ì 를 먹ìë¤. ê·¸ë¬ë¤ê° ë§ì ìì ë¤ìì ëì¨ê²ì´ Keycloak íê³ Okta. ëë¤ ë¹ì·í ì¸ì¦ & ê¶í ë¶ì¬ ì루ì ì´ì§ë§ keycloak ì´ ë ë§ì 기ë¥ì ì ê³µíë¤ë ì ìì Keycloak ì ì¬ì©í´ ë³´ë ¤ê³ íë¤. keycloak ì¤ì¹ & ì¤í Keycloak ë¤ì´ë¡ë ì ì ìí´ì ZIP or TAR.GZ íì¼ì
388 ë¤ì´ë¡ë ë°ëë¤. ZIP TAR.GZ 를 ë°ìì¼ë©´ ìì¶ì í´ì íë¤. $ unzip keycloak-10.0.2.zip $ tar -zxvf keycloak-10.0.2.tar.gz ìì¶ í´ì ê° ëëë©´ âbinâ ëë í ë¦¬ë¡ ì´ëí´ì âstandalone.shâ ëë âstandalone.batâ ì ì¤ííë¤. # For Linux/Unix $ cd bin $ sh standalone.sh # or ./standalone.sh # For Windows $ \\bin\\standalone.bat ì´ë¬ë©´ ì¤íì ëë¬ë¤. ì주 ì½ë¤. ì´ì ë¸ë¼ì°ì 를 ì´ê³ http://localhost:8080/auth ì¼ë¡ ì ìíì. ê´ë¦¬ì ì½ì íë©´ì´ ëì¨ë¤. localhost ë¡ ì¤ííì ê²½ì° ë³´ìì ì¸ ì´ì ë¡ ì¸í´ ê³ì ì´ ìë ìíë¡ ì¤íëë¤. ë§ì½ ê³ì ì´ ì¶ê°ë ìíë¡ ì¤ííê³ ì¶ì¼ë©´ bin/add-user.sh or bin/add-user.bat ì ì¤ííì. ì íë©´ìì âAdministration Console >â ì ì ííë©´ ë¡ê·¸ì¸ íë©´ì´ ëì¨ë¤. ê·¸ë¬ë©´ ì´ì ì ê°ì
í ê´ë¦¬ì ì´ë¦ê³¼ ë¹ë°ë²í¸ë¥¼ ì
ë ¥íì. ê´ë¦¬ì ì½ìì ê´ë¦¬ì ì´ë¦ê³¼ ë¹ë°ë²í¸ë¥¼ ì
ë ¥íê³ ë¡ê·¸ì¸ì íë©´ keycloak ê´ë¦¬ì íë©´ì´ ëì¨ë¤. ì¬ê¸°ê¹ì§ ì¤ë©´ keycloak ì¤íê¹ì§ë ëëê²ì´ë¤. Keycloak ê´ë ¨ ì©ì´ OIDC : OAuth ê° ê¶í ë¶ì¬ë§ ë¤ë£¨ë ê²ì´ë¼ë©´ OIDC ë OAuth 를 í¬í¨íì¬ ì¸ì¦ê³¼ ê¶íë¶ì¬ë¥¼ 모ë í¬í¨í ê²ì´ë¤. SSO ì 구íì ìí ìë¨ì¼ë¡ ì¬ì©ëë¤. Realm : ì¸ì¦, ê¶í ë¶ì¬ê° ì ì©ëë ë²ì를 ëíë´ë ë¨ìì´ë¤. SSO 를 ì ì©íë¤ê³ íìë í´ë¹ SSO ê° ì ì©ëë ë²ìë Realm ë¨ìì´ë¤. Client : ì¸ì¦, ê¶í ë¶ì¬ íì를 ëííëë¡ ë§¡ê¸¸ ì´í리ì¼ì´ì
ì ëíë´ë ë¨ìì´ë¤. ê·¸ ë¨ìë ì¹ì¬ì´í¸ í¹ì REST API 를 ì ê³µíë ìë¹ì¤ë ë ì ìë¤. íëì Realm ì nê°ì Client 를 ìì±, ê´ë¦¬í ì ìë¤. User : Client ì ì¸ì¦ì ìì²í ì¬ì©ì를 ëíë¸ë¤. íëì Realm ìë Realm ì ì¢
ìë nê°ì User 를 ìì±íê³ ê´ë¦¬í ì ìë¤. 기본ì ì¼ë¡ User ë Username, Email, FirstName, LastName ì¼ë¡ 구ì±ëì´ ìì§ë§ Custom User Attribute 를 ì¬ì©íë©´ ì¬ì©ìê° ìíë ìì±ì ì¶ê°í ì ìë¤. Role : User ìê² ë¶ì¬í ê¶í ë´ì©ì ëíë¸ë¤. ì¬ê¸°ìë Keycloak ì REST API 를 ì¬ì©í ê¶íì ë¶ì¬í ì ìê³ ì¬ì©ìê° ì ìí ê¶íì ë¶ì¬í ìë ìë¤. ì°¸ê³ : https://jsonobject.tistory.com/445 Keycloak ì¤ì MSA ìì ì¸ì¦, ê¶í ë¶ì¬ ìë¹ì¤ë¡ keycloak ì ì¬ì©í기 ìí´ìë ë¤ì ì¤ì ë¤ì í´ì¤ì¼ íë¤. Realm ìì± Client ìì± User ìì± Realm ì ì´ê¸°ìë Master Realm ë§ ì¡´ì¬íë¤. íì§ë§ ì°ë¦¬ë Master ê° ìë MSA ìì ì¬ì©í ë ë¦ì´ íìí기 ë문ì ìì±í´ì¤ë¤. keycloak ê´ë¦¬ì íë©´ìì ì¼ìª½ ìì Master 를 ëë¥´ê³ add realm ë²í¼ì ë르ì. ë ë¦ì ìì±íë©´ ë°ë¡ ìë¡ ìì±ë ë ë¦ì ê´ë¦¬ íì´ì§ë¡ ì´ëíë¤. Realm Settings ìì ì¤ì í ì ìëê±´ ì¼ë°ì¤ì , ë¡ê·¸ì¸ íë©´ ì¤ì , í¤ ìí¸í ì¤ì , ì´ë©ì¼ ìë¹ì¤ ì¤ì , ë¡ê·¸ì¸/íìê°ì
ë±ì í
ë§ ì¤ì , ìºì ì¤ì , í í° ì¤ì ë±ì´ ìë¤. ì§ê¸ ë¹ì¥ì ë°ê¿ì¼ í ë¶ë¶ì ìê³ íìì ê³µì문ì를 ì°¸ê³ íì. ì´ì client 를 ìì±íì. ë§ì°¬ê°ì§ë¡ ì¼ìª½ìì âClientsâ íì ì ííì. ê·¸ë¬ë©´ 기본ì ì¸ í´ë¼ì´ì¸í¸ 목ë¡ì´ ëì¤ëë° ê±´ë릴 íìë ìê³ ì°ì¸¡ ìì ë³´ì´ë âCreateâ ë²í¼ì ëë¬ Client 를 ìì±íì. ì¬ê¸° ìì íìì ê³µì문ì를 ì°¸ê³ íì¬ ì¤ì íë©´ëë¤. ì§ê¸ ë¹ì¥ íìí ìµì
ì 2ê°ì§ê° ìë¤. 첫ë²ì§¸ë Client-secret ì ìì±í기 ìí Access Type í. í´ë¹ ê°ì public ìì confidential ì¼ë¡ ë³ê²½íë¤. ê·¸ë¦¬ê³ ëì¤ì User ê´ë ¨ REST API 를 ì¬ì©í기 ìí´ íìí Service Accounts Enabled íì íì±í ìì¼ì¤ë¤. ë§ì§ë§ì¼ë¡ â*â ì²ë¦¬ê° ëì´ìë Valid Redirect URIs ë¶ë¶ì´ ìëë° ì¸ì¦ í 리ë¤ì´ë í¸ ë 주ì를 ì
ë ¥í´ì£¼ë©´ëë¤. ëì ê²½ì°ë gateway 주ìë¡ ì
ë ¥íë¤. ì ë¶ ìë£ë¬ì¼ë©´ save ë²í¼ì ëë¬ ì ì¥íì. client ì¤ì ì´ ëë¬ë¤. ì´ì client-secret ì´ ì ìì±ëìëì§ Clients -> Credentials íìì íì¸í´ë³´ì. ë§ì§ë§ì¼ë¡ ì¸ì¦í ë íìí ê³ì ì íë ì¶ê°í´ì£¼ì. âUsersâ íì í´ë¦íë©´ ì ì 목ë¡ì´ ëì¤ëë° ë¹ì°í íëë ìë¤. ê·¸ëì ì¤ë¥¸ìª½ì âAdd userâ 를 í´ë¦í´ì ì ì 를 ì¶ê°íì. ììì ë§íë¯ì´ 기본ì ì¼ë¡ Username, Email, First Name, Last Name ì´ ìë¤. ê·¸ë°ë° ë¹ë°ë²í¸ ì
ë ¥ì¹¸ì..? ë ì¼ë¨ Username ë§ ë³íê° ìì¼ë ê·¸ê²ë§ ì
ë ¥íê³ ì ì¥íì. ì ì¥ì´ ìë£ëë©´ íì´ì§ê° ìë¡ê³ 침ëë©° âCredentialsâ íì´ ì긴ê²ì ë³¼ ì ìë¤. íì í´ë¦íë©´ ë¹ë°ë²í¸ë¥¼ ì
ë ¥íë ê³³ì´ ëì¨ë¤. ì¬ê¸°ì ì ì ì ë¹ë°ë²í¸ë¥¼ ì¶ê°í´ì£¼ì. ì´ì Gateway 를 ìí keycloak ì¤ì ì ëì´ ë¬ë¤. Gateway ì¤ì ììì ì¤ì í keycloak ì ì´ì©í기 ìí´ Gateway ììë ì¤ì ì í´ì£¼ì. config ìë²ì ìë gateway ì¤ì íì¼ì ë¤ìê³¼ ê°ì´ ì½ë를 ì
ë ¥íì. spring: security: oauth2: client: provider: keycloak: issuer-uri: http://{keycloak address}/auth/realms/{realm name} user-name-attribute: preferred_username registration: keycloak: client-id: {client name} client-secret: {client secret} gateway: default-filters: - TokenRelay= ê·¸ë¦¬ê³ Gateway íë¡ì í¸ìì src/main/java/com/{groupId}/{artifactId}/config ë°ì í´ëì¤íì¼ì íë ì¶ê°íì. @Order(99) @Configuration @EnableWebFluxSecurity public class GatewayWebSecurityConfiguration { @Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ReactiveClientRegistrationRepository clientRegistrationRepository) { // Authenticate through configured keycloak SSO Provider http.oauth2Login();
388 // Also logout at the keycloak SSO Connect Provider http.logout(logout -> logout.logoutSuccessHandler( new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository) )); // Require authentication for all requests http.authorizeExchange().anyExchange().authenticated(); // Allow showing /home within a frame http.headers().frameOptions().mode(Mode.SAMEORIGIN); // Disable CSRF in the gateway to prevent conflicts with proxied server CSRF http.csrf().disable(); return http.build(); } } ì§ê¸ ê°ë°ì¤ì¸ MSA íë¡ì í¸ìì Gateway ë Spring Cloud Gateway 를 ì¬ì©íìë¤. 기본ì ì¼ë¡ ë¹ë기ì ì¼ë¡ ì²ë¦¬ë기 ë문ì WebFlux 를 ì¬ì©íìë¤. 모ë ìì± í ì¤ë¥ê° ë ê²½ì°ìë ë¤ì ëê°ì Dependency 를 ì¶ê°íì. spring-boot-starter-oauth2-client spring-cloud-starter-security ì½ë ìì±ì´ ìë£ëìì¼ë©´ config, discovery, gateway ìë¹ì¤ë¥¼ ì¤íí´ë³´ì. gateway ë¡ ì ìíë©´ ì´ì ì ë³´ìë keycloak ë¡ê·¸ì¸ íë©´ì´ ë³´ì¼ ê²ì´ë¤. ì¼ë¨ Gateway ê¹ì§ë keycloak ì´ ì ì ì©ë ê²ì´ë¤. Service ì¤ì ì´ì ë§ì§ë§ì¼ë¡ REST API ìë¹ì¤ë±ì ì ì©í ì¤ì ì ë³´ì. config ìë²ìì service ì ì¤ì íì¼ì ë¤ìê³¼ ê°ì´ ìì±íì. spring: security: oauth2: resourceserver: jwt: issuer-uri: http://{keycloak address}/auth/realms/{realm name} Dependency ë ë¤ìê³¼ ê°ë¤. spring-security-oauth2-jose spring-security-oauth2-resource-server spring-cloud-starter-oauth2 spring-cloud-starter-security ì´ì ìíë¦¬í° ì¤ì í´ëì¤ íì¼ì ìì±íì @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // Validate tokens through configured OpenID Provider http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter()); // Require authentication for all requests http.authorizeRequests().anyRequest().authenticated(); // Allow showing pages within a frame http.headers().frameOptions().sameOrigin(); } private JwtAuthenticationConverter jwtAuthenticationConverter() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); // Convert realm_access.roles claims to granted authorities, for use in access decisions converter.setJwtGrantedAuthoritiesConverter(new KeycloakRealmRoleConverter()); return converter; } class KeycloakRealmRoleConverter implements Converter<Jwt, Collection<GrantedAuthority>> { @Override public Collection<GrantedAuthority> convert(Jwt jwt) { final Map<String, Object> realmAccess = (Map<String, Object>) jwt.getClaims().get(\"realm_access\"); return ((List<String>) realmAccess.get(\"roles\")).stream() .map(roleName -> \"ROLE_\" + roleName) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); } } } ê·¸ë¦¬ê³ í ì¤í¸ë¥¼ ìí´ ì»¨í¸ë¡¤ë¬ì ì½ë를 íë ìì±íì. @RestController @RequestMapping(value = \"/\") public class UserController { \tprivate final UserService userService; \tpublic UserController(UserService userService)\t{ \t\tthis.userService = userService; \t} \t@PreAuthorize(value = \"#oauth2.hasScope('email')\") \t@GetMapping(value = \"users/{userId}\") \tpublic UserInfoDTO getUserInfo(@PathVariable String userId) throws Exception { \t\tUserInfoDTO userInfoDTO = new UserInfoDTO(); \t\tuserInfoDTO.setId(userId); \t\treturn userService.getUserInfo(userInfoDTO); \t} \t } ì ë´ì©ì OAuth, JWT ë¡ ì¸ì¦ì ì²ë¦¬íë¤ë ê²ì´ë©° ê¶íì email ì¼ëë§ Users API 를 ì¡°íí ì ìë¤. ì´ì service ê¹ì§ ì¤íí´ì 컨í¸ë¡¤ë¬ë¡ ìì²ì í´ë³´ì. ë¡ê·¸ì¸ì´ ìëìë¤ë©´ ë¡ê·¸ì¸ í ê°ì´ ì¶ë ¥ë ê²ì´ë¤. ë¡ê·¸ì¸ ì ì ì ë¹ë°ë²í¸ë ì´ì ì keycloak ê´ë¦¬ íì´ì§ìì ë§ë Username ê³¼ Password ì´ë¤. ë§ì¹ë©° íì¬ keycloak ì h2 ë°ì´í°ë² ì´ì¤ë¥¼ ì¬ì©í´ì íì¼ë¡ ì¬ì©ì를 ê´ë¦¬íê³ ìë¤. íì§ë§ ëë ë´ê° ë§ë¤ì´ëì ì¬ì©ì í ì´ë¸ì ì¬ì©í´ì ì¸ì¦ì íê³ ì¶ë¤. ë¤ììë ì¬ì©ì ì¸ë¶ í ì´ë¸ì keycloak ì ì°ê²°íë ê²ì ììë³´ì. ","categories": ["posts"], 389 "tags": ["msa","keycloak"], 390 "url": "https://subji.github.io/posts/2020/07/08/keycloak1", 391 "teaser":null},{ 392 "title": "MSA ì¸ì¦ ìë¹ì¤ Keycloak ì¤ì¹ ë° ì¤ì ", 393 "excerpt":"Keycloak ìë² ì구ì¬í Java ê° ì¤íë ì ìë OS Java 8 JDK ì´ì zip or gzip and tar ëª ë ¹ì´ ì¤í ê°ë¥ ìµì 512M ë¨ ìµì 1G ì´ìì ëì¤í¬ ê³µê° ê³µì ì¸ë¶ DB (MySQL, Oracle, PostgreSQL, â¦) ì ì¤íí ì ìê³ í´ë¬ì¤í°ë¥¼ 구ì±í ì ìë íê²½ ê·¸ ë°ì ë¤í¸ìí¬ ë° ë¦¬ë ì¤ì ì¶ê° ì ì¸ ì구ì¬íì ê³µì문ì를 ì°¸ê³ íì. keycloak ìë² ëë í 리 êµ¬ì± bin/ : ìë²ë¥¼ ë¶í íê±°ë ìë²ìì ë¤ë¥¸ ê´ë¦¬ ìì ì ìííë ë¤ìí ì¤í¬ë¦½í¸ê° í¬í¨ëì´ ìë¤. domain/ : ë¤ìì ì¤ëª í 모ë ì¤ ëë©ì¸ 모ë ìì keycloak ì ì¤íí ë êµ¬ì± íì¼ê³¼ Working Directory ê° í¬í¨ëë¤. modules/ : ìë²ìì ì¬ì©íë Java ë¼ì´ë¸ë¬ë¦¬ë¥¼ í¬í¨í ëë í 리. ëì¤ì ì¤ëª íê² ì§ë§ ì¸ë¶ RDBMS 를 ì°ê²°í ë íìí jdbc driver ë ì¬ê¸°ì ìì¹íê² ëë¤. standalone/ : ë 립 ì¤í í 모ë ìì keycloak ì ì¤íí ë êµ¬ì± íì¼ê³¼ Working Directory ê° í¬í¨ëë¤. standalone/deployments/ : keycloak ì ëí extension ë¤ì ì¶ê°íë ê³µê°. themes/ : ë¡ê·¸ì¸, íìê°ì ë±ì UI ì ëí íì¼ í¬í¨. UI ë Freemarker ë¡ ëì´ ìë¤. ì°¸ê³ : ê³µì 문ì - ëë í 리 구조 Keycloak ì¤í 모ë keycloak ì 기본ì ì¼ë¡ WildFly Application Server ìì 구ì±ëìê³ ìë ë´ì©ì keycloak ì ë°°í¬ì ëí 기본ì ì¸ ë´ì©ë§ ì¤ëª íë¤. ìì¸í ë´ì©ì WildFly 18 Document 를 ì°¸ê³ íì. Standalone Mode (ë 립 ì¤í í 모ë) : íëì keycloak ìë² ì¸ì¤í´ì¤ ë§ ì¤ííë ¤ë ê²½ì°ì ì ì©íë¤. keycloak ìì ì¬ì©ëë 모ë ìºìë í´ë¬ì¤í°ë¡ 구ì±ëì´ ìì§ ì기 ë문ì ë¶ë°°ëì§ ìì¼ë©° ë¡ì»¬ ì ì©ì´ë¤. íë¡ì í¸ë¥¼ ì´ììì ì¬ì©íê² ë ê²½ì° ë 립 ì¤í í 모ëë ë¨ì¼ ì¸ì¤í´ì¤ì´ê¸° ë문ì ì¤ë¥ ë°ì ì 모ë ìë¹ì¤ê° ì¸ì¦ & ê¶í ë¶ì¬ ìë¹ì¤ë¥¼ ì¬ì©í ì ìê² ëì´ ì¹ëª ì ì¸ ì¥ì ê° ë°ìíë¤. ë°ë¼ì í´ë¹ 모ëë í ì¤í¸ ìì ì¬ì©íëë¡ ê¶ì íë¤. ì¤ì íì¼ì standalone/configuration/standalone.xml ì´ë¤. Standalone Mode ì¤í ë°©ë² $ bin/standalone.sh --server-config=standalone.xml # bin\\standalone.bat Standalone Clustered Mode (ë 립í í´ë¬ì¤í° 모ë) : ë 립 ì¤í í 모ë를 í´ë¬ì¤í°ë¡ 구ì±íë 모ëì´ë¤. ì´ ëª¨ëë ê°ê°ì keycloak ì¸ì¤í´ì¤ì 구ì±ì ë³ê²½í´ì¤ì¼ íë¤ë ì ìì ìê°ì´ ë§ì´ ììëë©°, ì¤ë¥ë¥¼ ë°ìí ì ë ìë¤. ëí ë°ëì ê³µì DB ì°ê²°ì 구ì±íê³ ë¡ë ë°¸ë°ì를 구ì±í´ì¼ íë¤. ì¤ì íì¼ì standalone/configuration/standalone.ha.xml ì´ë¤. Standalone Clustered Mode ì¤í ë°©ë² $ bin/standalone.sh --server-config=standalone-ha.xml # bin\\standalone.bat Domain Clustered Mode (ëë©ì¸ í´ë¬ì¤í° 모ë) : ë 립í í´ë¬ì¤í° 모ëì ë¨ì ì í´ê²°í ì ìë ë°©ë²ì´ë¤. ì¤ììì keycloak ì¸ì¤í´ì¤ë¥¼ ê´ë¦¬íë 모ëì´ë¤. ì´ê¸° ì¤ì ì´ ë³µì¡íê³ ìê°ì´ ë§ì´ ììë ì ìì§ë§ ì¶í ê´ë¦¬ì ì¸ ì¸¡ë©´ììë ììë§í ë 립í í´ë¬ì¤í° 모ëë³´ë¤ ë«ë¤. ëë©ì¸ í´ë¬ì¤í° 모ëë ë¤ì ë´ì©ë¤ë¡ 구ì±ëì´ìë¤. Domain Controller : í´ë¬ì¤í°ì ê° ë ¸ëì ëí ì¼ë° 구ì±ì ì ì¥, ê´ë¦¬ ë° ê²ìíë íë¡ì¸ì¤. ì´ íë¡ì¸ì¤ë í´ë¬ì¤í° ë ¸ëê° êµ¬ì±ì ì»ë ì¤ì¬ì ìí ì íë¤. Host Controller : í¹ì 물리 ìë²ìì ì¬ë¬ê°ì ìë² ì¸ì¤í´ì¤ë¤ì ê´ë¦¬íë¤. ëë©ì¸ 컨í¸ë¡¤ë¬ë ì¬ë¬ ì»´í¨í°ì ì¤ì¹ë í¸ì¤í¸ 컨í¸ë¡¤ë¬ì ìí¸ ìì©íì¬ í´ë¬ì¤í°ë¥¼ ê´ë¦¬í ì ìë¤. ëí íëì ìë²ìì í¸ì¤í¸, ëë©ì¸ê°ì´ ì¬ë¬ íë¡ì¸ì¤ê° ì¤íëë걸 ë°©ì§íê³ ì¶ì ëë ëë©ì¸ 컨í¸ë¡¤ë¬ë¡ í¸ì¤í¸ 컨í¸ë¡¤ë¬ ìí ì ì¤ ì ìë¤. Domain Profile : í´ë¬ì¤í°ì ìí ì¬ë¬ ë ¸ëë¤ì ì¤ì ë¤ì´ë¤. ëë©ì¸ 컨í¸ë¡¤ë¬ìì ë¤ë¥¸ ë ¸ëë¤ì íë¡íì ì ìí ì ìë¤. Server Group : ë§ ê·¸ëë¡ ìë² ê·¸ë£¹ì´ë¤. íëì ìë² ê·¸ë£¹ì ëë©ì¸ íë¡íì¼ì í ë¹ í ì ìì¼ë©°, 그룹 ë´ì 모ë ìë¹ì¤ë ê°ì ëë©ì¸ íë¡íì¼ì ì¬ì©íë¤. ì
393¤ì íì¼ì domain/configuration/domain.xml ì´ë¤. ëë©ì¸ í´ë¬ì¤í° 모ëì ìì¸í ë´ì©ì ë¤ìì ê³µì문ìì ëììë¤. Cross-Datacenter Replication Mode : ë´ì©ë ë§ì´ ê¸¸ê³ í´ì ê°ë¨íê² ìê° ì ëë§ íê² ë¤. ìì¸í ë´ì©ì ê³µì문ì 를 ì°¸ê³ íì. êµì°¨ ë°ì´í°ì¼í° ë³µì 모ëë ìì§ ìì í ì§ìëì§ ìë 모ëì´ë¤. ìì ë§í 3ê°ì§ 모ëë íëì ê³µì ë°ì´í°ë² ì´ì¤ë¥¼ ì¬ì©íì§ë§ ì¬ê¸°ìë ì¬ë¬ê°ì ë°ì´í°ë² ì´ì¤ë¥¼ ìë¡ì í´ë¬ì¤í°ê° ì¬ì©íë 모ëì´ë¤. ì°¸ê³ : ê³µì 문ì - ì¤í 모ë Keycloak RDBMS ì¤ì ì´ì keycloak ìì h2 ë°ì´í°ë² ì´ì¤ë¥¼ ì¬ì©íë ê²ì´ ìëë¼ ì¬ì©ìì DB ì ë°ì´í°ë¥¼ ì¬ì©íì¬ ë¡ê·¸ì¸íë ì¤ì ì ì¶ê°í´ë³´ì. jdbc driver ì¤ì¹ (ì¬ê¸°ìë MariaDB) : ë¤ì´ë¡ë ë°ì driver íì¼ì modules ëë í 리 ìë ìì¹ìí¨ë¤. modules ëë í 리ë keycloak ì í´ëì¤ ê²½ë¡ì´ë©°, Dependency 를 ì ìí ê³³ì´ë¤. jdbc driver 를 ë¤ìê³¼ ê°ì´ ê²½ë¡ë¥¼ ìì±íì¬ ìì¹ìí¤ì. modules/system/layers/keycloak/org/{database name}/main/ ììì ë§í ê²½ë¡ë¥¼ ë³´ë©´ module.xml íì¼ì´ ë³´ì´ë ë° ì¬ê¸°ì ë´ê° ì¶ê°í driver 를 keycloak ì 모ëë¡ ì¬ì©íê² ë¤ë¼ê³ ì¤ì í´ì¤ë¤. <?xml version=\"1.0\" ?> <!-- name ì org.{database name} ì ì ì´ì¤ë¤ --> <module xmlns=\"urn:jboss:module:1.3\" name=\"org.mariadb\"> <resources> <!-- ì¤ì driver jar íì¼ì ì´ë¦ --> <resource-root path=\"mariadb-java-client-2.6.1.jar\"/> </resources> <dependencies> <module name=\"javax.api\"/> <module name=\"javax.transaction.api\"/> </dependencies> </module> Driver ì ì¸ ë° ë¡ë : ì´ì ì¶ê°í jdbc driver 모ëì ìë²ì ë¡ëí기 ìí ì¤ì ì ì¶ê°íì. standalone.xml / standalone-ha.xml / domain.xml ì¤ì íì¼ <subsystem xmlns=\"urn:jboss:domain:datasources:5.0\"> <datasources> <drivers> <driver name=\"h2\" module=\"com.h2database.h2\"> <xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class> </driver> <!-- ì¬ê¸°ì ì¶ê°í jdbc driver ìì± --> <!-- driver ì´ë¦ì ìì±, module ì 1ë²ìì name ì ì¤ì í ì´ë¦ --> <driver name=\"mariadb\" module=\"org.mariadb\"> <xa-datasource-class>org.mariadb.jdbc.MariaDbDataSource</xa-datasource-class> </driver> </drivers> </datasources> </subsystem> keycloak ì ë°ì´í° ìì¤ë¥¼ ë³ê²½ : 기본 H2 를 ì¬ì©íë ë°ì´í° ìì¤ë¥¼ mariadb 를 ì¬ì©íë ë°ì´í° ìì¤ë¡ ë³ê²½íì. ê°ì íì¼ìì ë¤ì ë¶ë¶ì ìì íë©´ëë¤. ì¬ê¸°ì 주ìí ì ì´ íë ìëë° MySQL, MariaDB ì ê²½ì° ê¸°ë³¸ì ì¼ë¡ utf8mb4 ë¡ ëì´ ìëë°, ë°ëì utf8 ë¡ ë³ê²½ í ë¤ìì ìì±íì. how to change utf8mb4 to utf8 <subsystem xmlns=\"urn:jboss:domain:datasources:5.0\"> <datasources> <datasource jndi-name=\"java:jboss/datasources/KeycloakDS\" pool-name=\"KeycloakDS\" enabled=\"true\" use-java-context=\"true\"> <!-- ì¬ì©ì DB ì 주ì (ì¸ì½ë© UTF8 ë¡ ë³ê²½) --> <connection-url>jdbc:mariadb://localhost/keycloak?characterEncoding=UTF-8</connection-url> <!-- ë°ì´í° ìì¤ ì¤ì ì ì´ë¦ --> <driver>mariadb</driver> <pool> <max-pool-size>20</max-pool-size> </pool> <security> <user-name>{username}</user-name> <password>{password</password> </security> </datasource> </datasources> </subsystem>
393 ìë² ë°ì´í°ë² ì´ì¤ êµ¬ì± : ì´ì keycloak ìë²ì ë°ì´í° ë² ì´ì¤ë¥¼ 구ì±í´ë³´ì. ìì ê°ì íì¼ìì ë¤ì ë¶ë¶ì ìì íë©´ ëë¤. <subsystem xmlns=\"urn:jboss:domain:keycloak-server:1.1\"> <spi name=\"connectionsJpa\"> <provider name=\"default\" enabled=\"true\"> <properties> <property name=\"dataSource\" value=\"java:jboss/datasources/KeycloakDS\"/> <property name=\"initializeEmpty\" value=\"false\"/> <property name=\"migrationStrategy\" value=\"manual\"/> <property name=\"migrationExport\" value=\"${jboss.home.dir}/keycloak-database-update.sql\"/> </properties> </provider> </spi> </subsystem> dataSource : ë°ì´í° ìì¤ ì´ë¦, 3ë² ì¤ì ìì jndi-name ë¶ë¶ì´ë¤. driver : ì´ê¸° ì¤ì ììë ìë³´ì¸ë¤. hibernate ê° ìëì¼ë¡ ê°ì§í기 ë문ì ë°ë¡ ì¤ì í íìê° ìë¤. initializeEmpty : value ê° false ì¼ ê²½ì° ìëì¼ë¡ ì´ê¸°í íë¤. migrationStrategy : update, manual, validate ê° ìë¤. update ë ìëì¼ë¡ keycloak ê´ë ¨ í ì´ë¸ì ìì±. manual ì ê²½ì° ìëì¼ë¡ ìì±í´ì¤ì¼ íë¤. validate ë ë¨ìí ë°ì´í°ë² ì´ì¤ì ì¤í¤ë§ê° ìµì ì¸ì§ë§ ê²ì¬íë¤. migrationExport : ë°ì´í°ë² ì´ì¤ ì´ê¸°í / ë§ì´ê·¸ë ì´ì íì¼ì ìì±íë ê²½ë¡ showSql : Default false, SQL ì ì¶ë ¥í ì§ ì¬ë¶ formatSql : Default true SQL í¬ë§·í ì¬ë¶ ì´ë ê² í´ì RDBMS ìì ì°ê²°ì´ ëë¬ë¤. ìë²ë¥¼ ì¤í í í ë°ì´í°ë² ì´ì¤ë¥¼ íì¸í´ë³´ë©´ ì¬ë¬ê°ì í ì´ë¸ì´ ì¶ê°ë ê²ì íì¸í ì ìë¤. ë§ì¹ë©° ë¤ìì Keycloak ìì ì ê³µíë REST API 를 ìì²íê³ ë°ë ë°©ë²ê³¼ Custom User Attribute 를 ì¬ì©í기 ìí User Storage SPI 를 íì©í´ë³´ë 걸 ììë³´ì. ","categories": ["posts"], 394 "tags": ["msa","keycloak"], 395 "url": "https://subji.github.io/posts/2020/07/08/keycloak2", 396 "teaser":null},{ 397 "title": "Throttling ê³¼ Debouncing ì ëí´ì", 398 "excerpt":"ë¤ì´ê°ë©° DOM ì´ë²¤í¸ ì¤ ì¤í¬ë¡¤ì ê´í ê²ì´ ìë¤. ì¤í¬ë¡¤ì í ëë§ë¤ ë°ìíë ì´ë²¤í¸ ê°ì ê²ì´ë¤. ì´ë²¤í¸ ìì ë¡ê·¸ë¥¼ ì°ì´ë³´ë©´ ì¤í¬ë¡¤ì í ëë§ë¤ ììì´ ë§ì ë¡ê·¸ê° ì¶ë ¥ëë¤. ì´ë ê² ë ê²½ì° ì´ë²¤í¸ í¨ì ìì ajax í¸ì¶ë±ê³¼ ê°ì ë§ì 리ìì¤ê° ì모ëë ëìì íê² ëë¤ë©´ íì´ì§ ì±ë¥ì ìí¥ì 미ì¹ê² ëë¤. ê·¸ëì ëì? ì´ë¼ê³ í ì ìë ë°©ë²ì´ Throttling ê³¼ Debouncing ì´ë¤. Throttling Throttling ì ì´ë²¤í¸ ìì ìë í¨ìì í¸ì¶ ìµë íì를 ì ííë ê²ì´ë¤. ì를ë¤ë©´ ë¤ìê³¼ ê°ë¤. ì¤í¬ë¡¤ ì´ë²¤í¸ë¥¼ ëê³ 10ì´ëì ì¤í¬ë¡¤ ëìì ìííìì ë í¨ìê° ì´ 1000ë² ì¤íëë¤ê³ íì. í ë²ì ì¤íì´ 1ms ë¼ê³ íìë, 10ì´ ëì ì´ 10000ms ì ìê°ì´ ììëë¤. ì´ë Throttling ì ì¬ì©íë¤. ì´ 10000ms ìì 100ms ë§ë¤ íë²ì© ì¤íì ê°ì íì. ê·¸ë¬ë©´ ì´ 100ë²ì í¨ìê° ì¤íëë¤. 10ì´ë¼ë ìê°ë´ì Throttling 기ë²ì ì¬ì©í´ì 1/10ì ìê°ì´ ì¤ì´ë ê² ëê²ì´ë¤. Throttling ì â100ms ì´ì íë²ì© ì¤ííììì¤â ì ê°ì´ ì´ ì¤í ìê° ë´ìì í¨ì í¸ì¶ì ìµë íì를 ì ííë ê²ì´ë¤. Debouncing Debouncing ì Throttling ê³¼ ì¡°ê¸ì ë¹ì·íë¤. Debounce ë â그룹íâ ê° ê°ë¥íë¤. ì´ê²ì´ 무ì¨ë§ì´ëíë©´ Debounce ì ê°ë ì ë³´ë©´ ìì ìëë°, Debounce ë N ì´ê° ì§ë í íë² ì¤íëë ë°©ë²ì´ë¤. ì¦, N ì´ê° ì§ë기 ì ê¹ì§ í¨ìê° ì¤íëì§ ìëë¤. ì¦, ì¬ë¬ë² ì´ë²¤í¸ê° ë°ìíëë¼ê³ N ì´ê° ì§ë íì íë². ë¨ íë²ë§ ì¤ííë ê²ì´ë¤. ì´í ë¤ì íì´ë¨¸ê° ì¬ì¤ì ëê³ ëë¤ì 기ë¤ë ¸ë¤ê° N ì´íì íë² ë°ìíë ê²ì´ë¤. ì´ë Debounce ì í¹ì§ ì¤ íëì¸ â그룹íâ 를 ë³¼ ì ìë¤. N ì´ ê¹ì§ 기ë¤ë¦° ì´ë²¤í¸ ë´ì©ë¤ì´ 그룹ì§ì´ì ¸ N ì´ íì íë² ì¤íëë ê²ì´ë¤. Throttling, Debounce ì°¨ì´ ì°¨ì´ë ê°ë¨íë¤. Throttling ì ê²½ì° N ì´ë§ë¤ ì¤íëë ê². Debounce ì ê²½ì° N ì´ í íë² ì¤íëë ê². ë§ì¹ë©° ê°ëµíê² ì¬ë¬ ê¸ë¤ì ì½ì ë°íì¼ë¡ ì 리íë¤. ìì¸í ë´ì©ì ë¤ìì ì°¸ê³ íì. CSS-Tricks : The difference between throttling and debouncing ","categories": ["posts"], 399 "tags": ["javascript"], 400 "url": "https://subji.github.io/posts/2020/07/13/throttlinganddebouncingwithjs", 401 "teaser":null},{ 402 "title": "MSA ì¸ì¦ ìë¹ì¤ Keycloak User Storage SPI", 403 "excerpt":"SPI ? Keycloak ìì ì ê³µíë 모ëê³¼ ê°ì ê°ë ì´ë¤. SPI ë Service Provider Interface ì ì¤ìë§ë¡ Keycloak ì ìë ê° ê¸°ë¥ë¤ì ê°ë°ìê° ì§ì 구íí ì ìê² ëìì¤ë¤. ì¼ë°ì ì¼ë¡ Provider íì¼ê³¼ ProviderFactory íì¼ë¡ 구ííë¤. 구í ê°ë¥í 기ë¥ë¤ì ë¡ê·¸ì¸ & íìê°ì ë±ì í ë§, LDAP, ì¸ì¦ ë±ì´ ìë¤. User Storage SPI User Storage SPI ë 기본ì ì¼ë¡ H2 ë°ì´í°ë² ì´ì¤ë¥¼ ì¬ì©íë Keycloak ì ìì ì ì¸ë¶ DB ìë ì°ê²°íëë¡ ëìì¤ë¤. ì¦, ì¸ë¶ì ì¤ì¹ë DB 를 ì´ì©í´ì ì¸ì¦ì í ì ìê² ëë¤. ì°¸ê³ : User Storgae SPI User Storage 구í User Storage ëë í 리 구조 adapter/CustomUserAdapter
403: ì¸ë¶ DB 컬ë¼ê³¼ Keycloak DB 를 매íìì¼ì£¼ë ì´ëí° entity/AuthoritiesMapping: ì¸ë¶ DB ì ê¶í 매í ìí°í° entity/AuthoritiesMappingKey: ì¸ë¶ DB ì ê¶í 매í ë³µí©í¤ ìí°í° entity/AuthoritiesType: ì¸ë¶ DB ì ê¶í ìí°í° entity/UserInfo: ì¸ë¶ DB ì ì¬ì©ì ìí°í° repository/CustomUserStorageRepository: ì¸ë¶ DB 리í¬ì§í 리 (Hibernate ì¬ì©) CustomUserStorageProvider: ì¸ë¶ DB ì ë°ì´í°ë¥¼ ì¬ì©íì¬ ì¸ì¦, ì¬ì©ì ê´ë¦¬ë±ì 구í CustomUserStorageProviderFactory: Provider Id 를 ì¤ì íê³ Keycloak ê³¼ ì°ê²° resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory: ë°ëì ë§ë¤ì´ì¼ëë íì¼ë¡ Keycloak ììë ì´ íì¼ë¡ Provider 를 ì°¾ì ì ì©íë¤. resources/META-INF/persistence.xml: Hibernate ì¤ì íì¼ ì´ì íëíë ì½ë를 ë³´ë©° ììë³´ì. 먼ì ê°ì¥ ì°ì ì¼ë¡ 구íí´ì¼ ëëê²ì´ META-INF ìë íì¼ê³¼ Provider, ProviderFactory ì´ë¤. 먼ì META-INF/services ìë íì¼ì ë³´ë©´ íì¼ ì´ë¦ì Keycloak ì UserStorageProviderFactory í´ëì¤ì í¨í¤ì§ ê²½ë¡ë¥¼ í¬í¨í ì´ë¦ì´ë¤. íì¼ì ë§ë¤ë©´ ë´ì©ì ê°ë¨íê² ê°ë°ìê° ë§ë Custom User Storage Provider ì í¨í¤ì§ ê²½ë¡ì íì¼ëª
ì ì ì´ì£¼ë©´ ëë¤. resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory # {package path}.{provider factory file name} com.demo.storage.CustomUserStorageProviderFactory ë¤ìì¼ë¡ Provider Factory 를 ë³´ì. CustomUserStorageProviderFactory import javax.naming.InitialContext; import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.storage.UserStorageProviderFactory; public class CustomUserStorageProviderFactory implements UserStorageProviderFactory<CustomUserStorageProvider> { @Override public CustomUserStorageProvider create(KeycloakSession session, ComponentModel model) { try { InitialContext ctx = new InitialContext(); // 1) java:global/{jar íì¼ëª
}/Provider í´ëì¤ëª
CustomUserStorageProvider provider = (CustomUserStorageProvider) ctx.lookup(\"java:global/custom-user-storage-jpa/\" + CustomUserStorageProvider.class.getSimpleName()); provider.setModel(model); provider.setSession(session); return provider; } catch (Exception e) { throw new RuntimeException(e); } } @Override public String getId() { // 2) configuration.xml ìì <spi name=\"{idê°}\"> ì ìì± return \"custom-user-storage\"; } } JBoss(WildFly) ìì ì¤ì ë´ì©ì ì°¾ëë¤. ì¬ê¸°ìë ì ì ì¤ì ì¤ ê°ë°ìê° êµ¬íí User Storage Provider 를 ì°¾ì¼ë©°, 주ìí´ì¼ í ì ì âjava:global/â ë¤ì ë°ëì ê°ë°ìê° ë¹ëí jar ì íì¼ëª
ì¼ë¡ ìì±í´ì¼ íë¤ë ê²ì´ë¤. WildFly ê° ì²ìì¸ ë´ê²ë ë°ëì ì ê¼¬ë° ë³´ë¼ì ëë¡ ì°¾ê¸° íë¤ìë¤.. ë§ì§ë§ì¼ë¡ ë¤ì Provider í´ëì¤ ëª
ì ì
ë ¥íì. ì¬ê¸°ë ì¤ìí ë¶ë¶ì¸ë° Keycloak ììë standalone.xml íì¼ì SPI ë¤ì´ ì ì¸ë ë¶ë¶ì´ ìëë°, ì¬ê¸°ì ê°ë°ìê° ë§ë SPI ë ìì±íê² ëë¤. ì´ë Keycloak ì 무ì¸ê°ë¥¼ ì´ì©í´ì ê°ë°ìê° ë§ë SPI 를 찾거ë ì¬ì©í´ì¼ íëë° ì´ë ì ì¸íëê² Id ê°ì´ë¤. ëì¤ì standalone.xml íì¼ìë ìì±íê² ì§ë§ ì´ë¤ ììì´ ì í´ì§ê² ìëì´ì ë§ìëë¡ ìì±í´ë ëë¤. íì§ë§ ì¤ìíì ì ì¬ê¸°ì ì
ë ¥í Id ê°ì´ë standalone.xml ì ìì±í Id ê°ì´ë ê°ìì¼ íë¤. ì ëê° íì¼ì ë¨ì§ Keycloak ê³¼ 매ì¹ìì¼ì£¼ë ìí ì ì¤ì ì¼ë¡ íë¤. ë¤ìì 본격ì ì¼ë¡ SPI ì ë´ì©ì 구ííë í´ëì¤ë¤. ì¬ê¸°ë 구íë ë©ìëê° ì¡°ê¸ ë§ìì ë°ë¡ ì¤ëª
í기 ë³´ë¤ë ì½ëì 주ìì ë¬ìì ê°ëµíê² ì¤ëª
ì íê² ë¤. CustomUserStorageProvider import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import javax.ejb.Local; import javax.ejb.Remove; import javax.ejb.Stateful;
403 import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import com.daumsoft.storage.userStorage.adapter.CustomUserAdapter; import com.daumsoft.storage.userStorage.entity.UserInfo; import org.keycloak.component.ComponentModel; import org.keycloak.credential.CredentialInput; import org.keycloak.credential.CredentialInputUpdater; import org.keycloak.credential.CredentialInputValidator; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.credential.OTPCredentialModel; import org.keycloak.models.credential.PasswordCredentialModel; import org.keycloak.storage.StorageId; import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserQueryProvider; import org.keycloak.storage.user.UserRegistrationProvider; import org.mindrot.jbcrypt.BCrypt; // StateFul Bean ì¼ë¡ ì ì¸ @Stateful // EJB í´ë¼ì´ì¸í¸ì ëì¼í íê²½ì ìì ê²½ì° ì ì¸ ì¦, ì¬ê¸°ì WildFly ì ë°°í¬ë기 ë문ì ëì¼í íê²½ì¼ë¡ ê°ì£¼í¨ @Local(CustomUserStorageProvider.class) // User Storage SPI public class CustomUserStorageProvider implements UserStorageProvider, // ì¬ì©ì ì¡°í UserLookupProvider, // ì¬ì©ì ì¡°í UserQueryProvider, // ì¬ì©ì ë±ë¡ UserRegistrationProvider, // ë¹ë°ë²í¸ ê²ì¬ CredentialInputValidator, // ë¹ë°ë²í¸ ë³ê²½ CredentialInputUpdater { protected ComponentModel model; protected KeycloakSession session; // ì¸ë¶ DB 쿼리를 ì¬ì©í EntityManager ì¬ì©, Spring Boot JPA 를 ì¬ì©íë ¤ íì§ë§ ìëì ê·¸ë¥ Hibernate 를 ì¬ì© @PersistenceContext protected EntityManager entityManager; // ë¹ ìì±ì ìì±, ìí´ì£¼ë©´ ìë¬ê° ëë¤. public CustomUserStorageProvider() {} // Keycloak ì ì¬ì©ì ì¸ì
ê³¼ ì»´í¬ëí¸ ëª¨ë¸ì ê°ì ¸ì´ public CustomUserStorageProvider(KeycloakSession session, ComponentModel model) { this.model = model; this.session = session; } // ìì
ì´ ëëë©´ EntityManager ì¸ì
ì¢
ë£ë±ì í꺼ë²ì ìííë ë©ìë @Remove @Override public void close() {} /** * ì¬ì©ìê° ë¡ê·¸ì¸ì í ë ì
ë ¥ë Id ë¡ ì¬ì©ì ì 보를 ì¡°í */ @Override public UserModel getUserById(String keycloakId, RealmModel realm) { System.out.println(\"\\n\\ngetUserById\\n\\n\"); String userId = StorageId.externalId(keycloakId); UserInfo userInfo = entityManager.createNamedQuery(\"findByUserId\", UserInfo.class) .setParameter(\"userId\", userId) .getResultList().stream().findFirst().orElse(null); return new CustomUserAdapter(session, realm, model, userInfo); } /** * ì¬ì©ìê° ë¡ê·¸ì¸ì í ë ì
ë ¥ë username ì¼ë¡ ì¬ì©ì ì 보를 ì¡°í */ @Override public UserModel getUserByUsername(String username, RealmModel realm) { System.out.println(\"\\ngetUserByUsername : \" + username + \"\\n\"); UserInfo userInfo = entityManager.createNamedQuery(\"findByUserId\", UserInfo.class) .setParameter(\"userId\", username) .getResultList().stream().findFirst().orElse(null); return userInfo == null ? null : new CustomUserAdapter(session, realm, model, userInfo); } /** * ì¬ì©ìê° ë¡ê·¸ì¸ì í ë ì
ë ¥ë Email ë¡ ì¬ì©ì ì 보를 ì¡°í */ @Override public UserModel getUserByEmail(String email, RealmModel realm) { System.out.println(\"\\ngetUserByEmail: \" + email + \"\\n\"); UserInfo userInfo = entityManager.createNamedQuery(\"findByUserEmail\", UserInfo.class) .setParameter(\"userEmail\", email) .getResultList().stream().findFirst().orElse(null); return userInfo == null ? null : new CustomUserAdapter(session, realm, model, userInfo); } /** * ì¬ì©ì ì¶ê°, ì¸ë¶ DB ì ë¤ì´ê°ë ê²ì´ ìëë¼ Keycloak DB ì ì¶ê°ëë¤. */ @Override public UserModel addUser(RealmModel realm, String username) { // System.out.println(\"\\naddUser\\n\"); // System.out.println(username); // System.out.println(\"\\naddUser\\n\"); // UserInfo userInfo = new UserInfo(); // userInfo.setUserId(username); // userInfo.setDelYn(\"N\"); // entityManager.persist(userInfo); // return new CustomUserAdapter(session, realm, model, userInfo); return null; } /** * ì¬ì©ì ìì , ìì Keycloak DB ìì ì ê±°íë ì©ë */ @Override public boolean removeUser(RealmModel realm, UserModel user) { return false; } /** * ì¬ì©ì ë¹ë°ë²í¸ ì
ë°ì´í¸ */ @Override public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) { System.out.println(\"updatecredential\\n\\n\"); System.out.println(user.toString()); System.out.println(input); System.out.println(\"updatecredential\\n\\n\"); return true; } /** * ë¹ë°ë²í¸ ì¢
ë¥(Otp, Password, ë±) ì ë¹íì±í */ @Override public void disableCredentialType(RealmModel realm, UserModel user, Str
403ing credentialType) { System.out.println(\"disableCredentialType\\n\\n\"); System.out.println(user.toString()); System.out.println(credentialType); System.out.println(\"disableCredentialType\\n\\n\"); } /** * ë¹íì±í ë ë¹ë°ë²í¸ ì¢
ë¥(Otp, Password) ì¡°í, ì¬ê¸°ì OTP ë ì¬ì©íì§ ìì ê²ì´ë¯ë¡ OTP 를 ë°í */ @Override public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) { System.out.println(\"getDisableableCredentialTypes\\n\\n\"); Set<String> set = new HashSet<>(); set.add(OTPCredentialModel.TYPE); return set; } /** * ì§ìëë ë¹ë°ë²í¸ ì¢
ë¥ (Password, Otp, ...) ê° íì¬ ì¬ì©í ì¢
ë¥ì ë§ëì§ íì¸ */ @Override public boolean supportsCredentialType(String credentialType) { System.out.println(\"\\n\\n supportsCredentialType: \" + credentialType + \"\\n\\n\"); return PasswordCredentialModel.TYPE.equals(credentialType); } /** * ì íí ì´ë¤ 기ë¥ì¸ì§ë 모르ì§ë§ ë¹ë°ë²í¸ ì¸ì¦ì ì´ë¤ ì¤ì ì í´ì£¼ë? ë©ìë */ @Override public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) { System.out.println(\"\\n\\n isConfiguredFor: \" + credentialType + \"\\n\\n\"); return false; } /** * ë¹ë°ë²í¸ ê²ì¬ ë©ìë, ì¬ê¸°ìë Bcrypt 를 ì¬ì©íë¤. */ @Override public boolean isValid(RealmModel realm, UserModel user, CredentialInput credentialInput) { if (!(credentialInput instanceof UserCredentialModel)) return false; if (supportsCredentialType(credentialInput.getType())) { UserInfo userInfo = (UserInfo)session.getAttribute(\"userInfo\"); return BCrypt.checkpw(credentialInput.getChallengeResponse(), userInfo.getUserPassword()); } else { return false; } } /** * ì¬ì©ì ì를 ì¡°í */ @Override public int getUsersCount(RealmModel realm) { return 0; } /** * ì¬ì©ì 목ë¡ì ì¡°í, Keycloak REST API ë±ìì ì¬ì© */ @Override public List<UserModel> getUsers(RealmModel realm) { System.out.println(\"\\ngetUsers\\n\"); return null; } /** * firstResult íì´ì§ìì maxResults ê°ì ì¬ì©ì를 ì¡°í */ @Override public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) { System.out.println(\"getUsers\\n\\n\"); System.out.println(firstResult + \", \" + maxResults); System.out.println(\"getUsers\\n\\n\"); return null; } /** * Keycloak ê´ë¦¬ì ëìë³´ëìì ê²ìí ì¬ì©ì 목ë¡ì ì¡°í */ @Override public List<UserModel> searchForUser(String search, RealmModel realm) { System.out.println(\"\\nsearchForUser 1 :\" + search + \"\\n\\n\"); return null; } /** * Keycloak ê´ë¦¬ì ëìë³´ëìì ê²ìí ì¬ì©ì 목ë¡ì ì¡°í */ @Override public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { System.out.println(\"\\nsearchForUser 2 :\" + search + \"\\n\"); List<UserInfo> result = entityManager.createNamedQuery(\"findByUserIdLike\", UserInfo.class).setParameter(\"userId\", search).getResultList(); // AuthoritiesType authType = result.get(0).getAuthoritiesMapping().get(0).getAuthoritiesType(); // System.out.println(\"searchForUser 2 Role: \"); // System.out.println(authType.getAuthorityType()); // System.out.println(\"searchForUser 2 end\"); return result .stream() .map(user -> new CustomUserAdapter(session, realm, model, user)) .collect(Collectors.toList()); } @Override public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm) { System.out.println(\"\\nsearchForUser 3 : \\n\\n\"); return null; } /** * Keycloak ê´ë¦¬ì ëìë³´ëìì ì ì²´ ì¬ì©ì 목ë¡ì ì¡°í */ @Override public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) { System.out.println(\"\\nsearchForUser 4 :\\n\\n\"); List<UserInfo> result = entityManager.createNamedQuery(\"findAll\", UserInfo.class) .setFirstResult(firstResult) .setMaxResults(20) .getResultList(); return result.stream().map(user -> new CustomUserAdapter(session, realm, model, user)) .collect(Collectors.toList()); } /** * Keycloak ëìë³´ëìì ê²ìí 그룹 멤ë²ë¥¼ ì¡°í */ @Override public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { return null; } /** * Keycloak ëìë³´ëìì ì ì²´ 그룹 멤ë²ë¥¼ ì¡°í */ @Override public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) { return null; } /** * Keycloak ëìë³´ëìì ì¬ì©ì Attribute 를 ì¡°í */ @Override public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { return null; } /** * Provider ìì ì¬ì©ëë Setter */ public void setModel(ComponentModel model) { this.model = model; } /** * Provider ìì ì¬ì©ëë Setter */ public void setSession(KeycloakSession session) { this.session = session; } } Provider í´ëì¤ë ê°ë¨í ì¸ì¦ ì¸ìë Keycloak ëìë³´ëìì ì¬ì©ëë ì¬ì©ì ê´ë ¨ ì¡°í, ë±ë¡, ë³ê²½, ìì ë±ì ì§ì 구íí ì ìê² ëì´ìë¤. ë¤ìì ì¸ë¶ DB ì ë´ì©ë¤ì Keycloak DB ì 매íí ì ìê² ëì주ë Adapter í´ëì¤ë¥¼ ë³´ì. ìì ì ì§ ìì ë©ìëë¡ ì¸í´ ë³ëë¡ ì¤ëª
ë³´ë¤ë ê°ë¨í 주ìì¼ë¡ ì²ë¦¬íë¤. CustomUserAdapter import
403com.daumsoft.storage.userStorage.entity.AuthoritiesType; import com.daumsoft.storage.userStorage.entity.UserInfo; import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.storage.StorageId; import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; // User Adapter Federated Storage ì¶ìí´ëì¤ ìì public class CustomUserAdapter extends AbstractUserAdapterFederatedStorage { protected UserInfo userInfo; protected String keycloakId; // Keycloak DB ì 매íë Adapter ìì±ì public CustomUserAdapter(KeycloakSession session, RealmModel realm, ComponentModel model, UserInfo userInfo) { super(session, realm, model); this.userInfo = userInfo; if (userInfo != null) { // ì¸ë¶ DB ìì ì¡°íí ì¬ì©ì ì ë³´ê° null ì´ ìë ê²½ì° ì¸ë¶ DB ì ì¬ì©ì PK 를 Keycloak ì¬ì©ì DB ì PK ê°ì¼ë¡ ì¬ì© this.keycloakId = StorageId.keycloakId(model, String.valueOf(userInfo.getUserSeq())); // Provider ìì ì¬ì©í기 ìí´ ì¸ì
ì ì¬ì©ì ì ë³´ ê°ì ì ì¥ session.setAttribute(\"userInfo\", userInfo); } } /** * Keycloak DB ì username ì
ë ¥, ì¡°í ì¼ë°ì ì¼ë¡ ì¬ì©ëë id ê°ë
ì´ë¤ */ @Override public String getUsername() { return userInfo != null ? userInfo.getUserId() : null; } @Override public void setUsername(String username) { userInfo.setUserId(username); } /** * Keycloak DB ì Email ì
ë ¥, ì¡°í */ @Override public String getEmail() { return userInfo != null ? userInfo.getUserEmail() : null; } @Override public void setEmail(String email) { userInfo.setUserEmail(email); } /** * ì¸ë¶ DB ì Role ì ê°ì ¸ì Keycloak ìì ì¬ì©í ì ìê²íë ë©ìë */ @Override public void grantRole(RoleModel role) { AuthoritiesType authType = userInfo.getAuthoritiesMapping().get(0).getAuthoritiesType(); System.out.println(\"grantRole\\n\"); System.out.println(authType.getAuthorityType()); System.out.println(\"\\ngrantRole\"); } } 3ê°ì 주ì í´ëì¤ì íëì ì¤ì íì¼ë¤ì ë³´ìë¤. ìí°í°ì 리í¬ì§í 리ì ê²½ì°ë ì¸ë¶ DB íê²½ì ë§ì¶°ì 구ííë©´ ë ê±° ê°ë¤. ì¶ê°ë¡ íëë ììë³´ìë©´ ì´ì ì Keycloak standalone.xml ì¤ì íì¼ìì ìì±í ì¸ë¶ DB ìì¤ê° ìë¤. ì´ê±¸ ì¬ì©íë ¤ë©´ persistance.xml ì ë¤ìì ì¶ê°í´ì£¼ë©´ ëë¤. persistance.xml <?xml version=\"1.0\" encoding=\"UTF-8\"?> <persistence xmlns=\"https://xmlns.jcp.org/xml/ns/persistence\" xmlns:xsi=\"https://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"https://xmlns.jcp.org/xml/ns/persistence https://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd\" version=\"2.1\"> <persistence-unit name=\"custom-user-storage-jpa\"> <description>Hibernate Entity Manager Example</description> <!-- standalone.xml ìì ìì±í DB ì¤ì ë¤ì ê°ì ¸ì¨ë¤. --> <jta-data-source>java:jboss/datasource/UserStoreXADS</jta-data-source> <properties> <property name=\"hibernate.show_sql\" value=\"true\" /> <property name=\"hibernate.hbm2ddl.auto\" value=\"none\" /> <property name=\"hibernate.dialect\" value=\"org.hibernate.dialect.MariaDBDialect\" /> <!-- <property name=\"hibernate.physical_naming_strategy\" value=\"org.ohjic.hfinSync.PhysicalNamingStrategyImpl\" /> --> </properties> </persistence-unit> </persistence> Keycloak standalone.xml ì¤ì Provider 를 ì ì©í기 ìí´ì ëªê°ì§ ì¬ì ì ì¤ì íì¼ì ìì±í´ì¼ íëê²ì´ ìë¤. ë§ì§ë ìê³ ëê°ì§? ì ë ëë¤. ì²«ì§¸ë¡ ê°ë°ìê° ë§ë spi 를 찾기 ìí ì ì¸ì½ë, ì´ì ì ProviderFactory ìì ìì±í Id ê°ì ì¬ê¸°ìì ì¬ì©íë¤. standalone.xml <!-- Provider Factory ìì ìì±í id ê°ì ì
ë ¥ --> <spi name=\"custom-user-storage\"> </spi> ë째ë persistance.xml ìì Keycloak ì¤ì íì¼ìì ìì±í DB ìì±ì ì¬ì©í기 ìí´ ì¤ì í DataSource ë´ì©ì´ë¤. ì´ì ê¸ìì ìì±í Datasource ë¶ë¶ìì âxa-â ë¶ë¶ì ì¶ê°íë¤. ì´ ë¶ë¶ì´ persistance.xml ìì ì¬ì©ëë DB ì¤ì ì´ë¤. standalone.xml <subsystem xmlns=\"urn:jboss:domain:datasources:5.0\"> <datasources> ... <xa-datasource jndi-name=\"java:jboss/datasource/UserStoreXADS\" pool-name=\"UserStoreXADS\"> <xa-datasource-property name=\"ServerName\"> {external db host} </xa-datasource-property> <xa-datasource-property name=\"DatabaseName\"> {external db name} </xa-datasource-property> <driver>mariadb</driver> <security> <user-name>{external db username}</user-name> <password>{external db password}</password> </security> </xa-datasource> <drivers> <driver name=\"mariadb\" module=\"org.mariadb\"> <xa-datasource-class>org.mariadb.jdbc.MariaDbDataSource</xa-datasource-class> </driver> </drivers> </datasources> ... </subsystem> How to use? ê°ë°ë ë¤ ëìê³ , ì¤ì íì¼ë ìì±íë¤. ê·¸ë¼ ì´ì ë¹ë를 í ê²ì´ê³ , jar íì¼ì
403 ë§ë¤ê²ì´ë¤. ê·¸ë¼ ì´ì ì´ê±¸ ì´ë»ê² í´ì¼íëê±°ì§? ë¼ë ê³ ë¯¼ì íê³ ì°¾ìë³´ìë¤. ë©ì§ ììë°ì ì°¾ì ì ììë¤. ê³µì문ìì ì ëìììë¤. ë°©ë²ì ê°ë¨íë¤. standalone/deployments ìëì jar íì¼ì ë³µì¬í´ì£¼ë©´ ëë¤. ë¨! ììì ë§í íì¼ëª ì Provider Factory í´ëì¤ìì ìì±í âjava:globalâ ë¤ì ì¤ë ì´ë¦ê³¼ ê°ê² í´ì¤ì¼ íë¤ëì 주ìíì. ì´ì Keycloak ìë²ë¥¼ ì¬ì¤ííê±°ë ì¤íì¤ì´ë¼ë©´ ê°ë§ëë©´ ìëì¼ë¡ Deploy ê° ëë¤. ê·¸ë¦¬ê³ ìë Keycloak ëìë³´ëìì ì ì ì©ëìëì§ íì¸í´ë³´ì. 먼ì User Federation íì ì ííê³ Select box ìì ìì ì´ ì¶ê°í Id ê°ì ì°¾ëë¤. ì íí í save ë²í¼ì ë르면 ìëì ê°ì´ ì ì¶ê°ë íë©´ì ë³¼ ì ìë¤. ìë ì´ë¯¸ì§ë ê³µì문ììì ê°ì ¸ì¨ê²ì´ë¯ë¡ ë§ì½ ìì ìì±í jar 를 ì¶ê°í´ì¤¬ë¤ë©´ ID ì âcustom-user-storageâ ê° ë³´ì¼ ê²ì´ë¤. ë§ì¹ë©° ì¼ë¨ 구íì íê³ ë³´ë ì¡°ê¸ ì´ë ¤ì´ ì ì´ ë§ìë¤. ì¬ì©ì ì¶ê°ëë ì ìì ì¸ë¶ DB ì Keycloak DB ê° ë¤ë¥´ë¤ê±°ë ì¬ì©ëë Key ë¤ì´ ë무 ë¬ë¼ì 구ííëë° ë¦¬ìì¤ê° ë§ì´ ë¤ì´ê°ê² ëìë¤. ê·¸ëì ê²½íì¼ì 구íì íë¤ê³ ì¹ê³ ë¤ë¥¸ ë°©ë²ì ì°¾ê¸°ë¡ íë¤. ì´ë»ê² í´ì¼ í ê¹ ê³ ë¯¼íë¤ê° ì´ì°¨í¼ Keycloak ìì ì¬ì©ì ê´ë ¨ 모ë 기ë¥ì ì ê³µíë ì°¨ë¼ë¦¬ Keycloak DB 를 ì¬ì©íìê³ ! ê·¸ë¦¬ê³ ë íë ë²ë¡ íì´ì¬ì¼ë¡ Keycloak REST API 를 ì´ì©í´ì ì¸ë¶ DB ë´ì©ì ì ë ¥í´ì£¼ìë¤. REST API ë ë¤ì í¸ìì ê°ë¨í ìê°íê³ , OIDC 를 ì¬ì©íë Keycloak ìì Naver ë¡ê·¸ì¸ì ì°ëíë ë°©ë²ë ì ê¹ ìê°í ê²ì´ë¤. ","categories": ["posts"], 404 "tags": ["msa","keycloak"], 405 "url": "https://subji.github.io/posts/2020/07/24/keycloak3", 406 "teaser":null},{ 407 "title": "MSA ì¸ì¦ ìë¹ì¤ Keycloak REST API, Naver ë¡ê·¸ì¸", 408 "excerpt":"Keycloak REST API ê³µì문ìì ë³´ë©´ ì ë§ ë¤ìí API ë¤ì´ ì ê³µëë¤. ê·¸ì¤ ì¬ê¸°ì ë¤ë¤ë³¼ API ë access_token ì ë°ê¸ë°ë ê²ê³¼, ì¬ì©ì ìì±, Role 매í, ì¬ì©ì ì¡°í API ì´ë¤. ê·¸ë¦¬ê³ ì ê¹ Identity Provider 를 ì¬ì©í´ì OIDC(Open Id Connect) 를 ì§ìíì§ ìë ë¤ì´ë² ë¡ê·¸ì¸ì ì¤ëª íê³ ì íë¤. Keycloak REST API Access Token ë°ê¸ API curl http://localhost:8080/auth/realms/{keycloak realm}/protocol/openid-connect/token \\ -dclient_id={keycloak client id} \\ -dclient_secret={keycloak client secret} \\ -dscope=profile \\ -dgrant_type=client_credentials ììì ë°ë¡ ì ë ¥í´ì¼ íë ë¶ë¶ì realm, clientid, client secret ì ëì´ë¤. ë¬¼ë¡ client_credentials 를 ì¬ì©í ê²½ì° clientid, client secret ì´ íìíë¤. ê²°ê³¼ë ë¤ìê³¼ ê°ì´ ì¶ë ¥ëë¤. ì´ì ë°ìì¨ access token ì¼ë¡ Keycloak ì¸ì¦ì íê³ ë¤ìí API 를 ì¬ì©í ì ìë¤. ììì ë§í ëªê°ì§ë¥¼ ì¬ì©í ìì 를 ë³´ì. Create User curl http://localhost:8080/auth/admin/realms/{keycloak realm}/users \\ -H \"Authorization: bearer {access_token}\" \\ -X POST \\ --data '{ \"username\": \"{username}\", \"enabled\": \"true\", \"credentials\": [ { \"value\": \"{password}\" } ] }' ì¶ê°ë¡ ê³µì문ìì ë³´ë©´ realmRoles ë¼ê³ ìëë°, ìëì´ ìëë¤. ì ì§ë ëª¨ë¥´ê² ë¤. ê°ë°ì ìíê±´ê°? ìë¬´í¼ Role 를 ì¶ê°íê³ ì í ë ì¡°ê¸ íë¤ì§ë§ Keycloak ëìë³´ëìì Role ì ì§ì ì¶ê°í´ì API ë¡ ì¬ì©ìì Role ì 매íìì¼ì¤ì¼ íë¤. Role 매í API ë ë¤ìê³¼ ê°ë¤. ì¬ê¸°ìë Client Role 매íì ì¬ì©íë¤. Realm Role 매íê³¼ë Path íëì ì°¨ì´ë§ ìë¤. Client Role Mapping curl http://localhost:8080/auth/admin/realms/sometrend-realm/users/{keycloak user id}/role-mappings/clients/{keycloak client} \\ -H \"Authorization: bearer {access_token}\" \\ -X POST \\ --data '[ { \"id\": \"{realm/client role id}\", \"name\": \"{role name}\", \"clientRole\": \"true\", \"containerId\": \"{realm/client id}\" } ]' Realm 매íì ì¬ì©íë ¤ë©´ ë¤ì clients/{keycloak client} 를 ì ê±°í´ì£¼ë©´ ëë¤. ì¬ì©ì를 ì¶ê°íê³ Role 매íê¹ì§ í´ì¤¬ì¼ë ì´ì ì¶ê°ë ì¬ì©ì를 ì¡°íí´ë³´ì. ë¤ìì ì ì²´ ì¬ì©ì를 ì¡°ííë ìì²ì´ë¤. ë§ì½ username ëë Email ë±ì¼ë¡ ì¡°ê±´ì ì£¼ê³ ì¡°í를 íê³ ì¶ì¼ë©´ ë¤ìí Parameter ê° ê³µì문ìì ëììë¤. Get User Parameter Find User curl http://localhost:8080/auth/admin/realms/{keycloak realm}/users Naver ë¡ê·¸ì¸ ê°ë¨íê² API 를 ì´ì©í´ì ì¬ì©ì를 Keycloak DB ì ì¶ê°íë¤. ì´ì ë¡ê·¸ì¸ë ìëë¤. ì¬ê¸°ì ì¶ê°ë¡ ìì ë¡ê·¸ì¸ì ì¶ê°íê³ ì¶ë¤. ë ì를ë¤ì´ 구ê¸ì´ë¼ëê° íì´ì¤ë¶ì´ë¼ëê°.. ë¤íì´ë Keycloak ì OIDC 를 ì§ìíë ë¤ìí ìì ë¡ê·¸ì¸ë¤ì ì§ìíë¤. ìëì 그림ì ë³´ë©´ ì¬ë§í ìì ë¡ê·¸ì¸ì ì§ìíë¤ê³ ë³¼ ì ìë¤. ê·¸ë°ë° ì¤ìí ê²ì ì¬ê¸´ íêµì´ë¤. íêµì ë¤ì´ë², ì¹´ì¹´ì¤ ì¬ì©ì ì를 무ìí ì ìë¤. ê·¸ë¬ê¸°ì ë¤ì´ë² ë¡ê·¸ì¸, ì¹´ì¹´ì¤ ë¡ê·¸ì¸ì ì¶ê°í´ì¤ì¼ íë¤. ê·¼ë° ì ì¬ì§ìì ë³´ë¯ì´ ë¤ì´ë²ë ì¹´ì¹´ì¤ë ëì ì»ê³ ì°¾ìë´ë ìë¤. ì´ì ë ë¤ì´ë²ì ì¹´ì¹´ì¤ì ê²½ì° OIDC 를 ì§ìíì§ ìê³ OAuth ë§ì ì§ìí기 ë문ì´ë¤. ê·¸ëì ì´ë¶ë¶ì ê°ë°ìê° ì§ì Identity Provider 를 구íí´ì OAuth ë§ì ì¬ì©íë ìì ë¡ê·¸ì¸ì ì°ê²°í´ì¤ì¼ íë¤. ë¤ì´ë² ì© Identity Provider 를 ë§ë¤ì´ë³´ì. ìë 구íì ë¤ì ë´ì©ì ì°¸ê³ íë¤. ì°¸ê³ : Baidu Identity Provider 먼ì íë¡ì í¸ êµ¬ì¡°ë ë¤ìê³¼ ê°ë¤. ì´ì ì User Storage SPI 를 구íí ë ì²ë¼ META-INF/services ìë íì¼ê³¼ Provider, ProviderFactory ê° ì¡´ì¬íë ê²ì ë³¼ ì ìë¤. ì¬ê¸°ë ì¸ë¶ DB ì ì°ê²°í´ì¤ì¼íë ë³µì¡í 구íì ì기ë문ì íì¼ë ëªê° ìëë¤. ê·¸ë¬ë©´ íëíë ë³´ì. 먼ì META-INF/services ìë íì¼ë¤ì ë³´ì. UserStorageSPI ì ë¤ë¥¸ì ì´ë¼ë©´ Mapper íì¼ì´ ì¶ê°ë¬ë¤. ì¼ë¨ ì íí 무ì¨ìí ì íëì§ ëª¨ë¥´ë Keycloak í´ëì¤ëª ê³¼ í¨í¤ì§ëª ì¼ë¡ íì¼ëª ì ìì±íê³ ë´ì©ì¼ë¡ë ê°ë°ìê° ë§ë 매í¼ì í©í 리 í´ëì¤ì í¨í¤ì§ëª ê³¼ íì¼ëª ì ì ì´ì£¼ëë¡ íì. Gradle Dependency compile 'org.keycloak:keycloak-services:10.0.2' compile 'org.keycloak:keycloak-server-spi:10.0.2' compile 'org.keycloak:keycloak-server-spi-private:10.0.2' org.keycloak.broker.provider.IdentityProvi
408derMapper moe.saru.keycloak.modules.naver.NaverUserAttributeMapper org.keycloak.broker.social.SocialIdentityProviderFactory moe.saru.keycloak.modules.naver.NaverIdentityProviderFactory NaverUserAttributeMapper import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper; /** * User attribute mapper. */ public class NaverUserAttributeMapper extends AbstractJsonUserAttributeMapper { \tprivate static final String[] cp = new String[] { NaverIdentityProviderFactory.PROVIDER_ID }; \t@Override \tpublic String[] getCompatibleProviders() { \t\treturn cp; \t} \t@Override \tpublic String getId() { \t\treturn \"naver-user-attribute-mapper\"; \t} } ì íí 매í¼ê° íë ìí ì íì¤ì¹ ìë¤. ë¤ë§ ììì»¨ë° ì¬ì©ì ìì± ë§¤í¼ë¼ë ì ⦠ìë§ Identity Provider ì ë³´ ì
ë ¥ íë©´ìì ë³´ì´ë ìì±ë¤ì 매íìì¼ì£¼ë ê² ê°ë¤. NaverIdentityProviderFactory import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig; import org.keycloak.broker.provider.AbstractIdentityProviderFactory; import org.keycloak.broker.social.SocialIdentityProviderFactory; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; public class NaverIdentityProviderFactory extends AbstractIdentityProviderFactory<NaverIdentityProvider> implements SocialIdentityProviderFactory<NaverIdentityProvider> { public static final String PROVIDER_ID = \"naver\"; @Override public String getName() { return \"naver\"; } @Override public NaverIdentityProvider create(KeycloakSession session, IdentityProviderModel model) { return new NaverIdentityProvider(session, new OAuth2IdentityProviderConfig(model)); } @Override public String getId() { return PROVIDER_ID; } @Override public OAuth2IdentityProviderConfig createConfig() { return new OAuth2IdentityProviderConfig(); } } ì¹ìí Factory í´ëì¤ë¤. ìì Id ê°ì ë£ì´ì£¼ë©°, Provider 를 ìì±í´ì¤ë¤. 주ìí ì ì ìë createConfig ê° ìì¼ë©´ ìë¬ê° ëë¤ë ì ì´ë¤. ë°ëì ìì±í´ì£¼ì ì¬ê¸°ìë OAuth2 IdentityProvider ì¤ì ì ìì±íìë¤. NaverIdentityProvider import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import java.util.Iterator; import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig; import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.broker.social.SocialIdentityProvider; import org.keycloak.events.EventBuilder; import org.keycloak.models.KeycloakSession; public class NaverIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider { \tpublic static final String AUTH_URL = \"https://nid.naver.com/oauth2.0/authorize\"; \tpublic static final String TOKEN_URL = \"https://nid.naver.com/oauth2.0/token\"; \tpublic static final String PROFILE_URL = \"https://openapi.naver.com/v1/nid/me\"; \tpublic static final String DEFAULT_SCOPE = \"basic\"; \tpublic NaverIdentityProvider(KeycloakSession session, OAuth2IdentityProviderConfig config) { \t\tsuper(session, config); \t\t \t\tconfig.setAuthorizationUrl(AUTH_URL); \t\tconfig.setTokenUrl(TOKEN_URL); \t\tconfig.setUserInfoUrl(PROFILE_URL); \t} \t@Override \tprotected boolean supportsExternalExchange() { \t\treturn true; \t} \t@Override \tprotected String getProfileEndpointForValidation(EventBuilder event) { \t\treturn PROFILE_URL; \t} \t@Override \tprotected BrokeredIdentityContext extractIdentityFromProfile(EventBuilder event, JsonNode profile) { \t\t// getJsonProperty ë Oidc ê´ë ¨ íì±ë§ ê°ë¥íë¯ë¡ JsonNode ì get ë©ìë를 ì´ì©í´ì ê°ì ¸ì¨ë¤. \t\t// BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, \"response\")); \t\tBrokeredIdentityContext user = new BrokeredIdentityContext(profile.get(\"response\").get(\"id\").asText()); \t\tString email = profile.get(\"response\").get(\"email\").asText(); \t\tuser.setIdpConfig(getConfig()); \t\tuser.setUsername(email); \t\tuser.setEmail(email); \t\tuser.setIdp(this); \t\tAbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias()); \t\treturn user; \t} \t@Override \tprotected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) { \t\ttry { \t\t\tJsonNode profile = SimpleHttp.doGet(PROFILE_URL, session).param(\"access_token\", accessToken).asJson(); \t\t\tBrokeredIdentityContext user = extractIdentityFromProfile(null, profile); \t\t\treturn user; \t\t} catch (Exception e) { \t\t\tthrow new IdentityBrokerException(\"Could not obtain user profile from naver.\", e); \t\t} \t} \t@Override \tprotected String getDefaultScopes() { \t\treturn \"\"; \t} } 먼ì ììë¡ ì ì¸ë 문ìì´ë¤ì ê°ê° ë¤ì´ë²ì ì¸ì¦ URL, í í° ë°ê¸ URL ê³¼ Profile ì ê°ì ¸ì
408¤ë URL ë¤ì ì¤ì íë¤. Scope ë ë¤ì´ë² ê°ë°ê°ì´ë를 ë³´ë©´ ë°ë¡ ì¤ì ì ìí´ì¤ë ëë¤ê³ í´ì ìì±ì íì§ë§ ìë getDefaultScopes ìì ë¹ ë¬¸ìì´ì ë°ííê² íë¤. ìì±ìììë Identity Provider ì 기본 URL ì¤ì ì í´ì£¼ìë¤. getProfileEndpointForValidation: ë¤ì´ë² Profile Endpoint 주ì ë°í extractIdentityFromProfile: ë¤ì´ë² Profile ë´ì© ë°í doGetFederatedIdentity: ì¤ì ë¡ ë¤ì´ë²ì ì¸ì¦ ìì²ì íê³ í í°ì ë°ìì¤ë ìí , í í°ì ì´ì©í´ Profile ì ê°ì ¸ì¤ë ìí ì ìííë ë©ìëì´ë¤. getDefaultScopes: ë¤ì´ë² ê°ë°ê°ì´ëìë ëììë¯ì´ scope ê°ì´ íìì기 ë문ì ì¬ê¸°ìë ë¹ ë¬¸ìì´ì ë°í User Storage SPI ì ë¹íë©´ 구íì´ ë¹êµì ê°ë¨íë¤. ì ê·¸ë¼ ì¬ê¸°ì ë? ìì íë¡ì í¸ êµ¬ì¡° 그림ì ë¤ìë³´ì. src ì ê°ì ë 벨ì resources ëë í ë¦¬ê° ìë¤. ë³´ë©´ .html íì¥ì를 ê°ì§ íì¼ ëê°ê° ìë¤. ì´ê²ì ë¤ì íë©´ì ì°¾ìê°ëë° íìí íì¼ì´ì§ ììê¹ ì¶ë¤. (ìë ë¶ì´ ìì¼ë©´ ìë ¤ì£¼ì¸ì..) 주ìí ì ì ê° html íì¼ëª ìì ë§ì§ë§ì ì¤ë naver ì´ë¶ë¶ì´ë¤. ìì Factory í´ëì¤ìì ìì±í Id ê°ì ì¬ê¸°ìë¤ ì ì´ì¤ì¼ íë¤. ì ì´ì£¼ì§ ìì ê²½ì° ì 그림ì íë©´ì¼ë¡ ì°ê²°ì´ ìëë¤. ì´ì íë¡ì í¸ë¥¼ ë¹ëí´ë³´ì. ì¬ê¸°ììë Gradle íë¡ì í¸ë¡ ê°ë°ëì기ë문ì âgradle bulidâ 를 íì. ê·¸ë¬ë©´ build ëë í 리 ìëì jar ê° ìì±ëë¤. ê·¸ë¼ ì´ì ì´ jar 를 ì´ì ì²ë¼ standalone/deployments ìëì ë£ì´ì£¼ì. ê·¸ë¦¬ê³ Keycloak ìë²ê° ì¬ ì¤í ëë©´ ëìë³´ëë¡ ì ìí´ì Identity Provider 를 ë³´ì. ë¤ì´ë²ê° ì ì¶ê°ëìë¤. 굿! ì´ì naver 를 ì íí´ë³´ì í¹ì ìë¬ê° ë°ìíê±°ë íì´ì§ ì´ëì´ ìëë¤ë©´ íë íì¸í´ì¼í ì ì´ ìë¤. ììì ë§í .html íì¼ë¤ì keycloak íë¡ì í¸ìì themes/base/admin/resources/partials/ ë°ì ë¬ë³´ì. ê·¸ë°ë¤ Keycloak ìë²ë¥¼ ì¬ììíê³ ë¤ì naver 를 ì ííë©´ ë¤ìê³¼ ê°ì íë©´ì´ ëì¨ë¤. ì¬ê¸°ì ì ë ¥í´ì¤ì¼í ê²ì ë¤ì´ë² ê°ë°ììì ë°ê¸ë°ì Client ID, Client Secret ëê°ë¿ì´ë¤. ì ë ¥ì´ ìë£ëìì¼ë©´ ë¤ì´ë² ê°ë°ìì ë±ë¡í´ì¼ ëë Callback URL ì¸ Redirect URL ì ë³µì¬í´ëê³ save 를 íì. ë§ì§ë§ì¼ë¡ ë¤ìë¡(ë¤ì´ë² ìì´ëë¡ ë¡ê·¸ì¸) ê°ë°ì íë©´ìì ì´ë°ì ë° ë±ë¡ì íê³ API ì¤ì íìì ë¤ì ëë¶ë¶ì ì ë ¥í´ì¤ì¼ íë¤. íëë ìë¹ì¤ê° ì¤íëë URL ê³¼ ë°©ê¸ ë³µì¬í Redirect URL ì ë£ì´ì¤ Callback URl ì´ë¤. Callback URL ì ë³µì¬í Redirect URL ì ë£ì´ì£¼ê³ ìë¹ì¤ URL ë ë§ì ì±ì주ì. í ì¤í¸ë¥¼ ìí´ ë¤ì URL ë¡ ì ìì íì http://localhost:8080/auth/realms/{realm}/account/ ê·¸ë¬ë©´ ë¤ìê³¼ ê°ì ë¡ê·¸ì¸ íë©´ì´ ëì¤ê³ ë¤ì´ë²ë¥¼ ì íí´ ë¤ì´ë² ë¡ê·¸ì¸ì ì§ííë©´ ëë¤. ë§ì¹ë©° OIDC ì OAuth ì ì°¨ì´ì ì ê³µë¶í ë´ì©ë ì¬ë ¤ì¼ê² ë¤. ì ì OIDC ë¡ ë³íë ì¶ì¸ì¬ì ê·¸ë°ì§ ì¤íìì¤ë¤ë ììë¤ì´ ë°ìì íëê°ë³´ë¤. ","categories": ["posts"], 409 "tags": ["msa","keycloak"], 410 "url": "https://subji.github.io/posts/2020/07/24/keycloak4", 411 "teaser":null},{ 412 "title": "JPA 무í ìí참조 Stack over flow ì¤ë¥", 413 "excerpt":"Json ìí참조 JPA 를 ì¬ì©íë©´ì Team -> Member ì ê°ì´ OneToMany -> ManyToOne ê³¼ ê°ì ìë°©í¥ ê´ê³ë¥¼ ê°ì§ë 구조ìì ë§ì´ ë°ìíë Json ìí참조ì ëí´ì ììë³´ì. Json ìí참조ë ìë±¡í¥ ê´ê³ìì ê°ì²´ë¥¼ ì§ë ¬í íë ê³¼ì ìì ì쪽 모ë ì§ë ¬í íë ¤í ë 무íì°¸ì¡°ê° ëì´ StackOverFlow 를 ë°ììí¨ë¤. 무ì¬ì½ Lombok ì ì¬ì©íë©° @Data 를 ì¬ì©íê±°ë @ToString ë° hashcode&equals ì´ë ¸í ì´ì ì ì¬ì©íë©´ì ë°ìí기ë íë¤. ToString ì´ë HashCode&Equals ìì ì ê°ì²´ê° ìë¡ì íë¡í¼í°ë¥¼ ê³ìí´ì 참조íë©´ì 무í ìíì°¸ì¡°ê° ë°ìíë¤. ì¦, ê°ê¸ì ì´ë©´ Entity ìì @Data, @ToString ë±ì ì´ë ¸í ì´ì ì¬ì©ì ì§ìíì. ë¤ìì ëªê°ì§ ìí참조 í´ê²°ë°©ë²ì´ë¤. @JsonIgnore ToString Override @JsonManagedReference, @JsonBackReference @JsonIdentityInfo 첫ë²
413째 @JsonIgnore ì ê²½ì° í´ë¹ ì´ë ¸í ì´ì ì´ ì ì©ë ë¶ë¶ì Json ì§ë ¬íìì ìì ì ì¸ìí¤ë¯ë¡ ì¢ì ë°©ë²ì´ ìëë¤. ëë²ì§¸ ìì ë§¤ë² ìë¡ Override 를 í´ì¤ì¼ í기 ë문ì ìì°ì± 측면ìì ì¢ì ë°©ë²ì´ ìëë¤. ê·¸ë¬ë©´ ë¨ì ë°©ë²ì 3, 4ë²ì§¸ë¤. 3 ë²ì§¸ë Jackson ë¼ì´ë¸ë¬ë¦¬ 1.x ë²ì ìì 4 ë²ì§¸ë 2.x ë²ì ì´ììì ì¬ì©ê°ë¥íë¤. 3ë²ì§¸ì ê²½ì° ì를 ë¤ì´ë³´ê² ë¤. ë¤ì ììì Entity ëê°ë¥¼ ë³´ì. @Entity public class User implements Serializable{ @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private long id; @Column(name=\"name\") private String name; @ManyToMany @JoinTable(name=\"users_roles\",joinColumns=@JoinColumn(name = \"user_fk\"), inverseJoinColumns=@JoinColumn(name = \"role_fk\")) @JsonManagedReference private Set<Role> roles = new HashSet<Role>(); ... @Entity public class Role implements Serializable { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int id; @ManyToMany(mappedBy=\"roles\") @JsonBackReference private Set<User> users = new HashSet<User>(); ... ì¬ë¬ëª ì User ê° ì¬ë¬ê°ì Role ì ê°ë ìë°©í¥ ë¤ëë¤ êµ¬ì¡°ì´ë¤. ìë°©í¥ êµ¬ì¡°ì´ê¸° ë문ì 무í ìíì°¸ì¡°ê° ë°ìí ê²ì´ë¤. User ì ìë *Set roles = new HashSet();* ë¶ë¶ì´ Jackson ìì ê°ì ¸ì¤ë **Forward part of Reference**. ì¦, Json íìì¼ë¡ ë³ííë ë¶ë¶ì´ë¤. ì´ë ê² ììì 참조íë ë¶ë¶ì ê°ì ¸ì Json ì¼ë¡ ë³ííë ê²ì ë§ì¬ë§ íë¡ì¸ì¤ë¼ê³ íë¤. ê·¸ë¦¬ê³ Role Entity ìì *Set users = new HashSet();* ë¶ë¶ì **Backward part of Reference** ë¼ê³ ë¤ìì 참조ëë ë¶ë¶ì´ë¤. Jackson ì´ ë¶ë¶ì ì§ë ¬ííì§ ìê³ ê·¸ë¥ ëì´ì¼ 무í ìíì°¸ì¡°ê° ë°ìíì§ ìê² ëë¤. ê·¸ëì ì£¼ì¸ Entity ì¸ User, JoinColumn ì´ ì ì¸ë ê³³ììë Json 참조를 ë°ììí¤ê³ , ìì Entity ì¸ Role, MappedBy ê° ì ì¸ë ê³³ììë Json ì§ë ¬í를 ë°©ì§íê² ëë¤. ë§ì§ë§ì¼ë¡ @JsonIdentityInfo ì ëí´ì ê°ë¨í ì¬ì©ë°©ë²ì ìê°íê³ ë§ì¹ê² ë¤. @Entity @JsonIdentityInfo(generator = IntSequenceGenerator::class, property = \"id\") public class User implements Serializable{ @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private long id; @Column(name=\"name\") private String name; @ManyToMany @JoinTable(name=\"users_roles\",joinColumns=@JoinColumn(name = \"user_fk\"), inverseJoinColumns=@JoinColumn(name = \"role_fk\")) private Set<Role> roles = new HashSet<Role>(); ... @Entity @JsonIdentityInfo(generator = IntSequenceGenerator::class, property = \"id\") public class Role implements Serializable { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int id; @ManyToMany(mappedBy=\"roles\") private Set<User> users = new HashSet<User>(); ... ì¬ì©ë²ì ê°ë¨íë¤. ê° Entity ì @JsonIdentityInfo(generator = IntSequenceGenerator::class, property = âidâ) 를 ì¶ê°íë©´ëë¤. generator ìë ì ìí ID 를 ì¤ì íë IntSequenceGenerator ì 문ìì´ ID 를 ì¤ì íë StringGenerator ì UUID ííë¡ ì¤ì íë UUIDGenerator ë±ì´ ìë¤. ëí property ìì±ì ë³ë¤ë¥¸ ê°ì ì íì§ ìì¼ë©´ ì§ë ¬í í ë @id ë¼ë ìì±ì´ë¦ì¼ë¡ ê°ì²´ì Id ê° ì¤ì ëë¤. ë§ì¹ë©° 차근차근 ê³µë¶íë©° ì¬ì©íë JPA ì¸ë° ì´ë ¤ìì´ ë§ë¤. ê·¸ëë ë§ì´ ì¬ì©íê³ ë¤ ìì´ìì¸ì§ ê³µë¶í ìë£ë ë§ê³ 문ì í´ê²° ìë£ë ë§ì ëìì´ ë§ì´ ëë¤. ì°¸ê³ Json and Jackson Stackoverflow 문ì Infinite Recursion with Jackson JSON and Hibernate JPA issue ","categories": ["posts"], 414 "tags": ["jpa"], 415 "url": "https://subji.github.io/posts/2020/08/06/infiniterecusionofjpa", 416 "teaser":null},{ 417 "title": "Java RestTemplate File Upload", 418 "excerpt":"How to RestTemplate 를 ì´ì©í´ì íì¼ì ì ì¡íê³ ì í ë ì¼ë°ì ì¸ ë°©ë²ì¼ë¡ ì ì¡íê² ëë©´ Jackson ë¼ì´ë¸ë¬ë¦¬ìì ì§ë ¬íì ì¤í¨íë¤ê³ ìë¬ ë©ì¸ì§ë¥¼ ë³´ë¸ë¤. ê·¸ë¬ë©´ ì´ë»ê² í ì ììê¹? íì¼ ì체를 ë°ì´í¸ë¡ ë³ê²½í´ì ì´ê²ì Multivaluemap ì ë´ì ë³´ë´ë ë°©ë²ì´ ìë¤. ì½ëë ë¤ìê³¼ ê°ë¤. HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); MultiValueMap<String, Object> body = new LinkedMultiValueMap<String, Object>(); for (MultipartFile file : files) { if (!file.isEmpty()) { body.add(\"file\", new ByteArrayRes
418ource(file.getBytes())); } } HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<MultiValueMap<String, Object>>(body, headers); ResponseEntity<Map<String, Object>> result = restTemplate.exchange( \"{api uri}\", HttpMethod.POST, entity, new ParameterizedTypeReference<Map<String, Object>>() {}); } ì ì½ëëë¡ ë³´ë´ë©´ Jackson FormHttpMessageConverter ê´ë ¨ ìë¬ê° ë°ìíë¤. ëªìê°ì ë¬´ì¨ ìë¬ì¸ì§ í¤ë§¤ê³ ììë¤. ìì¸í ìë¬ ì¤íí¸ë ì´ì¤ë¥¼ ë³´ë ë¬´ì¨ ì»¨ë²í°ê° ì꾸 ìë¤ê³ íë¤. 컨ë²í°ê° ìë¤ëê² ë¬´ì¨ë»ì´ì§.. íê³ ìê°íë¤ê° ì? RestTemplate ì 컨ë²í°ë¥¼ ì¤ì í ë¶ë¶ì´ ììë¤ëê² ìê°ë¬ë¤. ê·¸ëì ë¤ì 구ê¸ë§ì íë ë¤ìê³¼ ê°ì´ File ì ë³´ë´ê³ ì í ëìë Resource ê´ë ¨ 컨ë²í°ë¥¼ ì¶ê°íë¼ë ë§ì´ ììë¤. HttpMessageConverter<Object> jackson = new MappingJackson2HttpMessageConverter(); HttpMessageConverter<Resource> resource = new ResourceHttpMessageConverter(); FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter(); formHttpMessageConverter.addPartConverter(jackson); formHttpMessageConverter.addPartConverter(resource); RestTemplate restTemplate = new RestTemplate(Arrays.asList(jackson, resource, formHttpMessageConverter)); ì´ ì½ë를 ì¶ê°íëê¹ ë³´ë´ëê±´ ìë³´ë´ì§ë¤. íì§ë§ ë°ìë ìë¬ê° ë°ì! ë ì´ê±°ë ì´ë µì§ ìê² í´ê²°í ì ììë¤. ë©ì¸ì§ë¥¼ ë³´ë¼ë íìí 컨ë²í°ê° ìë¤ë©´ ë°ìëë ë´ì©ì ê°ì ¸ì¬ 컨ë²í°ê° ì¤ì ì´ ëì´ ìê±°ë í´ì¼í ê²ì´ë¤. ê·¸ëì ë°ë API ìë ì ì¤ì ì ì¶ê°í´ì£¼ë ì ëìë¤. ì¶ê°ë¡ new ByteArrayResource 를 ì¬ì©íëë°, ì¤íë§ 5.1 ë²ì ì´íììë file.getResource() 를 ì¬ì©íì¬ ë¦¬ìì¤ë¥¼ ë³´ë¼ì ìë¤ê³ íë¤. ìµì¢ ìì ì½ë HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); MultiValueMap<String, Object> body = new LinkedMultiValueMap<String, Object>(); for (MultipartFile file : files) { if (!file.isEmpty()) { body.add(\"file\", file.getResource()); } } HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<MultiValueMap<String, Object>>(body, headers); ResponseEntity<Map<String, Object>> result = restTemplate.exchange( \"{api uri}\", HttpMethod.POST, entity, new ParameterizedTypeReference<Map<String, Object>>() {}); } ì°¸ê³ How to send Multipart form data with restTemplate Spring-mvc Resttemplate form/multipart: image + JSON in POST ","categories": ["posts"], 419 "tags": ["java"], 420 "url": "https://subji.github.io/posts/2020/08/10/fileuploadusingresttemplate", 421 "teaser":null},{ 422 "title": "Eureka self-preservation", 423 "excerpt":"Eureka Eureka ë CAP ì 리 측면ìì ë³¼ ì ìë AP ìì¤í ì´ë¤. ì¬ë¬ê°ì ë¤í¸ìí¬ íí°ì ìë¤ê³ íìë ê°ê°ì íí°ì ë¤ì ë ì§ì¤í¸ë¦¬ ê°ì ì¼ì¹íì§ ìê² ëë¤. Eureka ì Self-preservation 기ë¥ì ì´ë¬í ë¶ì¼ì¹ë¥¼ ìµìí í´ì£¼ë í¹ì§ì´ë¤. Eureka Self-Preservation ? Self Preservation ì Eureka ìë²ìì í¹ì ìê°ì´ë íìë±ì ê°ì ì´ê³¼íë heartbeat 를 ìì íì§ ëª»í ë Eureka ë ì§ì¤í¸ë¦¬ìì í´ë¹ ì¸ì¤í´ì¤ì ë§ë£ë¥¼ ì¤ì§íë 기ë¥ì´ë¤. heartbeat ë ê°ê°ì Eureka í´ë¼ì´ì¸í¸ë¡ ì´ë í ìíê°ì´ë í¹ì ê°ì ë°ì í´ë¹ í´ë¼ì´ì¸í¸ì ìí를 ì ì ìë ì ë³´ì´ë¤. Eureka Self-Preservation Flow Eureka Server 1, Server 2 ê° ì¡´ì¬ Eureka Client Intance 1, 2, 3, 4, 5 => Eureka Server 1 ì ë±ë¡ Eureka Server 1 -> Server 2 ë¡ ë±ë¡ ë³µì¬ ê°ê°ì ìë² ë ì§ì¤í¸ë¦¬ì Instance 1, 2, 3, 4, 5 ê° ì¡´ì¬ Eureka Client Instance 2 ë Instance 4 ì ì°ê²° ì¤ì´ë¤. Eureka Client Instance 4, 5 ê° ë¤í¸ìí¬ íí°ì ì¼ë¡ ë¶ë¦¬ Eureka Server ë Instance 4, 5 ì Heartbeat ê° ëì´ì ìì ëì§ ìëê²ì íì¸íê³ ë ì§ì¤í¸ë¦¬ìì Instance 4, 5 ì ê±° Eureka Server ìì ê°ì기 15% ì´ìì Instance ê° ì¢ ë£ë¨ì ìê³ Self-Preservation 모ë ì¤í ì´ì ë¶í° Eureka Server ë ëë¨¸ì§ 1, 2, 3 Instance ê° ì¢ ë£ëëë¼ë ë ì§ì¤í¸ë¦¬ìì ë§ë£ë ê²½ì°ìë§ Instance 를 ì¤ì§ ì¦, Instance 3 ì ì¢ ë£ìí¤ëë¼ë ìë² ë ì§ì¤í¸ë¦¬ìë íì± ìíë¡ ë¨ììë¤. ëí Instance 3 ì리ì ìë¡ì´ Instance ë±ë¡ì íì©íë¤. Why Self-Preservation ? Self-Preservation 모ëì íì±í ì´ì ? í´ë¼ì´ì¸í¸ì ì¢ ë£ê° ìë ì못ë ë¤í¸ìí¬ íí°ì ë¶ë¦¬ë¡ ì¸í heartbeat ê° ìì ì´ ìëë íìì¼ ì ìë¤. ì´ ê²½ì° ì못ë ë¤í¸ìí¬ íí°ì ì ìì í ê²½ì° ìëëë¡ ë±ë¡ì´ ê°ë¥íë¯ë¡ 무조건ì ì¼ë¡ ë ì§ì¤í¸ë¦¬ìì ì ê±°íë ê²ì ì³ì§ ìì ì ìë¤. ì¼ë¶ Instance ì ì°ê²°ì´ ëì´ì§ëë¼ë ë ë¤ë¥¸ Instance ì ì°ê²°ì´ ìì ê²½ì° ê°ì ë¡ ë ì§ì¤í¸ë¦¬ìì ì ê±°í ë ì¤ë¥ê° ë°ìí ì ìë¤. ë°ë¼ì í¹ì ìê° ëë íì ëì ë ì§ì¤í¸ë¦¬ì íì± ìíë¡ ëë ê²ì´ ë«ë¤. About Self-Preservation configuration eureka.instance.lease-renewal-interval-in-seconds = 30 Eureka Client ê° ì¬ì í íì± ìíìì ëíë´ê¸° ìí´ Eureka Server ì heartbeat 를 ë³´ë´ë ë¹ë를 ëíë´ë©° 기본ê°ì 30ì´ì´ë¤. ì¦, 30ì´ ë§ë¤ íë²ì© heartbeat 를 ìì íê² ëë¤. ê°ê¸ì 기본ê°ì¸ 30ì´ ì¬ì©ì ê¶ì¥íë¤. eureka.instance.lease-expiration-duration-in-seconds = 90 Server ë ì§ì¤í¸ë¦¬ìì Client Instance 를 ì ê±°í기 ì ì ë§ì§ë§ heartbeat 를 ë°ì í ìë²ê° ë기íë ìê°ì ëíë¸ë¤. ì´ê°ì ìì ë§í lease-renewal-interval-in-seconds ë³´ë¤ ì»¤ì¼íë¤. ì´ ê°ì ë무 ê¸¸ê² ì¤ì íë©´ ë ì§ì¤í¸ë¦¬ì íì± ìíê° ì¤ì ê°ì ë°ë¼ ë¬ë¼ì§ë¯ë¡ ì´ì ì ì¤ëª í lease-renewal-interval-in-seconds ì ë
423¶ë¹ ì¤ì heartbeat ê³ì°ì ì ë°ëì ìí¥ì ì£¼ê² ëë¤ê³ íë¤. ëí ë무 ìê² ì¤ì íë©´ ìì¤í ì´ ì¼ìì ì¸ ë¤í¸ìí¬ ê²°í¨ì 견ë ì ìê² ëë¤ê³ íë¤. eureka.server.eviction-interval-timer-in-ms = 60 * 1000 ì´ ì¤ì ì Instance ê° ì¢ ë£ëê³ ë§ë£ê° ëììë ë ì§ì¤í¸ë¦¬ìì Instance 를 ì ê±°íë ì¤ì¼ì¥´ë¬ê° ì´ ë¹ëë¡ ì¤íëë¤. lease-expiration-duration-in-seconds ì ê°ì ë무 ê¸¸ê² ì¤ì íë©´ ìì¤í ì´ ìì²´ Self-Preservation ì¼ë¡ ë¤ì´ê°ë ê²ì´ ì§ì°ëë¤. eureka.server.renewal-percent-threshold = 0.85 ì´ ê°ì ë¶ë¹ ìì heartbeat 를 ê³ì°íë ë° ì¬ì©ëë¤. ì´ê°ì ê³ì°ì Eureka ëìë³´ë를 ë³´ë©´ ì ì ìëë°, Renews Threshold ì Renews (last min) ë¶ë¶ì ë³´ë©´ ì ì ìë¤. 첫ë²ì§¸ë ê°±ì 기ì¤ì´ ëë ìê³ê°ì´ë©°, ëë²ì§¸ë ë§ì§ë§ ê°±ì ë ê°ì´ë¤. ì¦ âEMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEYâRE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.â ì ê°ì ìë¬ê° ëë©´ì Instance ê° ì¢ ë£ëì§ ìëë¤ë©´ Renews Threshold ê°ì´ Renews (last min) ë³´ë¤ í´ ê²½ì°ì ë°ìíë ìë¬ì´ë¤. ìê³ ê°ì´ ë í´ ê²½ì° Self-Preservation 모ëê° ì¤íëë©° ë§ë£ë Instance ê° ì¢ ë£ëì§ ì기 ë문ì´ë¤. ê³ì° ë°©ë²ì ë¤ìê³¼ ê°ë¤. Renews Threshold => ë¶ë¹ heartbeat ì (기본ê°ì¸ 30ì´ì íë²ì¼ë¡ ê°ì ) * Instance ì + Spring ìì 구ì±íë ìµì Instance ì (1) * renewal-percent-threshold ê° ê³ì° í ë°ì¬ë¦¼ì íë¤. ex: 2 * 1 + 1 * 0.85 = 3 Renews (last min) => ë¶ë¹ heartbeat ì (기본ê°ì¸ 30ì´ì íë²ì¼ë¡ ê°ì ) * Instance ì ex: 2 * 1 = 2 ì¦, í ì¤í¸ íê²½ì´ë ë³ëì ì´ìíê²½ 구ì±ìì ììë§í ê²½ê³ ë¥¼ ìì ê³ ì í ë í¼ì¼í¸ ê°ì ë®ì¶ë©´ ëê² ë¤. eureka.server.renewal-threshold-update-interval-ms = 15 * 60 * 1000 ë ì§ì¤í¸ë¦¬ìì Instance 를 ì ê±°íë ì¤ì¼ì¤ë¬ë ë¶ë¹ ìì heartbaet 를 ê³ì°íë ê²ì´ ì¬ê¸°ì ì¤ì ë ë¹ë ê° ë§í¼ ì¤íëë¤. eureka.server.enable-self-preservation = true Self-Preservation 모ë를 ì¼ê³ ë ì ìë ì¤ì ì´ë¤. ê°ê¸ì ì´ë©´ ì´ì ìí©ììë ëì§ ìë ê²ì ê¶ì¥íë¤. ì°¸ê³ The Mystery of Eureka Self-Preservation ","categories": ["posts"], 424 "tags": ["msa","eureka"], 425 "url": "https://subji.github.io/posts/2020/08/11/springcloudeurekaregistry", 426 "teaser":null},{ 427 "title": "java.sql.SQLException: Field 'id' doesn't have a default value 문ì í´ê²°", 428 "excerpt":"Problem ë¶ëª ì°ê´ê´ê³ 매íë ì ëë¡íê³ í ì´ë¸ìë Not Null ë¡ ì¤ì ì í´ëìë¤. ê·¸ë°ë° ì꾸 âdoesnât have a default valueâ 문ì ê° ë°ìíë¤. How to solve? ì무리 ì°¾ìë ì½ëììë ì¤ë¥ê° ììë¤. ê·¸ë ê² ëë ìë¤ê° ì¤íì¤ë²íë¡ì°ìì íì¤ê¸° ë¹ì ì°¾ê² ëìë¤. Hibernate: âField âidâ doesnât have a default valueâ ì¬ê¸°ìì 100 í를 ëê² ë°ì ëµë³ì ë³´ë©´ ì ì ìë¤. âê²½ì°ì ë°ë¼ì 모ë¸ì´ë ORMì ë°ì´í°ë² ì´ì¤ì ë³ê²½ì¬íì´ ì ííê² ë°ìëì§ ìì ì ìë¤. (SchemeUpdate), ì½ëìì ì¤ë¥ë ì íí íì í ì ìë ì¤ë¥ê° ìëê³ ìì¸ì ì ì ìë ì¤ë¥ì ê²½ì°ìë ë°ì´í°ë² ì´ì¤ ëë í ì´ë¸ì ë¤ì ë§ë¤ê³ ì¬ì¤íì íëê²ì ê¶íë¤. (SchemeExport)â ê·¸ë ë¤. ëì ê²½ì°ìë ì íí ìì¸ì ëë¼ê³ ë§í ì ììë¿ëë¼ ì°¾ì ìë ìì기ì ì ëµë³ì´ ë§ëë§ì´ìë¤. ê·¸ëì ì¤ë¥ê° ëë 컬ë¼ì´ í¬í¨ë í ì´ë¸ì ëëíê³ ë¤ì ì¶ê°íìë¤. ìë²ë¥¼ ì¬ì¤ííë ìëë¤.. Why? ê·¸ë ë¤ë©´ ëê° ë¬¸ì ì¼ê¹ ëëì²´? ëë¶ë¶ì ê¸ìì Hibernate ìì ë°ìíë ì¤ë¥ë¼ê¸°ë³´ë¨ í¹ì§? ì´ë¼ê³ íë¤. hibernate ë DB를 ì¶ìííë ë구ë¡ì¨ í ì´ë¸ì´ë 컬ë¼ì ëí´ì ì주 ì íí 매íì í
428ì§ ìëë¤ê³ íë¤. ê·¸ëì ë°ì´í° íì ì´ ë¤ë¥¸ë°ë 매íìí¤ê±°ë íë¤ê³ íë¤. ê·¸ëì ì¤ê°ì í ì´ë¸ì´ë 컬ë¼ì ì¤ì ì ë³ê²½íë©´ ë°ìì´ ìëë ê²ì¼ ìë ìë¤ê³ ìê°íë¤. ì¶ê°ë¡ í ì´ë¸ ëë 컬ë¼ì ë³ê²½ì¬íì ì¢ ë ì ì ì©ëê² íë ¤ë©´ hbm2ddl.auto ìµì ìì update 를 ì¬ì©íë©´ ëë¤ê³ íë¤. íì§ë§ update ìµì ìë hibernate ì ëì¨í 매í í¹ì§ì ìì í ì²ë¦¬ê° ìëëê²ê°ë¤. hbm2ddl.auto ì ìµì ì ë¤ìê³¼ ê°ë¤. create: SessionFactory ììì í ì´ë¸ ìì íê³ ë¤ì ìì± create-drop: create + SessionFactory ì¢ ë£ì í ì´ë¸ ìì update: SessionFactory ììì ì»¬ë¼ ì¶ê°/ìì ìí. ë°ì´í° ìì ìí¨ validate: SessionFactory ììì í ì´ë¸ ì»¬ë¼ íì¸íì¬ ë¤ë¥´ë©´ ìì¸ ë°ì ì°¸ê³ íì´ë²ë¤ì´í¸ì hbm2ddl.autoì updateê° ì¢ìê¹? validateê° ì¢ìê¹? ","categories": ["posts"], 429 "tags": ["jpa","hibernate"], 430 "url": "https://subji.github.io/posts/2020/08/20/jpadoesnthaveadefaultvalue", 431 "teaser":null},{ 432 "title": "MapStruct Dependency ì¶ê° ì ë¹ë ì¤ë¥ 문ì ", 433 "excerpt":"Problem MapStruct 를 ì¬ì©í기 ìí´ ìì¡´ì±ì ì¶ê°íê³ mvn clean install ì ì¤ííëë°, âcannot find symbolâ ì¤ë¥ë¥¼ ë§êµ¬ ë±ì´ëë¤. How to solve? 먼ì MapStruct 를 ì¬ì©í기 ìí´ìë pom.xml ì ë¤ìê³¼ ê°ì´ ì¶ê°í´ì¤ì¼íë¤. <properties> <org.mapstruct.version>1.3.1.Final</org.mapstruct.version> <org.projectlombok.version>1.18.12</org.projectlombok.version> </properties> <dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${org.projectlombok.version}</version> <scope>provided</scope> </dependency> <!-- ... --> </dependencies> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <!-- mapstruct 를 ì¬ì©í ë lombok ê³¼ ì¶©ëì´ ë°ìíë¤. ê·¸ëì lombok ì ëí path ë ì¶ê°í´ì¤ë¤. --> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> ì¬ê¸°ì ë¹ë를 íë©´ ìì ë§í ì¤ë¥ê° ë§êµ¬ ë°ìíë¤. ìì¸ì ì°¾ìë³´ë ëêµ°ê°ê° ì¹ì íê² ì¤ëª í´ì¤¬ë¤. ëµì ê°ë¨íë¤. mapstruct-processor dependency ê° ì¡°ê¸ì ë¤ë¥¸ ë°©ìì¼ë¡ í¸ì¶ ë기ì annotaionProcessorPaths ì ìì±íê² ëë¤. ì´ë, ë§ì½ lombok ì ê°ì´ ì¬ì©íê³ ìë¤ë©´, ì¶©ëì´ ë°ìíë¤ê³ íë¤. ì íí ì´ì ë ë ì°¾ìë´ì¼ê² ë¤. ë°©ë²ì ëê°ì§ë¤. lombok dependency ë annotationProcessorPaths ì ì¶ê°í´ì¤ë¤. scope 를 provided ë¡ ìì±íë ë°©ë²ì´ë¤. Lombok/Mapstruct problem: Cannot find symbol ì´ë¥¼ í ëë¡ ë¤ì pom.xml ì ìì±í´ë³´ì. ìëë 1ë² ë°©ë²ì ì ì©í ì½ëì´ë¤. <properties> <org.mapstruct.version>1.3.1.Final</org.mapstruct.version> <org.projectlombok.version>1.18.12</org.projectlombok.version> </properties> <dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${org.projectlombok.version}</version> <scope>provided</scope> </dependency> <!-- ... --> </dependencies> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <!-- mapstruct 를 ì¬ì©í ë lombok ê³¼ ì¶©ëì´ ë°ìíë¤. ê·¸ëì lombok ì ëí path ë ì¶ê°í´ì¤ë¤. --> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </path> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${org.projectlombok.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> ë ììë³´ë mapstruct ê³µì github ìë ì°¸ê³ í ë§í ì½ëê° ëªê° ì¬ë¼ìììë¤. ì°¸ê³ mapstruct/mapstruct-examples ","categories": ["posts"], 434 "tags": ["lombok","mapstruct","maven"], 435 "url": "https://subji.github.io/posts/2020/08/20/mapstructandlombokcrash", 436 "teaser":null},{ 437 "title": "ë¡ê·¸ì¸ ë¤ì ìíê³ ì¬ì©ì ê¶í ì ë°ì´í¸ & ë³ê²½í기", 438 "excerpt":"Problem íìê°ì , 멤ë²ì 구매ë±ì ë¡ì§ì ê°ë°íë©´ì 구매 ìë£ìì ìì ì¬ì©ìì ê¶íì ë³ê²½ ëë ì¶ê°ë¥¼ í´ì¤ì¼íë¤. ë¨ìí ë°©ë²ì¼ë¡ë ê°ì ë° êµ¬ë§¤ ìë£ í ë¤ì ë¡ê·¸ì¸ íë©´ì¼ë¡ ì®ê¸°ë ë°©ë²ì´ ìë¤. íì§ë§ ì´ ë°©ë²ì ì¬ì©ìë¡ íì¬ê¸ ë¶í¸í? ê³¼ì ì¼ ê²ì´ë¤. ê·¸ëì ì°¾ìë³´ë ë¡ê·¸ìì - ë¡ê·¸ì¸ ìì´ Spring ì Security ì ìë SecurityContext ì ê°ì ë³ê²½í´ì£¼ë©´ ëë ë°©ë²ì´ ììë¤. ë¤ìì ê°ë¨í ì¬ì©ë°©ë²ì ìì´ë¤. How to solve? Authentication auth = SecurityContextHolder.getContext().getAuthentication(); List<GrantedAuthority> updatedAuthorities = new ArrayList<>(auth.getAuthorities()); updatedAuthorities.add(...); //add your role here [e.g., new SimpleGrantedAuthority(\"ROLE_NEW_ROLE\")] Authentication newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), updatedAuthorities); SecurityContextHolder.getContext().setAuthentication(newAuth); ëë Keycloak ì ì¬ì©íê³ ìì´ì Keycloak ë²ì ë ì¬ë ¤ë³¸ë¤. List<GrantedAuthority> newRoles = new ArrayList<>(); if (result.getStatusCode().is2xxSuccessful()) { newRoles = result.getBody().stream().map( res -> { // {keycloak auth url}/auth/admin/realms/{realm name}/users/{user id(uuid)}/role-mappings/clients/{clientid{uuid}, containerid}; // ìì² í ë°ìì¨ ê°ìì Security ì 기본 ì ëì¬ì¸ ROLE_ ì´ ìëµëì´ ìì¼ë¯ë¡ ì¶ê°í´ì£¼ì´ì¼ íë¤. // ì¶ê°íì§ ìì¼ë©´ Security ìì ìí í ê²ì¬ê° ëì§ ìëë¤. String role = \"ROLE_\" + ((String) res.get(\"name\")); return new KeycloakRole(role); } ) .collect(Collectors.toList()); } Authentication auth = SecurityContextHolder.getContext().getAuthentication(); KeycloakAuthenticationToken existToken = (KeycloakAuthenticationToken) auth; List<GrantedAuthority> oldRoles = auth.getAuthorities().stream().map(authority -> new KeycloakRole(authority.getAuthority())).collect(Collectors.toList()); newRoles.addAll(oldRoles); Authentication newAuth = new KeycloakAuthenticationToken(existToken.getAccount(), existToken.isInteractive(), newRoles); SecurityContextHolder.getContext().setAuthentication(newAuth); ì°¸ê³ How to reload authorities on user update with Spring Security ","categories": ["posts"], 439 "tags": ["spring security","spring"], 440 "url": "https://subji.github.io/posts/2020/09/03/updaterealtimeroleinsecuritywithoutlogin", 441 "teaser":null},{ 442 "title": "ì´í´ë¦½ì¤ íë¡ì í¸ë¥¼ VSCODE ì Import í ë ì´ì", 443 "excerpt":"Problem ì´í´ë¦½ì¤ìì ë§ë ì¤íë§ ë¶í¸ íë¡ì í¸ë¥¼ vscode ìì ì¬ì©í ë classpath 를 못찾ëë¤ë ì
443¤ë¥ê° ëì ì¤íì´ ìëë¤. How to solve? ~ mvn eclipse:eclipse ìì ëª ë ¹ì´ë¥¼ ì¬ì©íë©´ ì´í´ë¦½ì¤ íë¡ì í¸ë¡ ë³íì´ ëë©° classpath íì¼ì´ ì¶ê°ëë¤. ì°¸ê³ Visual Studio Code - Java Classpath is incomplete. Only syntax errors will be reported ","categories": ["posts"], 444 "tags": ["vscode","eclipse"], 445 "url": "https://subji.github.io/posts/2020/09/06/vscodeimporteclipse", 446 "teaser":null},{ 447 "title": "CentOS ìì Redis ì¬ë¬ê° ì¤íí기", 448 "excerpt":"Topic master-slave-sentinal êµ¬ì¡°ë¡ Redis 를 구ì±í기 ìí´ì ì¬ë¬ ë°©ë²ì ìê°í´ë´¤ë¤. 첫째ë íëì ìë²ì ì¬ë¬ê°ì slave ìë²ë¥¼ ëì°ë ë°©ë². ëë²ì§¸ë ì¬ë¬ê°ì ìë²ì ê°ê° íëì© slave 를 ëì°ë ë°©ë². ê°ê°ì ì¥ë¨ì ì ìë¤. 첫ë²ì§¸ë ì¬ë¬ ìë²ë¥¼ íì¸íì§ ììë ëë ê´ë¦¬íë ê´ì ìì ì½ë¤ë ì´ì ì´ ìë¤. ëë²ì§¸ë ê·¸ëë 첫ë²ì§¸ë³´ë¤ë ë ëì ìì ì±? ì´ ìì ì ìê² ë¤. Redis 를 ì¬ë¬ê°ë¡ 구ì±íë ì´ì ì ë°ë¼ ëëê² ì§ë§, ëë ì¬ê¸°ì Redis 를 ë¨ìí ì¸ì í´ë¬ì¤í°ë§ì¼ë¡ ì¬ì©íê¸°ë¡ í기 ë문ì íëì ìë²ì ì¬ë¬ê°ì Redis 를 ëì°ê¸°ë¡ íë¤. How to solve? 1. redis rdb íì¼ì´ ìì±ëë ëë í 리 ìì± rdb íì¼ ê²½ë¡ì ê²½ì° Redis ìì ìëì¼ë¡ ìì±í´ì£¼ì§ ì기 ë문ì ì§ì ë§ë¤ì´ ì£¼ê³ ì¬ì©ì, 그룹 ê¶í(redis) ë 주ì´ì¼ íë¤. ë¤ìì redis2 ë¼ë ìë¡ì´ ìë²ë¥¼ ìì±íë ê²½ì°ì ìì ì´ë¤. mkdir -p /var/lib/redis2/ chown redis /var/lib/redis2/ chgrp redis /var/lib/redis2/ # ëë chown redis:redis /var/lib/redis2/ 2. ì¤ì íì¼ ì¶ê° 기본ì¼ë¡ ì¤ì¹ë ì¤ì íì¼ì ë³µì¬í´ ëªê°ì§ë§ ìì íë¤. slaveof ìì master ì host ì port 를 ì ëë¤. masterauth ì requirepass ì master ìì ì¤ì í í¨ì¤ìë를 ì ëë¤. ë머ì§ë 구ê¸ë§ì¼ë¡ íìí ë´ì©ì ìì±íë¤. ì¬ì©ì ê¶íì ë¶ì¬íë¤. ê¶íì ë¶ì¬íì§ ìì¼ë©´ ì¤í ì¤í¬ë¦½í¸ìì ì¤ë¥ê° ë°ìíë¤. cp /etc/redis.conf /etc/redis2.conf chown redis /etc/redis2.conf slaveof 127.0.0.1 6379 masterauth {password} requirepass {password} --- redis2.conf 3.ìë¹ì¤ ì¤í¬ë¦½í¸ ìì± ìë¹ì¤ ì¤í¬ë¦½í¸ì ì¤í ìì, ì¢ ë£ ë¶ë¶ì ë³ê²½íë¤. daemonize no 를 ì¶ê°íì§ ìì¼ë©´ ë°ì´í° ì½ì ì ì¶©ëì´ ë°ìíë¤. cp /usr/lib/systemd/system/redis.service /usr/lib/systemd/system/redis2.service ------ redis2.service file ------ [Service] ExecStart=/usr/bin/redis-server /etc/redis2.conf --daemonize no ExecStop=/usr/bin/redis-shutdown redis2 --------------------------------- systemctl enable redis2 systemctl start redis2 ì°¸ê³ [REDIS] 를 CentOSì ë ë ì¤ì ì¬ë¬ ì¸ì¤í´ì¤ë¥¼ ì¤í ","categories": ["posts"], 449 "tags": ["redis"], 450 "url": "https://subji.github.io/posts/2020/10/08/howtomultipleinstanceofredisoncentos", 451 "teaser":null},{ 452 "title": "Apache ì¤ì (with SSL)", 453 "excerpt":"ìíì¹(httpd) ì¤ì Ubuntu ììë /etc/apache2 ì ìê³ CentOS ê°ì Redhat ê³ì´ì /etc/httpd ìëì ì¡´ì¬íë¤. ê°ì í¸ì¤í¸(Virtual Host) ë³ Config íì¼ì sites-availiable ë°ì ìì±íê³ , sites-enabled ì ìíí¸ ë§í¬ë¥¼ ì¶ê°íìë¤. httpd.conf # virtual host setting IncludeOptional sites-enabled/*.conf ê°ìí¸ì¤í¸ SSL ì¤ì mod_ssl ì ë°ëì ì¤ì¹íê³ íì±í í´ì¼íë¤. checking mobile device ë 모ë°ì¼ ê¸°ê¸°ë¡ ì ìíìì ë, ë¤ë¥¸ ëë©ì¸ì¼ë¡ ì´ëìí¤ê¸° ìí ì¤ì ì´ë¤. 모ë°ì¼ 기기 리ë¤ì´ë í ì ì¡°ê¸ ê³ ìíëë°. virtualhost:80 ë²ìì 모ë°ì¼ 기기를 ê²ì¬íë RewriteCond,Rule ì¤ì ì ì¶ê°í´ë ê³ì ê°ì íì´ì§ë§ ë¶ë¬ì¤ê³ 모ë°ì¼ íì´ì§ë¡ë ì´ëì´ ëì§ ììë¤. ê³°ê³°í ìê°í´ë³´ë ììì http ìì https ë¡ ë³´ë´ë ì¤ì ì´ ììë¤. ê·¸ë ë¤ë©´ 모ë°ì¼ 기기 ì²´í¬ë http ê° ìëë¼ https ìì í´ì¼ëëê² ìëê°? ê·¸ëì 80 í¬í¸ì ìì±í ì¤ì ì 443 ë²ì¼ë¡ ì®ê²¼ë¤. ê²°ê³¼ë ì±ê³µíë¤. ë¤ë§ 무í 리ë¤ì´ë í¸ë¼ë ëë¤ë¥¸ 문ì ê° ë°ìíë¤. ê·¸ëì R=301 ì¤ì ì¼ë¡ ê°ì 리ë¤ì´ë í íê³ , L ë¡ rule ì ëë´ë²ë ¸ë¤. NE ë ì´ì¤ì¼ì´í 문ì를 íì©íì§ ìë ì¡°ê±´ì´ë¤.(ì¬ê¸°ìë í¬ê² ìê´ìëë¯íë¤). Redirect http to https ë http ë¡ ì ìíì ë, ê°ì ë¡ https ë¡ ë¦¬ë¤ì´ë í¸íë ì¤ì ì´ë¤. example.conf ```sh <VirtualHost {domain name(DNS)}:443> ServerName {domain name(DNS) include \"www\"} ServerAlias {domain name(DNS)} DocumentRoot {web application path} SSLEngine on SSLProtocol All -SSLv2 -SSLv3 SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128:DHES-RSA-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHAAES256-SHA:AES:CAMESIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA SSLHonorCipherOrder on \t\t\t\t\t\t\t\t# case of Setigo RSA SSLCertificateFile {SSL file path}/_wildcard_{...}.crt SSLCertificateKeyFile {SSL file path}/_wildcard_{...}.key SSLCertificateChainFile {SSL file path}/ChainCA/rsa-dv.chain-bundle.pem SSLCACertificateFile {SSL file path}/ChainCA/AddTrustExternalCARoot.crt SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire ProxyPass / http://127.0.0.1:{PORT}/ connectiontimeout=900 timeout=900 ProxyPassReverse / http://127.0.0.1:{PORT}/ ProxyPreserveHost On RequestHeader set X-Forwarded-Proto \"https\" RequestHeader set X-Forwarded-Port \"443\" # CORS Config Header always set Access-Control-Allow-Origin \"*\" \t\t\t\t\t ErrorDocument 503 {error html file path} \t\t\t\t\t\t\t\t# Checking Mobile device RewriteEngine On RewriteCond %{QUERY_STRING} !^desktop RewriteCond %{HTTP_USER_AGENT} \"android|blackberry|googlebot-mobile|iemobile|ipad|iphone|ipod|#opera mobile|palmos|webos\" [NC,OR] RewriteCond %{HTTP_USER_AGENT} \"acs|alav|alca|amoi|audi|aste|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-\" [NC,OR] RewriteCond %{HTTP_USER_AGENT} \"dang|doco|eric|hipt|inno|ipaq|java|jigs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-\" [NC,OR] RewriteCond %{HTTP_USER_AGENT} \"maui|maxo|midp|mits|mmef|mobi|mot-|moto|mwbp|nec-|newt|noki|opwv\" [NC,OR] RewriteCond %{HTTP_USER_AGENT} \"palm|pana|pant|pdxg|phil|play|pluc|port|prox|qtek|qwap|sage|sams|sany\" [NC,OR] RewriteCond %{HTTP_USER_AGENT} \"sch-|sec-|send|seri|sgh-|shar|sie-|siem|smal|smar|sony|sph-|symb|t-mo\" [NC,OR] RewriteCond %{HTTP_USER_AGENT} \"teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|w3cs|wap-|wapa|wapi\" [NC,OR] RewriteCond %{HTTP_USER_AGENT} \"wapp|wapr|webc|winw|winw|xda|xda-\" [NC,OR] RewriteCond %{HTTP_USER_AGENT} \"up.browser|up.link|windowssce|iemobile|mini|mmp\" [NC,OR] RewriteCond %{HTTP_USER_AGENT} \"symbian|midp|wap|phone|pocket|mobile|pda|psp\" [NC] RewriteRule ^(.*)$ https://m.%{HTTP_HOST}$1 [R=301,NE,L] <Directory \"{web application path}\"> Options none AllowOverride none Order allow,deny Allow from all Require all granted </Directory> \t\t\t\t</VirtualHost> <VirtualHost *:80> ServerName {domain name(DNS) include \"www\"} ServerAlias {domain name(DNS)} DocumentRoot {web application path} Header always set Access-Control-Allow-Origin \"*\" \t\t\t\t\t\t\t\t# Redirect http to https RewriteEngine On RewriteCond %{HTTPS} !=on [NC] RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R,L] ProxyPreserveHost On <Directory \"{web application path}\"> Options none AllowOverride none Order allow,deny Allow from all Require all granted </Directory> </VirtualHost> ``` ì°¸ê³ Apache2.4 - mod_rewrite Reference mod_rewrite ë¡ URL ë¤ë£¨ê¸° Using multiple rewrite rules? https://stackoverflow.com/questions/3680463/mobile-redirect-using-htaccess ","categories": ["posts"], 454 "tags": ["apache","httpd"], 455 "url": "https://subji.github.io/posts/2020/11/27/goodtoknowregex", 456 "teaser":null},{ 457 "title": "ììëë©´ ì¢ì ì ê·ì(Regular Expression) 모ì", 458 "excerpt":"ì ë°ì´í¸ ì¼ì 2020-11-27 íêµ ë©ì¼ ê²ì¬ (ex. âac.krâ, âx.{íêµ ëë©ì¸}.ac.krâ, ë±ë±) /[\\@]{1}[a-z0-9A-Z\\.]*(?=\\.ac\\.kr)/gi ","categories": ["posts"], 459 "tags": ["regular expression"], 460 "url": "https://subji.github.io/posts/2020/11/27/howtosetapachesettingwithssl", 461 "teaser":null},{ 462 "title": "MacOS BigSur ìì Java íê²½ë³ì 못찾ë ì¤ë¥", 463 "excerpt":"문ì BigSur ë¡ ì ë°ì´í¸ë¥¼ íê³ ëì VSCode ìì ì¤íë§ ê°ë°ì¢ íë ¤ê³ íëë° ì¤íë§ ë¶í¸ ëìë³´ëê° ë³´ì´ì§ ìê³ ìë° ì»¤ë§¨ëë ì°¾ì ì ìë¤ë ì
463¤ë¥ê° ëìë¤. í´ê²° BigSur ë¡ ì ë°ì´í¸ëê³ ëì ë°ë ìë° íê²½ ë문ì ë°ìíë ì¤ë¥ìë¤. BigSur ììë 기본ì ì¼ë¡ /usr/bin/javac 를 ìë° ê¸°ë³¸ íê²½ë³ìë¡ ì í´ì ¸ìë¤. ì¦, jdk ê²½ë¡ë¥¼ ì¬ë³¼ë¦ ë§í¬ë¡ ì í´ëì§ ìëë¤. ê·¸ëì java 커맨ë를 ì¬ì©í ë ì¬ë°ë¥¸ ëìì í ì ììë¤. 먼ì íì¬ ì¤ì¹ë jdk ì ê²½ë¡ë¥¼ 보기ìí´ ìë 커맨ë를 ì ë ¥íë¤. ê·¸ë¬ë©´ ì¤ì¹ë jdk ì ê²½ë¡ê° ëì¨ë¤. /usr/libexec/java_home -V Matching Java Virtual Machines (1): 15.0.1 (x86_64) \"AdoptOpenJDK\" - \"AdoptOpenJDK 15\" /Library/Java/JavaVirtualMachines/adoptopenjdk-15.jdk/Contents/Home /Library/Java/JavaVirtualMachines/adoptopenjdk-15.jdk/Contents/Home # jdk ê²½ë¡ jdk ê²½ë¡ë¥¼ íì¸íë¤ë©´ ì´ì JAVA_HOME ì ì¤ì í´ì /usr/bin/javac ì íê²½ë³ì를 ì°ê²°í´ì¤ë¤. Mac ì zsh ê° ê¸°ë³¸ ìë¡ ì¤ì ëì´ì기 ë문ì .zshrc íì¼ì ì´ì´ì ë¤ì ê°ì ì ë ¥íê³ ì ì¥íë¤. vi ~/.zshrc # .zshrc file JAVA_HOME=`/usr/libexec/java_home` source ~/.zshrc ë§ì§ë§ì¼ë¡ JAVA_HOME ì´ ì ì¤ì ëìëì§ íì¸íë¤. ê²°ê³¼ê° jdk ê²½ë¡ë¡ ëì¨ë¤ë©´ ì±ê³µì´ë¤. echo $JAVA_HOME /Library/Java/JavaVirtualMachines/adoptopenjdk-15.jdk/Contents/Home ì°¸ê³ Error in every java file after updating to macOS Big Sur ","categories": ["posts"], 464 "tags": ["java","mac"], 465 "url": "https://subji.github.io/posts/2020/12/25/bigsurjavaerror", 466 "teaser":null},{ 467 "title": "Gradle : Could not initialize class org.codehaus.groovy.runtime.InvokerHelper Error", 468 "excerpt":"문ì Gradle Test 를 íë ê³¼ì ìì Could not initialize class org.codehaus.groovy.runtime.InvokerHelper Error ì¤ë¥ê° ë°ìíìë¤. í´ê²° Gradle 5.x ë²ì ìì jdk14 ë²ì ì ì§ìíì§ ìë ê³¼ì ìì ë°ìíë ì¤ë¥ìë¤. Gradle ë²ì ì 5.x ìì 6.x ë²ì ì¼ë¡ ì ê·¸ë ì´ëíë©´ í´ê²°ëë¤. gradle/wrapper/gradle-wrapper.properties # Gradle distribution version url # old version # distributionUrl=https\\://services.gradle.org/distributions/gradle-5.6.4-bin.zip # new version # distributionUrl=https\\://services.gradle.org/distributions/gradle-6.3-bin.zip ","categories": ["posts"], 469 "tags": ["gradle"], 470 "url": "https://subji.github.io/posts/2020/12/28/GradleCouldnotinitializeclassorgcodehausgroovyruntimeInvokerHelper", 471 "teaser":null},{ 472 "title": "Java ì 주차 구í기", 473 "excerpt":"ì 주차 ê³ì° ë°©ë² ì°ë¦¬ëë¼ ê¸°ì¤ì¼ë¡ ì 주차ë í 주ì ììì ììì¼ë¡ íë¤. ë§¤ë¬ ì²«ì§¸ì£¼ë ì´ì ë¬ì ë§ì§ë§ì£¼ì´ë ì´ë²ë¬ì 첫째주ì´ëê° í¹í ê³ ë¯¼ì´ë¤. ê°ë¨íê² íì¸íë ë°©ë²ì´ ìë¤. í´ë¹ 주ì 목ìì¼ë¡ íë¨í ì ìë¤. ì를ë¤ì´ ë¤ì 그림ì ìë 첫째주ë 9ìì ë§ì§ë§ 주ì¼ê¹ 10ìì 첫째주 ì¼ê¹? ì ëµì 10ìì 첫째주ì´ë¤. ìì ë§íëë¡ 10ì 1ì¼ì´ 목ìì¼ë¶í° ììí¨ì¼ë¡ì¨ 10ìì ì²«ì§¸ì£¼ê° ëë ê²ì´ë¤. ë°ëë¡ 9ì 30ì¼ì´ 목ìì¼ì´ë©´ 9ìì ë§ì§ë§ì£¼ê° ëëê²ì´ë¤. ì¬ì¤ 목ìì¼ë¡ 구ë¶íëê±´ êµì¥í ê°ë¨í ë°©ë²ì´ë¤. ì íí ë°©ë²ì 첫주ìì 4ì¼ì´ìì ì°¨ì§íë ë¬ì ì£¼ë¡ ê²°ì ëë ê²ì´ë¤. ì¦ ììì¼ ë¶í° íì£¼ê° ììíë ì°ë¦¬ëë¼ì ê²½ì° ëª©ìì¼ë¶í° ì¼ìì¼ê¹ì§ 4ì¼ì´ ëë¯ë¡ ê°ë¨íê² ëª©ìì¼ë¡ ê²°ì í ì ìë ê²ì´ë¤. ê·¸ë¼ ë´ ëë¦ëë¡ ì´ë¦¬ì 리 ìì 주차를 ê³ì°íë ì½ë를 ìì±í´ë³´ìë¤. public class CalendarUtil { /** * íë¼ë¯¸í°ë¡ ì ë¬ ë ë ì§ì 1ì¼ì 주차 ê³ì° * 1ì¼ì´ 목ìì¼(5) ë³´ë¤ í´ ê²½ì° ì²«ì§¸ 주 ì´ë¯ë¡ 0ì ë°í * 1ì¼ì´ ì ~ 목 ì´ì¸ì ë ì§ ì¼ ê²½ì° -1 ì ë°í. * 1ì¼ì´ 목ìì¼(5) ë³´ë¤ ìì¼ë©´ 첫째 ì£¼ê° ìëë¯ë¡ 1ì ë°í * @param year * @param month * @param day * @return */ public static int subWeekNumberIsFirstDayAfterThursday(int year, int month, int day) { Calendar calendar = Calendar.getInstance(Locale.KOREA); calendar.set(year, month - 1, day); calendar.set(Calendar.DAY_OF_MONTH, 1); calendar.setFirstDayOfWeek(Calendar.MONDAY); int weekOfDay = calendar.get(Calendar.DAY_OF_WEEK); if ((weekOfDay >= Calendar.MONDAY) && (weekOfDay <= Calendar.THURSDAY)) { return 0; } else if (day == 1 && (weekOfDay < Calendar.MONDAY || weekOfDay > Calendar.TUESDAY)) { return -1; } else { return 1; } } /** * í´ë¹ ë ì§ê° ë§ì§ë§ 주ì í´ë¹íê³ ë§ì§ë§ì£¼ì ë§ì§ë§ë ì§ê° 목ìì¼ë³´ë¤ ìì¼ë©´ * ë¤ìë¬ë¡ ëê²¨ì¼ íë¤ +1 * 목ìì¼ë³´ë¤ í¬ê±°ë ê°ì ê²½ì° ì´ë²ë¬ë¡ ê²°ì íë¤ +0 * @param year * @param month * @param day * @return */ public static int addMonthIsLastDayBeforeThursday(int year, int month, int day) { Calendar calendar = Calendar.getInstance(Locale.KOREA); calendar.setFirstDayOfWeek(Calendar.MONDAY); calendar.set(year, month - 1, day); int currentWeekNumber = calendar.get(Calendar.WEEK_OF_MONTH); int maximumWeekNumber = calendar.getActualMaximum(Calendar.WEEK_OF_MONTH); if (currentWeekNumber == maximumWeekNumber) { calendar.set(year, month - 1, calendar.getActualMaximum(Calendar.DAY_OF_MONTH)); int maximumDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); if (maximumDayOfWeek < Calendar.THURSDAY && maximumDayOfWeek > Calendar.SUNDAY) { return 1; } else { return 0; } } else { return 0; } } /** * í´ë¹ ë ì§ì 주차를 ë°í * @param year * @param month * @param day * @return */ public static String getCurrentWeekOfMonth(int year, int month, int day) { int subtractFirstWeekNumber = subWeekNumberIsFirstDayAfterThursday(year, month, day); int subtractLastWeekNumber = addMonthIsLastDayBeforeThursday(year, month, day); // ë§ì§ë§ 주차ìì ë¤ì ë¬ë¡ ëì´ê° ê²½ì° ë¤ìë¬ì 1ì¼ì 기ì¤ì¼ë¡ ì í´ì¤ë¤. // ì¶ê°ë¡ ë¤ì ë¬ ì²«ì§¸ì£¼ë 목ìì¼ë¶í° ììíë ê³¼ë°ìì ì¼ì를 í¬í¨í기 ë문ì í주를 ë¹¼ì§ ìëë¤. if (subtractLastWeekNumber > 0) { day = 1; subtractFirstWeekNumber = 0; } if (subtractFirstWeekNumber < 0) { Calendar calendar = Calendar.getInstance(Locale.KOREA); calendar.set(year, month - 1, day); calendar.add(Calendar.DATE, -1); return getCurrentWeekOfMonth(calendar.get(Calendar.YEAR), (calendar.get(Calendar.MONTH) + 1), calendar.get(Calendar.DATE)); } Calendar calendar = Calendar.getInstance(Locale.KOREA); calendar.setFirstDayOfWeek(Calendar.MONDAY); calendar.setMinimalDaysInFirstWeek(1); calendar.set(year, month - (1 - subtractLastWeekNumber), day); int dayOfWeekForFirstDayOfMonth = calendar.get(Calendar.WEEK_OF_MONTH) - subtractFirstWeekNumber; return (calendar.get(Calendar.MONTH) + 1) + \",\" + dayOfWeekForFirstDayOfMonth; } } ","categories": ["posts"], 474 "tags": ["java"], 475 "url": "https://subji.github.io/posts/2021/01/06/javagetweeknumberinkr", 476 "teaser":null},{ 477 "title": "Spring REST Docs ìê° ë° ìì ", 478 "excerpt":"Spring Rest Docs ìì±ì¼: 2021ë 1ì 5ì¼ ì¤ì 9:19 í´ë¹ ë´ì©ì ê³µì 문ì 2.0.5.RELEASE ë²ì ì ì°¸ê³ ë° í ëë¡ ìì±íììµëë¤. Overview Spring REST Docs ë RESTful ìë¹ì¤ì 문ìí를 ëì주ë ë구ì´ë¤. Spring REST Docs ë 문ì ì
478ì± ëêµ¬ë¡ ê¸°ë³¸ì ì¼ë¡ Asciidoctor 를 ì¬ì©íë©°, ì´ê²ì ì¬ì©í´ HTML ì ìì±íë¤. íìí ê²½ì° Markdown ì ì¬ì©íëë¡ ë³ê²½í ì ìë¤. Spring REST Docs ë Spring MVC ì í
ì¤í¸ íë ì ìí¬, Spring WebFlux WebTestClient ëë Rest Assured 3 ë¡ ìì±ë í
ì¤í¸ ì½ëìì ìì±ë Snippet ì ì¬ì©íë¤. í
ì¤í¸ ê¸°ë° ì ê·¼ ë°©ìì ìë¹ì¤ 문ìì ì íì±ì ë³´ì¥í´ì¤ë¤. Snippet ì´ ì¬ë°ë¥´ì§ ìì ê²½ì° í
ì¤í¸ê° ì¤í¨í기 ë문ì´ë¤. RESTful ìë¹ì¤ë¥¼ 문ìí íë ê²ì í´ë¹ 리ìì¤ë¥¼ ì¤ëª
íë ê²ì´ë¤. 리ìì¤ ì¤ëª
ì íµì¬ì HTTP ìì²ê³¼ HTTP ìëµì ì¸ë¶ì ë³´ì´ë¤. Spring REST Docs 를 ì¬ì©íë©´ 리ìì¤ì HTTP ìì² ë° ìëµì ì¬ì©íì¬ ìë¹ì¤ ë´ë¶ì ì¸ë¶ ì ë³´ë¡ë¶í° 문ì를 ë³´í¸í ì ìë¤. ì¦, ìë¹ì¤ ë´ë¶ ì½ëì ì¶ê° ëë ìì ê³¼ ê°ì´ ìí¥ì ì£¼ì§ ìê³ ë ë³ëì 문ìí ìì
ì ì§íí ì ìê² ëë¤. ì´ë ìë¹ì¤ 구íë³´ë¤ API 를 문ìí íëë° ëìì´ ëë¤. ëí 문ì를 ì¬ ìì
íì§ ìê³ ë ë²ì ì ë°ë¼ ë³ê²½í ì ìë¤. ë§ì´ ì¬ì©ëë RESTful API 문ìë구ë¡ë ëíì ì¼ë¡ Swagger ê° ìë¤. ìë íë Swagger ì Spring REST Docs ì ë¹êµ ë´ì©ì´ë¤. Spring REST Docs Swagger ì½ëì ì¶ê° ë° ìì ì´ ìë¤. ì½ëì ì´ë
¸í
ì´ì
ë±ì ì¶ê°í´ì¼ íë¤. í
ì¤í¸ ì½ë ìì±ì´ íìíë©°, í
ì¤í¸ ì±ê³µ ì 문ìê° ìì±ëë¤. í
ì¤í¸ ì½ë ìì´ ìë¹ì¤ 쪽 ì½ë ë° ì´ë
¸í
ì´ì
ì¶ê°ë¡ 문ì를 ìì±í ì ìë¤. ë²ì ë³íì ì ì°íê³ ì íì±ì´ ëë¤. ë²ì ë³íì ë§ì¶° ì¬ ìì±í´ì¼ íë©° ì´ë¥¼ íì§ ìì ë ì íì±ì´ ë®ë¤.   ìì¸í ë´ì©ì ì¬ê¸°ë¥¼ ì°¸ê³ íë¤. ììí기 ìí ì í리ì¼ì´ì
ì¬ë¬ ìí ìì© íë¡ê·¸ë¨ì ì¬ì©í ì ìë¤. MockMvc | Sample | Build System| Description | âââââ| â | â | |Spring Data REST|Maven|Spring Data REST 를 ì¬ì©| |Spring HATEOAS|Gradle|Spring HATEOAS 를 ì¬ì©| WebTestClient | Sample | Build System| Description | âââââ| â | â | |WebTestClient|Gradle|Spring WebFlux ì WebTestClient 를 ì¬ì©| REST Assured | Sample | Build System| Description | âââââ| â | â | |REST Assured|Gradle|Spring WebFlux ì WebTestClient 를 ì¬ì©| ê³ ê¸ | Sample | Build System| Description | âââââ| â | â | |Slate|Gradle|Markdown ë° Slate 를 ì¬ì©| |TestNG|Gradle|TestNG ì¬ì©| |JUnit 5|Gradle|JUnit 5 를 ì¬ì©| ì구 ì¬í Java 8 > Spring Framework 5 (5.0.2) > ë¹ë ìì¤í
ì¤ì Maven <!-- \t\"test\" scope 를 ê°ì§ë spring-restdocs-mockmvc ìì¡´ì± ì¶ê° \tëë spring-restdocs-webtestclient, spring-restdocs-restassured ì¶ê° --> <dependency> \t<groupId>org.springframework.restdocs</groupId> \t<artifactId>spring-restdocs-mockmvc</artifactId> \t<version>{project-version}</version> \t<scope>test</scope> </dependency> <build> \t<plugins> \t\t<!-- asciidoctor íë¬ê·¸ì¸ ì¶ê° --> \t\t<plugin> \t\t\t<groupId>org.asciidoctor</groupId> \t\t\t<artifactId>asciidoctor-maven-plugin</artifactId> \t\t\t<version>1.5.8</version> \t\t\t<executions> \t\t\t\t<execution> \t\t\t\t\t<id>generate-docs</id> \t\t\t\t\t<!-- \"prepare-package\" ìµì
ì í¨í¤ì§ë´ì API 문ì를 í¬í¨í ì ìê² íë¤. --> \t\t\t\t\t<phase>prepare-package</phase> \t\t\t\t\t<goals> \t\t\t\t\t\t<goal>process-asciidoc</goal> \t\t\t\t\t</goals> \t\t\t\t\t<configuration> \t\t\t\t\t\t<backend>html</backend> \t\t\t\t\t\t<doctype>book</doctype> \t\t\t\t\t</configuration> \t\t\t\t</execution> \t\t\t</executions> \t\t\t<dependencies> \t\t\t\t<!-- \t\t\t\t\t\"asciidoctor\" ì ìì¡´ì±ì¼ë¡ spring-restdocs-asciidoctor ì¶ê° \t\t\t\t\t.adoc íì¼ì´ target/generated-snippets ìëì ìì±ë Snippet ì ê°ë¦¬í¤ë \t\t\t\t\tì¤ì ì´ ì¶ê°ëë¤. \t\t\t\t\tëí operation ë¸ë¡ 매í¬ë¡ë¥¼ ì¬ì©í ì ìë¤. \t\t\t\t--> \t\t\t\t<dependency> \t\t\t\t\t<groupId>org.springframework.restdocs</groupId> \t\t\t\t\t<artifactId>spring-restdocs-asciidoctor</artifactId> \t\t\t\t\t<version>{project-version}</version> \t\t\t\t</dependency> \t\t\t</dependencies> \t\t</plugin> \t</plugins> </build> Gradle plugins { \t// Asciidoctor íë¬ê·¸ì¸ ì ì© \tid \"org.asciidoctor.convert\" version \"1.5.9.2\" } dependencies { \t/* \t\tasciidocker ì ëí spring-restdocs-asciidocker ìì¡´ì± ì¶ê° \t\tMaven ì²ë¼ build/generated-snippets ë°ì ìì±ë Snippet ì .adoc íì¼ì´ ìëì¼ë¡ \t\tê°ë¦¬í¤ëë¡ íë ì¤ì ì¶ê°. \t\toperation ë¸ë¡ 매í¬ë¡ ì¬ì© ê°ë¥ \t*/ \tasciidoctor 'org.springframework.restdocs:spring-restdocs-asciidoctor:{project-version}' \t// Maven ê³¼ ê°ì´ test Scope ì ëí mockMvc ìì¡´ì±ì ì¶ê° (WebClient, Assured ì¬ì©ê°ë¥) \ttestCompile 'org.springframework.restdocs:spring-restdocs-mockmvc:{project-version}' }
478 ext { \t// Snippet ì ìì± ìì¹ë¥¼ ì§ì \tsnippetsDir = file('build/generated-snippets') } test { \t// Snippets ëë í 리를 ì¶ë ¥ì¼ë¡ ìì
íëë¡ ì¤ì \toutputs.dir snippetsDir } asciidoctor { \t// Snippets ëë í 리를 Input ëë í ë¦¬ë¡ ì¤ì \tinputs.dir snippetsDir \t// 문ì ìì± ì í
ì¤í¸ê° ì¤íëëë¡ test ì ì¢
ì ì¤ì \tdependsOn test } 문ì í¨í¤ì§ ìì±ë API 문ì를 jar ëë war ì í¨í¤ì§í기 ìí ì¤ì ì´ë¤. ì´ë Spring Boot ì ìí´ ì ì ì½í
ì¸ ë¡ ì ê³µëë¤. jar ê° ë¹ëë기 ì ì 문ìê° ìì±. ìì±ë 문ìë jar ì í¬í¨. Maven <plugin> \t<!-- Asciidoctor íë¬ê·¸ì¸ ì ì¸ --> \t<groupId>org.asciidoctor</groupId> \t<artifactId>asciidoctor-maven-plugin</artifactId> </plugin> <plugin> \t<!-- \t\t문ìê° í¨í¤ì§ë¡ ë³µì¬ë기 ì ì ìì±ëì¼ íë¯ë¡ ìì Asciidoctor íë¬ê·¸ì¸ ë¤ì ì ì¸íë¤. \t--> \t<artifactId>maven-resources-plugin</artifactId> \t<version>2.7</version> \t<executions> \t\t<execution> \t\t\t<id>copy-resources</id> \t\t\t<phase>prepare-package</phase> \t\t\t<goals> \t\t\t\t<goal>copy-resources</goal> \t\t\t</goals> \t\t\t<configuration> \t\t\t <!-- ìì±ë 문ì를 static/docs ì ë³µì¬ --> \t\t\t\t<outputDirectory> \t\t\t\t\t${project.build.outputDirectory}/static/docs \t\t\t\t</outputDirectory> \t\t\t\t<resources> \t\t\t\t\t<resource> \t\t\t\t\t\t<directory> \t\t\t\t\t\t\t${project.build.directory}/generated-docs \t\t\t\t\t\t</directory> \t\t\t\t\t</resource> \t\t\t\t</resources> \t\t\t</configuration> \t\t</execution> \t</executions> </plugin> Gradle bootJar { \t// ë¹ë ì 문ì ìì± íì¸ \tdependsOn asciidoctor \t// ìì±ë 문ì를 static/docs ì ë³µì¬ \tfrom (\"${asciidoctor.outputDir}/html5\") { \t\tinto 'static/docs' \t} } 문ì ì¡°ê° ìì± Spring REST Docs ë í
ì¤í¸ íë ììí¬, WebTestClient ë±ì ì¬ì©í´ì 문ìíì íìí ìë¹ì¤ì ìì²ì íë¤. ê·¸ ë¤ì ìì² ë° ê²°ê³¼ ìëµì ëí 문ì Snippet ì ìì±íë¤. í
ì¤í¸ ì¤ì í
ì¤í¸ íë ììí¬ì ë°ë¼ ë¤ë¥´ë¤. ì¬ê¸°ììë JUnit 5 ì ëí ì¤ì ì ë¤ë£¬ë¤. JUnit 5 í
ì¤í¸ ì¤ì RestDocumentationExtension ì í
ì¤í¸ í´ëì¤ì ì ì©íë¤. @ExtendWith(RestDocumentationExtension.class) public class JUnit5ExampleTests {} RestDocumentationExtension ì Maven ì ê²½ì° âtarget/generated-snippetsâ Gradle ì ê²½ì° âbuild/generated-snippetsâ 를 ìëì¼ë¡ ì¶ë ¥ ëë í ë¦¬ë¡ ì¤ì ëì´ìë¤. ì¼ë°ì ì¸ Spring Application ì ê²½ì°ìë SpringExtension ì ì ì©íë¤. @ExtendWith({ RestDocumentationExtension.class, SpringExtension.class }) public class JUnit5ExampleTests {} JUnit 5.1 ì ì¬ì©íë ê²½ì° í´ëì¤ì íëë¡ ë±ë¡í´ì ì¶ë ¥ ëë í 리를 ììë¡ ì§ì í ì ìë¤. public class JUnit5ExampleTests { \t@RegisterExtension \tfinal RestDocumentationExtension restDocumentation = new RestDocumentationExtension (\"custom\"); } ë¤ìì MockMvc 를 ì¬ì©í ì¤ì ë°©ë²ì´ë¤. import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; private MockMvc mockMvc; @BeforeEach public void setUp(WebApplicationContext webApplicationContext, \t\tRestDocumentationContextProvider restDocumentation) { \tthis.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) \t.apply(documentationConfiguration(restDocumentation)) \t.alwaysDo(document(\"{method-name}\", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()))) \t.build(); } documentationConfiguration ì 기본ê°ì´ ìì§ë§ ì¬ì©ì ì ì를 ìí ë¤ìí ì¤ì ë ì ê³µíë¤. ë´ì©ì ì¬ê¸°ë¥¼ ì°¸ê³ íë¤. ì¶ê°ë¡ alwaysDo() ë©ìëìì 문ìíë¡ ë³´ì¬ì¤ ë ì´ìê² ë³´ì¬ì£¼ê¸° ìí ìµì
ë ì¶ê°íë¤. JUnit ìì´ í
ì¤í¸ ì¤ì JUnitRestDocumentation ëì ManualRestDocumentation ì ì¬ì© @Rule ì´ë
¸í
ì´ì
ì ê±° private MockMvc mockMvc; @Autowired private WebApplicationContext context; private ManualRestDocumentation restDocumentation = new ManualRestDocumentation(); @BeforeMethod public void setUp(Method method) { \tthis.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) \t\t\t.apply(documentationConfiguration(this.restDocumentation)) \t\t\t.build(); \tthis.restDocumentation.beforeTest(getClass(), method.getName()); } ë§ì§ë§ì¼ë¡ ê° í
ì¤í¸ íì ManualRestDocumentation.afterTest 를 í¸ì¶í´ì¼ íë¤. @AfterMethod public void tearDown() { \tthis.restDocumentation.afterTest(); } RESTful ìë¹ì¤ í¸ì¶ RESTful í¸ì¶ ìì // ìë¹ì¤ì 루í¸(\"/\") ë¡ ìì²íê³ ìëµ íì
ì application/json ì¼ë¡ íë¤. this.mockMvc.perform(get(\"/\").accept(MediaType.APPLICATION_JSON)) \t// ìëµ íì¸ \t.andExpect(status().isOk()) \t// ìë¹ì¤ í¸ì¶ì 문ìí íëë° Snippet ì´ë¦ì index ë¡ ì§ì . \t//
478Snippet ì \"RestDocumentationResultHandler\" ì ìí´ ìì±ëë©°, í´ëì¤ì ì¸ì¤í´ì¤ë \t// \"org.springframework.restdocs.mockmvc.MockMvcRestDocumentation\" ì \"document\" // ë©ìë를 ì°¸ê³ íë¤. \t.andDo(document(\"index\")); 기본ì ì¼ë¡ 6ê°ì Snippet ì´ ìì±ëë¤. ê·¸ ì¸ì Snippet ì ëí ì ë³´ë ì¬ê¸°ë¥¼ ì°¸ê³ íë¤. <output-directory>/index/curl-request.adoc <output-directory>/index/http-request.adoc <output-directory>/index/http-response.adoc <output-directory>/index/httpie-request.adoc <output-directory>/index/request-body.adoc <output-directory>/index/response-body.adoc Snippet ì¬ì© í
ì¤í¸ í ìì±ë Snippet ì ì¬ì©í기ìí´ .adoc íì¼ì ìì±í´ì¼ íë¤. íì¼ëª
ì ì무ë ê²í´ë ìê´ìë¤. ê·¸ë¦¬ê³ ìì±ë snippet ì ì°ê²°í´ì£¼ë©´ ëë¤. include::{snippets}/index/curl-request.adoc[] ë§ë¤ì´ ëì .adoc ê³¼ ìì±ë Snippet ì ì°ê²°ì´ ëë¬ë¤ë©´ maven install ëë gradle build ë¡ .html íì¼ì ìì±í´ì¤ë¤. ë¹ëë구 ìì¤ íì¼ ìì± ë íì¼ Maven src/main/asciidoc/*.adoc target/generated-docs/*.html Gradle src/docs/asciidoc/*.adoc build/asciidoc/html5/*.html Simple Example ì½ëë ì¬ê¸°ë¥¼ ì°¸ê³ íë¤. ìì¡´ì± ì¶ê° spring-boot-starter-web ê³¼ Spring REST Docs 를 ìí spring-restdocs-mockmvc 를 ì¶ê°íë¤. <dependency> \t<groupId>org.springframework.boot</groupId> \t<artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> \t<groupId>org.springframework.restdocs</groupId> \t<artifactId>spring-restdocs-mockmvc</artifactId> \t<scope>test</scope> </dependency> Plugin ì¤ì Asciidoctor íë¬ê·¸ì¸ì ì¤ì ìì± ë Snippet ê³¼ ì°ê²°ë .adoc ì ë¹ë íì ë ë§ë¤ì´ì§ë HTML íì¼ì ìì¹ ì¤ì HTML íì¼ì ë³µì¬íì¬ ì¹ìì íì¸í ì ìë ëë í ë¦¬ë¡ ì´ë maven-resources-plugin ì Snippet ì´ ìì±ë ì´íì ëì(íì¼ ë³µì¬) ëì´ì¼ íë¯ë¡ asciidoctor-maven-plugin ë¤ì ì¤ëë¡íë¤. <plugin> \t<groupId>org.asciidoctor</groupId> \t<artifactId>asciidoctor-maven-plugin</artifactId> \t<version>1.5.8</version> \t<executions> \t\t<execution> \t\t\t<id>generate-docs</id> \t\t\t<phase>prepare-package</phase> \t\t\t<goals> \t\t\t\t<goal>process-asciidoc</goal> \t\t\t</goals> \t\t\t<configuration> \t\t\t\t<backend>html</backend> \t\t\t\t<doctype>book</doctype> \t\t\t\t<attributes> \t\t\t\t\t<snippets>${snippetsDirectory}</snippets> \t\t\t\t</attributes> \t\t\t\t<sourceDirectory>${basedir}/src/main/docs/asciidocs</sourceDirectory> \t\t\t\t<outputDirectory>${project.build.directory}/generated-docs</outputDirectory> \t\t\t</configuration> \t\t</execution> \t</executions> \t<dependencies> \t\t<dependency> \t\t\t<groupId>org.springframework.restdocs</groupId> \t\t\t<artifactId>spring-restdocs-asciidoctor</artifactId> \t\t\t<version>${spring-restdocs.version}</version> \t\t</dependency> \t</dependencies> </plugin> <plugin> \t<artifactId>maven-resources-plugin</artifactId> \t<version>2.7</version> \t<executions> \t\t<execution> \t\t\t<id>copy-resources</id> \t\t\t<phase>prepare-package</phase> \t\t\t<goals> \t\t\t\t<goal>copy-resources</goal> \t\t\t</goals> \t\t\t<configuration> \t\t\t\t<outputDirectory> \t\t\t\t\t${basedir}/src/main/resources/static/html/docs \t\t\t\t</outputDirectory> \t\t\t\t<resources> \t\t\t\t\t<resource> \t\t\t\t\t\t<directory> \t\t\t\t\t\t\t${project.build.directory}/generated-docs \t\t\t\t\t\t</directory> \t\t\t\t\t</resource> \t\t\t\t</resources> \t\t\t</configuration> \t\t</execution> \t</executions> </plugin> ìì Controller, Service, DTO ì¶ê° DTO @Data public class RestDocsTest { private String id; private String name; private String body; } Service @Service public class RestDocsTestService { public ResponseEntity<RestDocsTest> readContent(RestDocsTest restDocsTest) { RestDocsTest result = new RestDocsTest(); try { result.setId(\"ID\"); result.setName(\"Name\"); result.setBody(\"Body\"); return new ResponseEntity<>(result, HttpStatus.OK); } catch (Exception e) { return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); } } } Rest API Controller @RestController @RequestMapping(\"/test\") public class RestDocsRestController { private final RestDocsTestService restDocsTestService; public RestDocsRestController(RestDocsTestService restDocsTestService) { this.restDocsTestService = restDocsTestService; } @GetMapping(value = { \"\" }) public ResponseEntity<RestDocsTest>
478 readRestDocsTest(@RequestBody(required = false) RestDocsTest restDocsTest) { ResponseEntity<RestDocsTest> result = restDocsTestService.readContent(restDocsTest); return result; } } í¬í¸ ì¤ì (íì ì) ë° View Resolver íì¥ì ì¤ì server: port: 8080 spring: mvc: view: suffix: .html í ì¤í¸ ì½ë ìì± import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.restdocs.RestDocumentationContextProvider; import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.payload.PayloadDocumentation; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; @ExtendWith({ RestDocumentationExtension.class, SpringExtension.class }) @SpringBootTest(classes = ExampleSprintRestDocsApplication.class) class ExampleSprintRestDocsApplicationTests { \tprivate MockMvc mockMvc; \t@Test \tvoid contextLoads() { \t} \t@BeforeEach \tpublic void setUp( \t\tWebApplicationContext webApplicationContext, \t\tRestDocumentationContextProvider restDocumentationContextProvider)\t{ \t\tthis.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) \t\t\t.apply(documentationConfiguration(restDocumentationContextProvider)) \t\t\t.alwaysDo(document(\"{method-name}\", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()))) \t\t\t.build(); \t} \t@Test \tpublic void readExample() throws Exception { \t\tthis.mockMvc.perform(get(\"/test\")) \t\t.andExpect(status().isOk()) \t\t.andDo( \t\t\tdocument(\"test-example\", \t\t\t\tpreprocessRequest(prettyPrint()), \t\t\t\tpreprocessResponse(prettyPrint()), \t\t\t\tPayloadDocumentation.responseFields( \t\t\t\t\tPayloadDocumentation.fieldWithPath(\"id\") \t\t\t\t\t\t.description(\"ID íë ì ëë¤.\"), \t\t\t\t\tPayloadDocumentation.fieldWithPath(\"name\") \t\t\t\t\t\t.description(\"ì´ë¦ íë ì ëë¤.\"), \t\t\t\t\tPayloadDocumentation.fieldWithPath(\"body\") \t\t\t\t\t\t.description(\"ë°ì´í° íë ì ëë¤.\") \t\t))); \t\t\t// links( \t\t\t// \t// linkWithRel ì í ì¤í¸ë¥¼ ë§í¬í íë¤. \t\t\t// \tlinkWithRel(\"test\").description(\"The Test resource\")), \t\t\t// \t\t//subsectionWithPath 를 ì¬ì©í´ì ë©ìëì ëí ìëµì 문ìí íë¤. \t\t\t// \t\tresponseFields(subsectionWithPath(\"_links\") \t\t\t// \t\t\t.description(\"Links to other resources\")), \t\t\t// \t\t// ìëµì \"Content-type\" í¤ë를 ì¶ê°í기 ìí´ headerWithName ì ì¬ì© \t\t\t// \t\tresponseHeaders(headerWithName(\"Content-Type\") \t\t\t// \t\t\t.description(\"The Content-Type of the payload, e.g. `application/hal+json`\"))) \t} } ë¹ë ë° Snippet íì¸ ë§ë¤ì´ì§ Snippet ë¤ì ì°ê²°í´ì¤ ì¬ì©ì ì ì .adoc íì¼ì âsrc/main/docs/aciidocsâ ê²½ë¡ì ë§ë¤ê³ ë¤ìê³¼ ê°ì´ ìì±íë¤. = RESTful Notes API Guide :doctype: book :icons: font :source-highlighter: highlightjs :toc: left :toclevels: 4 :sectlinks: include::{snippets}/test-example/curl-request.adoc[] include::{snippets}/test-example/http-request.adoc[] include::{snippets}/test-example/http-response.adoc[] include::{snippets}/test-example/httpie-request.adoc[] include::{snippets}/test-example/request-body.adoc[] include::{snippets}/test-example/response-body.adoc[] include::{snippets}/test-example/response-fields.adoc[] ì´ì Snippet ë¤ì ë§ë¤ê¸° ìí ë¹ë를 ì¤ííë¤. mvn clean install ë¹ë ë° í ì¤í¸ê° ìë£ëë©´ target/generated-snippets ë°ì 기본 íì¼ë¤ì´ ì긴ë¤. í´ë¹ Snippet ë¤ì ìëì¼ë¡ .adoc ê³¼ ì°ê²°ëë¤. ëí static/html/docs ë°ì html ì´ ìì±ë ê²ì ë³¼ ì ìë¤. HTML ì¹ íì´ì§ ì°ê²° ì´ì ë§ë¤ì´ì§ HTML ì ì¹ íì´ì§ë¡ ë³¼ ì ìê² ì°ê²°í´ë³´ì. @Controller @RequestMapping(\"/docs\") public class RestDocsController { @GetMapping public String docs() { return \"html/docs/rest-test-api-guide\"; } } ì¹ íì´ì§ íì¸ ì¤íë§ ë¶í¸ ì í리ì¼ì´ì ì ì¤ííê³ ë¤ì 주ìë¡ ì ìíë©´ Spring REST Docs ë¡ ë§ë¤ì´ì§ 문ì를 ë³¼ ì ìë¤. http://localhost:8080/docs ","categories": ["posts"], 479 "tags": ["spring rest docs"], 480 "url": "https://subji.github.io/posts/2021/01/06/springrestdocsexample", 481 "teaser":null},{ 482 "title": "Spring Boot JUnit ìì íê¸ ê¹¨ì§ë ì¤ë¥", 483 "excerpt":"JUnit ìì ìëµ ê°ì ê²°ê³¼ ì¤ íê¸ì´ 깨ì§ë ì¤ë¥ JUnit ì¼ë¡ mockmvc.perform ì¼ë¡ ìëµê°ì ê°ì ¸ìì íì¸íë ¤ë ì¤ ê²°ê³¼ì íê¸ì´ í¬í¨ëìì ë 깨ì§ë ì¤ë¥ê° ë°ìíë ê²ì íì¸íìë¤. ì°¾ìë³´ë Spring 5.2 ë²ì ì´í Spring Boot ì ê²½ì° 2.2 ë²ì ì´í ë¶í° 기본 ì¸ì½ë© ë°©ìì¼ë¡ UTF-8 ì ëì´ì ì§ìíì§ ìëë¤ê³ íë¤. ì´ë 기본ê°ì´ ISO-8859-1 ë¡ ë³ê²½ëì기 ë문ì´ë¼ê³ íë¤. </br> </br> </br> í´ê²°ë°©ë² @before ì setup í¨ììì UTF-8 ë¡ ì¸ì½ë©ì í´ì£¼ë íí°ë¥¼ ì¶ê°í´ì£¼ë©´ëë¤. @Before public void setUp() { \tthis.mockMvc = MockMvcBuilders \t\t.webAppContextSetup(this.context) \t\t.addFilter(((request, response, chain) -> { \t\t\tresponse.setCharacterEncoding(\"UTF-8\"); \t\t\tchain.doFilter(request, response); \t\t})) \t\t.apply(springSecurity()).build(); } ì°¸ê³ Spring Boot MVC Test - MockMvc is always null MockMvc no longer handles UTF-8 characters ","categories": ["posts"], 484 "tags": ["spring boot","junit","test"],
485 "url": "https://subji.github.io/posts/2021/02/04/springbootmockmvccharset", 486 "teaser":null},{ 487 "title": "Intellij ì´ë¦° ìëí° ì í ì í´ë¹ íì¼ ê°ë¦¬í¤ê¸° Select opened file or editor", 488 "excerpt":"문ì ì´ì ì vscode 를 ì¬ì©íë©´ì ì´ë¦° íì¼ì ì ííìë, ì¼ìª½ì Sidebar ìì ìëì¼ë¡ íì¼ë¡ í¬ì»¤ì±ì´ ëë 기ë¥ì´ ììë¤. íë¡ì í¸ êµ¬ì¡°ê° ê¸¸ì´ì§ë©´ì ì¤í¬ë¡¤ì íë©´ì ì°¾ìê°ê¸° íë¤ë ì주 ë§ì ëìì´ ëììë¤. ì´ë²ì Intellij 를 ì¬ì©íë©´ì ë¹ì°í í´ë¹ 기ë¥ì´ ììê±°ë¼ ìê°íëë°, ë´ê° ë¬´ì¨ ì¤ì ì ì못íê±´ì§ ìëë©´ 기본ê°ì´ ê·¸ë°ê±´ì§ ëìíì§ ììë¤. ë ë¬¼ë¡ Ctrl + Shift + N ì ëë¬ì íì¼ì ì§ì ì°¾ë ë°©ë²ë ìë¤. íì§ë§ ê·¸ë¥ ì§ê¸ ì´ë¦° íì¼ì ìëì¼ë¡ íë¡ì í¸ êµ¬ì¡°ìì ì íëê² í´ì£¼ë©´ ë í¸í기ì ë°©ë²ì ì´ë¦¬ì 리 ì°¾ìë³´ìë¤. </br> </br> </br> í´ê²°ë°©ë² ë°©ë²ì ê°ë¨íë¤. ìëì¼ë¡ í¬ì»¤ì± í´ì£¼ë ë¨ì¶í¤ë ìë¤(Alt + f1). íì§ë§ ê·¸ë³´ë¤ íì ìëì¼ë¡ ëë걸 ë°ë¬ê¸°ì ë¤ì ì¤ì ì ì²´í¬íëë¡ íë¤. ","categories": ["posts"], 489 "tags": ["intellij"], 490 "url": "https://subji.github.io/posts/2021/02/25/intellijselectopeneditor", 491 "teaser":null},{ 492 "title": "java.lang.IllegalArgumentException: 'Content-Type' cannot contain wildcard type '*'", 493 "excerpt":"ì¤ë¥ RestTemplate ì¼ë¡ ìì²ì íëë° ì¨ì í ì ì¡ëì§ë ìê³ ì¤ë¥ë¥¼ ë±ì´ëë¤. ì¤ë¥ ë´ì©ì ê·¸ëë¡ ë³´ë©´ âContent-Typeâ ìë â*â ì ê°ì ìì¼ë ì¹´ë를 í¬í¨í ì ìë¤ê³ ëì¨ë¤. ê·¸ëì ì½ë를 ì´í´ë³´ìëë°, HttpHeaders headers = new HttpHeaders(); HttpEntity<Object> entity = new HttpEntity<>(params, headers); ë¼ê³ ëì´ììë¤. ë¤ë§ ê¶ê¸í ì ì ìë ì½ë ë¶ë¶ì¸ë°, public void setContentType(@Nullable MediaType mediaType) { if (mediaType != null) { Assert.isTrue(!mediaType.isWildcardType(), \"Content-Type cannot contain wildcard type '*'\"); Assert.isTrue(!mediaType.isWildcardSubtype(), \"Content-Type cannot contain wildcard subtype '*'\"); set(CONTENT_TYPE, mediaType.toString()); } else { remove(CONTENT_TYPE); } } Content-Type ì ì¤ì íì§ ììì ê²½ì°ì ëë ì ì ìë ê²½ì°ì â*â ì ê°ì ìì¼ëì¹´ëê° ì¤ì ëëê°ì´ë¤. ì°¾ìë³´ë Accept í¤ë를 ì¤ì íì§ ìê±°ë ì¬ì©íì§ ìë ê²½ì°ì í¹ì MediaType.ALL ë¡ ì¤ì í ê²½ì°ìë ìëµ íì ì ìì§ ëª»íë¤. ê·¸ëì ì 목과 ê°ì ì
493¤ë¥ë¥¼ ë°ìíë¤ê³ íë¤. ì¤ë¥ í´ê²° ë°©ë²ì ê°ë¨íë¤. Header ì Content-Type ì ì§ì í´ì£¼ë©´ ëë¤. HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); ì°¸ê³ Problem to Understand correct configuration for Http GET Requests on Spring Integration ","categories": ["posts"], 494 "tags": ["springbood"], 495 "url": "https://subji.github.io/posts/2021/03/30/cannotcontainwildcardstar", 496 "teaser":null}]
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.