# Recoeve Database setup (0.1 version) MySQL 이용. Time data must be saved as universal, not local, for globalization. 'eve' 계정으로 대부분 처리. 이 계정의 GRANT를 적절히 설정해야 할듯. 간단히 초기 유저들 확인용. ``` SELECT i,id,email,class,tReg,ipReg,pwd_iteration FROM `Users` LIMIT 10; SELECT * FROM `LogInLogs` ORDER BY 't' DESC LIMIT 0,10; ```/ ## TOC ## Set-up ```[.linenums.lang-sql] CREATE DATABASE `Recoeve0.1`; USE `Recoeve0.1`; CREATE USER 'eve'@'localhost' IDENTIFIED BY '{--password--}'; GRANT ALL ON `Recoeve0.1`.* TO 'eve'@'localhost'; ```/ ### Tables 우선 DATABASE 를 만든 후, TABLE , 를 만듬. Sign-up 자동화 방지를 위해서 도 만듬. E-mail stat 좀 확인하고 싶어서 . FinTech 관련된 , . Log-in 에 쓰일 , , 까지 만들어야 하고. Reco 를 저장할 , tree 구조 형식의 데이터 handling 을 도와줄 도 만들어야 함. Similarity 를 저장할 . Reco 의 여러가지 stat 를 위해 , , , .
에서 user 를 지울때에는 foreign key (REFERENCES `Users`) 가 있는 table 들을 먼저 지워야 함.
### Encoding settings
my.ini, my.cnf 파일로 encoding 설정.
Encoding 설정 : STATUS ```[.linenums] mysql> status; -------------- mysql Ver 14.14 Distrib 5.6.21, for Win64 (x86_64) Connection id: 7 Current database: Current user: root@localhost SSL: Not in use Using delimiter: ; Server version: 5.6.21-log MySQL Community Server (GPL) Protocol version: 10 Connection: localhost via TCP/IP Server characterset: utf8 Db characterset: utf8 Client characterset: utf8 Conn. characterset: utf8 TCP port: 3306 Uptime: 2 days 15 hours 17 min 30 sec Threads: 1 Questions: 27 Slow queries: 0 Opens: 70 Flush tables: 1 Open tab les: 63 Queries per second avg: 0.000 -------------- ```/
Encoding 설정 : SHOW variables like 'c%'; ```[.linenums.lang-sql] mysql> SHOW variables like 'c%'; +--------------------------+---------------------------------------------------------+ | Variable_name | Value | +--------------------------+---------------------------------------------------------+ | character_set_client | utf8 | | character_set_connection | utf8 | | character_set_database | utf8 | | character_set_filesystem | binary | | character_set_results | utf8 | | character_set_server | utf8 | | character_set_system | utf8 | | character_sets_dir | C:\Program Files\MySQL\MySQL Server 5.6\share\chars ets\ | | collation_connection | utf8_general_ci | | collation_database | utf8_general_ci | | collation_server | utf8_general_ci | | completion_type | NO_CHAIN | | concurrent_insert | AUTO | | connect_timeout | 10 | | core_file | OFF | +--------------------------+---------------------------------------------------------+ ```/
##[.hiden] HTTP Request Server 에서 기본적으로 받는 data 들은... ```[.scrollable.lang-ruby] # GET a file, From chrome Host: localhost:1000 Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.102 Safari/537.36 DNT: 1 Accept-Encoding: gzip,deflate,sdch Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4 Cookie: # GET favicon.ico, From chrome Host: localhost:1000 Connection: keep-alive Accept: */* DNT: 1 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.102 Safari/537.36 Accept-Encoding: gzip,deflate,sdch Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4 Cookie: # GET /, From chrome, iframe tag in http://kipid.tistory.com/entry/Access-from-other-hosts Host: localhost:1000 Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.103 Safari/537.36 DNT: 1 Referer: http://kipid.tistory.com/entry/Access-from-other-hosts Accept-Encoding: gzip,deflate,sdch Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4 # GET /image/Tulips.jpg, From chrome, img tag in http://kipid.tistory.com/entry/Access-from-other-hosts Host: localhost:1000 Connection: keep-alive Cache-Control: max-age=0 Accept: image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.103 Safari/537.36 DNT: 1 Referer: http://kipid.tistory.com/entry/Access-from-other-hosts Accept-Encoding: gzip,deflate,sdch Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4 # GET /log-in.css, From chrome, link tag in http://kipid.tistory.com/entry/Access-from-other-hosts Host: localhost:1000 Connection: keep-alive Cache-Control: max-age=0 Accept: text/css,*/*;q=0.1 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.103 Safari/537.36 DNT: 1 Referer: http://kipid.tistory.com/entry/Access-from-other-hosts Accept-Encoding: gzip,deflate,sdch Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4 ```/ 어느 URI 에서 요청이 들어왔는지 나타내는게 Referer 인듯? Cookie 에 browser cookie 가 오는거고.
IP 는 ``` req.remoteAddress(): /0:0:0:0:0:0:0:1:5454 ```/ 와 같이 뽑아낼 수 있는듯. ## Users ### 암호 해킹 방지 암호화 (하는 중) Iteration 이 들어가는 단방향 암호화 (Hash) 이용해서 네트워크 중간에 가로채는 해킹에 대해서도 어느정도 면역이 생기게 디자인 할수도 있을거 같은데... User 쪽 javascript 단에서 1000 iteration 해서 처음에 보내고. 다음부터는 999 iteration 해서 보내고 나머지는 server 에서 iteration 통해 암호가 일치하는지 확인. 이러면 사용자 암호가 털리지 않는한, 네트워크 중간에서 가로챈 암호화 된 암호 가지고도 해킹이 불가능하게 디자인 가능. 단 user 쪽 iteration 은 항상 줄어들어야 함. 안그러면 hash function 이 javascript 에 노출되므로 뒷쪽 iteration 을 유추가능. Server 의 user data 에는 iteration 정보를 저장하고 있어야 하고, user 가 로그인할때 id/e-mail 만 AJAX 로 먼저 보내서 암호 iteraion 을 몇번할 것인지 받아낸 다음 iteration 으로 암호화 후 전체 데이터 전송. 로그인 유지야 뭐... 쿠키 이용하는 거고. 쿠키가 털렸을때는 막을 방법은 없을듯? (하드웨어 정보 저장해놓고 비교하는 식으로 약간 높일수는 있을텐데... 이것도 베낄수 있을거 같아서 크게 보안이 향상되는 건 아닌듯.) 아무튼 JAVA 랑 Javascript 에서 똑같이 동작하는 hash function 만드는게 핵심일듯. 1 iteration 씩 줄어들게 만들거 같은데, 어느정도까지 해야 inverse hash function 찾기 어렵게 만들 수 있을까나???
  • 우선 function 형태로 inverse 가 찾아지면 안될테고. (이건 어차피 M to N function 이라 찾기 힘들듯?)
  • hash function 은 javascript 형태로 공개가 될테니, 이거 이용해서 table 만들고 이거 이용해서 inverse 를 구할수는 있음. 아마도 다른 더 큰 사이즈의 hash function 이용해서 hash table 을 만들텐데, 어느정도 size 로 해야 이런식의 해킹을 방지할 수 있을까나? 512 bit (=32bit * 16) 은 되어야 할듯?
### Sign-up: User Verification at Registration Stage (가입 시점에서의 유저 인증) Vert.x 로 이메일 보내기? (github.com - vert-x - mod-mailer) 그냥 JAVA로 gmail 계정 이용해서 보내는게 편할듯? 인증을 이메일만으로? 이거 위험할래나? 믿을만한 이메일 서버 리스트 만들어서 여기 메일로만 인증 가능하게 해버릴까? 해커가 이메일 서버를 만들어서 접근하면 막무가내로 계정 생성 가능할듯? CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) 를 그림문자 같은거 통해서? ReCAPTCHA? 등등?? 천천히 생각할까? Sign-up 과정은 id email pwd pwd_confirm id e@mail pwd_encrypted confirmed 와 같은 데이터를 user 가 보내면, hidden으로 같이 오는 데이터는 ip log screenWidth screenHeight req.remoteAddress() web 640 480
  1. id 와 email data 만 우선 받아서 unique (ignore case) 한지 ajax 통해서 우선 check. POST method로 "account/check"에 "id email" data를 보내면, tab 으로 아래 data 를 붙여서 response. ```[.linenums.lang-sql] @idAvailable : SELECT count(1) FROM `Users` WHERE `id`=? LIMIT 1; or findUserById(id).next(); @emailAvailable : SELECT count(1) FROM `Users` WHERE `email`=? LIMIT 1; or findUserByEmail(email).next(); SET @now=utc_timestamp(); if ( idAvailable && emailAvailable ){ SET @token=random_bytes(32); INSERT INTO `AuthToken` (`t`, `ip`, `token`) VALUES (@now, ip, @token); SEND {idAvailable, emailAvailable, @now, hex(@tokenStr)}; # 둘 다 사용가능하면 바로 sign-up 이 이루어지니 log 는 기록하지 않음. } else{ INSERT INTO `LogInLogs` (`user_i`, `t`, `ip`, `log`, `success`, `desc`) VALUES (1, @now, ip, "chk", false, "ID: "+id+" ["+idAvailable+"] and E-mail: "+email+" ["+emailAvailable+"] availability check."); # `user_i`=1 for anonymous. SEND {idAvailable, emailAvailable}; } ```/
  2. 중복 체크가 끝났다면 전체 데이터를 받아서 AuthToken 먼저 확인 후, TABLE `` 과 `` 에 ... ```[.linenums.lang-sql] SET @now=utc_timestamp(); SELECT * FROM `AuthToken` WHERE `t`=inputs.'tToken' and `ip`='ip from request'; SET @timeC=timediff(@now,`t`)<'01:00'; if ( `new` && hex(`token`)==inputs.'tokenStr' && timeC ){ UPDATE `AuthToken` SET `new`=false WHERE `t`='time' and `ip`='ip from request'; logs(1, @now, ip, "tkn", true, "tToken: "+t); # Token is verified. Continue; } else{ String errMsg="Sign-up error: "; if (token exists){ if (!new){ errMsg+="used token"; }errMsg+=", "; if (hex(token)!=inputs.'authToken'){ errMsg+="wrong token"; }errMsg+=", "; if (!timeC){ errMsg+="expired token"; }errMsg+=". "; } else{ errMsg+="no token. "; } errMsg+="ID: "+id+", E-mail: "+email+". tToken: "+t; INSERT INTO `LogInLogs` (`user_i`, `t`, `ip`, `log`, `success`, `desc`) VALUES (1, @now, ip, "tkn", false, errMsg); return; # Token is invalid. Break; } SET @salt=random_bytes(64); # or from JAVA : http://docs.oracle.com/javase/8/docs/api/java/util/Random.html # new java.util.Random().nextBytes(byte[] bytes) INSERT INTO `Users` (`id`, `pwd_salt`, `pwd`, `email`, `tReg`, `tLastVisit`, `ipReg`, `veriKey`) VALUES ('id', @salt, unhex(sha2(concat(@salt,'pwd_encrypted'))), 'e@mail', @now, @now, ip, hex(random_bytes(32))); SET @i=(SELECT `i` FROM `Users` WHERE `id`='id'); INSERT INTO `LogInLogs` (`user_i`, `t`, `ip`, `log`, `success`) VALUES (@i, @now, ip, "snu", true); UPDATE `UserClass` SET `count`=`count`+1 WHERE `class`=0; Gmail.sendVeriKey(email, id, veriKey); if (Error occured during registration){ DELETE FROM `Users` WHERE `id`=? and `pwd_salt`=? and `email`=? and `tReg`=? and `veriKey`=?; } ```/
  3. 등록한 e-mail 로 id/vefiKey 가 들어간 http://recoeve.net/account/verify/id/verification-Key 를 쏴 줌.
  4. 등록과정이 모두 잘 진행되었다면, "signed-up.html" 을 쏴줌. 또한 이 페이지에서 다시 log-in 페이지로 몇 초 후 이동되도록...
  5. User 가 log-in 상태에서 위 "verify" 링크를 누르면 인증 성공. Log-in 상태가 아니라면, log-in 페이지로 이동. ```[.linenums.lang-sql] CHECKING{ log-in id == index ? verification-Key == veriKey ? class == 0 ? datediff(@now, `tReg`) < 7 ? count('ip') < 20 ??? # IP check is pending. } then UPDATE `Users` SET `class`=6, `veriKey`=null WHERE `i`='i from request'; UPDATE `UserClass` SET `count`=`count`-1 WHERE `class`=0; UPDATE `UserClass` SET `count`=`count`+1 WHERE `class`=6; logs(user_i, now, ip, "vrf", verified); ```/
  6. 7일간 e-mail 인증이 없다면 DELETE user. ```[.linenums.lang-sql] # cf. timediff(utc_timestamp(),`tCreate`)<'24:00:00' # Using datediff(utc_timestamp(),`tCreate`)<7 # Regularly DELETE FROM `Users` WHERE 7<datediff(utc_timestamp(),`tReg`) and `class`=0; ```/
#### TABLE `Users` 가입 관련 정보? (regInfo: ip 같은거 저장해 놓을까? 이상한 접근 같은거 알려주게?) Timezone 은 javascript 로 처리할까나?
Users
```[.scrollable.lang-sql] # `UserClass` TABLE 을 먼저 CREATE 해야 함. CREATE TABLE `Users` ( `i` bigint #AUTO_INCREMENT by using `UserClass` (`class`=-1, `desc`="User index to put", `count`). , `id` char(21) NOT NULL UNIQUE #CHARACTER SET utf8 COLLATE utf8_general_ci , `email` varchar(50) NOT NULL UNIQUE #CHARACTER SET utf8 COLLATE utf8_general_ci , `pwd_salt` binary(128) NOT NULL #DEFAULT random_bytes(128) , `pwd` binary(64) NOT NULL , `pwd_iteration` int NOT NULL DEFAULT 10000 , `veriKey` varchar(64) #DEFAULT hex(random_bytes(32)) , `ipReg` varchar(32) NOT NULL , `tReg` datetime NOT NULL #DEFAULT utc_timestamp() , `tLastVisit` datetime NOT NULL #DEFAULT utc_timestamp() , `rmbdC` int DEFAULT 0 , `ssnC` int DEFAULT 0 , `class` int NOT NULL DEFAULT 0 #0: Not verified yet , `profile` varchar(1500) DEFAULT null , `settings` text DEFAULT null , `lang` char(3) DEFAULT null , `fin` bigint NOT NULL DEFAULT 0 #won base (localization handling by javascript) , PRIMARY KEY (`i`) , FOREIGN KEY (`class`) REFERENCES `UserClass` (`class`) ); # Since DEFAULT value must be constant, so random_bytes() and utc_timestamp() cannot be used in DEFAULT. `i`: Index, bigint (unsigned: 0 ~ 1844,6744,0737,0955,1615: ? $2^{64}-1$, and cf) int unsigned: 0 ~ 42,9496,7295) # AUTO_INCREMENT 로 하려고도 했으나, 내부적으로 어떻게 돌아가는지 (중간이나 마지막 데이터가 지워지면 어떻게 처리되는거지?) 자세한걸 파악하기 힘들어서 그냥 직접 구현. # ALTER TABLE `Users` AUTO_INCREMENT=100; # PRIMARY KEY # `i`=1 for anonymous, used in `LogInLogs`. INSERT INTO `Users` (`i`, `id`, `email`, `pwd_salt`, `pwd`, `tReg`, `ipReg`, `veriKey`, `tLastVisit`, `rmbdC`, `class`, `profile`) VALUES (1, 'anonymous', 'anonymous@recoeve.net', random_bytes(64), random_bytes(64), utc_timestamp(), 'recoeve.net', null, utc_timestamp(), 0, 6, 'Anonymous account. This is used in `LogInLogs`.'); # id case sensitivity check INSERT INTO `Users` (`i`, `id`, `email`, `pwd_salt`, `pwd`, `tReg`, `ipReg`, `veriKey`, `tLastVisit`, `rmbdC`, `class`, `profile`) VALUES (2, 'AnoNyMouS', 'anonymous2@recoeve.net', random_bytes(64), random_bytes(64), utc_timestamp(), 'recoeve.net', null, utc_timestamp(), 0, 6, 'Anonymous account. This is used in `LogInLogs`.'); # email case sensitivity check INSERT INTO `Users` (`i`, `id`, `email`, `pwd_salt`, `pwd`, `tReg`, `ipReg`, `veriKey`, `tLastVisit`, `rmbdC`, `class`, `profile`) VALUES (3, 'anonymous3', 'Anonymous@Recoeve.com', random_bytes(64), random_bytes(64), utc_timestamp(), 'recoeve.net', null, utc_timestamp(), 0, 6, 'Anonymous account. This is used in `LogInLogs`.'); `id`: User Id, char(21) (varchar로 할까? 3~21 letters are allowed.), NOT NULL UNIQUE # (Unique 해야만 함. ignore case?), 이걸 PRIMARY KEY로 할까? 검색은 UNIQUE KEY 설정 되어있어서 빠르긴 하겠지? `email`: E-mail Address # 암호화해서 저장해야 할듯? `pwd_salt`: Password Salt SELECT random_bytes(128); # User 별로 random 값 저장. AuthToken 값 이용. POST 통신할때 pwdEncrypt hash salt 로도 이용. # SELECT hex(random_bytes(128)); `pwd`: User Password, binary(64) UNHEX(SHA2(CONCAT(`pwd_salt`,`pwd`), 512)); # 암호화 해서 저장. // 128 (=512/4) words = 64 bytes (* 8 bit/byte) # 암호의 max-length는 64 (별 이유는 없음) 로. `veriKey`: Verification key, varchar(64) SELECT hex(random_bytes(32)); # 24시간 지나면 인증키 지우기? 다시 인증키 보내기도 가능하게? 일주일 후에도 사용자 인증이 없으면 아예 unregister? `ipReg` varchar(32) NOT NULL # IP at Registration stage `tReg`: Time of Registration, datetime, NOT NULL `tLastVisit`: Time of Last Visit, datetime, NOT NULL `rmbdC`: Count of remembereds, int (tinyint unsigned: 0 ~ 255), DEFAULT 0 # 5개 정도로 제약을 줘야할듯. `ssnC`: Count of sessions, int (tinyint unsigned: 0 ~ 255), DEFAULT 0 # 5개 정도로 제약을 줘야할듯. `class`: User Class, int, NOT NULL, FOREIGN KEY (`class`) REFERENCES `UserClass` (`class`) Change of class/Registration/Unregistration, of user must change `UserClass`. // Or can be checked directly by SELECT count(DISTINCT `class`) FROM `Users` ORDER BY `class` ASC | DESC; SELECT count(`class`) FROM `Users` GROUP BY `class` ASC | DESC; `profile`: 프로필 저장. `settings`: 잡다한 세팅들 한꺼번에 format 화 해서 그냥 넣어놓자. JSON 형식 쓸까? `lang`: 언어 선택: 'en', 'kr', 'ch', 'jp' 등등. 두 글자로만 하면 안좋을라나? 뭐 선택지는 javascript 로 full name 주면 되니까. // cookie 로도 처리? `fin`: Finantial 의 약자. 돈 거래 처럼. 뭔가 거래 형식으로 주고받을 수 있게 짜야겠음. ```/
Max of bigint is 1844,6744,0737,0955,1615 =? 2^{64}-1 ?. Password confirm 은 javascript 단에서 해결해주고... HTML form 에서 password type 은 암호화 되어서 post 전송되는거 맞나? 네트워크 중간에서 가로채는거에 대한 보안은 어찌 처리해 주는거지? 이쪽 보안이 엄청 취약한거 같은데... 나는 대충 hash 로 처리하긴 했음. #### TABLE `UserClass`: User Class Users are classified according to their behavior pattern. 'User class>=6' triggers. 'User class<=2' triggers.
UserClass
```[.linenums.lang-sql] // This table might be not needed. // Just for the fast/real-time statistics. CREATE TABLE `UserClass` ( `class` int NOT NULL , `desc` varchar(50) UNIQUE , `count` bigint , PRIMARY KEY (`class`) ); `class`: User class, int, NOT NULL, PRIMARY KEY `desc`: Description of the class, varchar(50) `count`: Total number of users in the class, bigint // For UPDATE UPDATE `UserClass` SET `count`=`count`+1 WHERE `class`=?; UPDATE `UserClass` SET `count`=`count`-1 WHERE `class`=?; // SELECT SELECT * FROM `UserClass`; ```/ class description count -2 Total number of accounts 0 -1 User index to put 100000000 0 Not verified yet 0 1 Abusing/Spam 0 2 Bad 2 0 3 Bad 3 0 4 Bad 4 0 5 Not sure 0 6 Initial 0 7 Real user 7 0 8 Real user 8 0 9 Real user 9 0 10 Sincere user 0


#### TABLE `Fin`: Financial Transaction 처리 잘 합시다 dev.mysql.com - START TRANSACTION, COMMIT, and ROLLBACK Syntax; and The Java™ Tutorials - Using Transactions; and tutorialspoint.com - JDBC - Transactions.
Fin
```[.linenums.lang-sql] CREATE TABLE `Fin` ( `giver` bigint NOT NULL , `taker` bigint NOT NULL , `time` datetime NOT NULL , `amount` bigint NOT NULL , `desc` varchar(200) , PRIMARY KEY (`giver`, `taker`, `time`) , FOREIGN KEY (`giver`) REFERENCES `Users` (`i`) , FOREIGN KEY (`taker`) REFERENCES `Users` (`i`) ); INSERT INTO `Fin` (`giver`, `taker`, `time`, `amount`, `desc`) VALUES (?, ?, ?, ?, ?); ```/
#### TABLE `DailyPoints`: Daily Points (보류?) Daily vote 같은 것도 생각할까? points 에 희소성이 있어야... 제대로 써먹을듯 하기도 한데... 23 시간 기준으로 points 10점 정도 (URI 한개에 1점씩만 줄 수 있음. 그러니 총 10개/23시간.) 를 추천하고 싶은 컨텐츠에 줄 수 있도록 해볼까나? 팬일 경우에는 자신이 좋아하는 아이돌 MV 에 하루 1점씩 계속 점수를 주는식의 게임 형식. points 는 개인화가 아니라 전체 평점(?) 같은거에 영향 주도록? 중동석을 좀 만들어 보자는건데 내가 지향하는 방식이 아닌가? 23시간 내에 이루어진 추천을 알고 있어야 할테니 에 dailyPointsHistory 같은걸 넣어놓아야 할듯? 최근 23시간 내의 추천만 저장해놓고 남은 추천수 확인하는 식으로. Comment 에 대한 vote up/down 이런데에 쓰여야 하나? Sim 올리는 용도로?
DailyPoints
```[.linenums.lang-sql] // 보류? CREATE TABLE `DailyPoints` ( `user` bigint NOT NULL , `cmter` bigint NOT NULL , `URI` bigint NOT NULL , `time` datetime NOT NULL , `amount` bigint NOT NULL , `desc` varchar(200) , PRIMARY KEY (`user`, `cmter`, `URI`) , FOREIGN KEY (`user`) REFERENCES `Users` (`i`) , FOREIGN KEY (`cmter`) REFERENCES `Users` (`i`) ); ```/
#### TABLE `EmailStat`: User Class Automated 가입이랑 abusing 막으려면 email stat 을 기록할 필요도 있는듯. 뭐 google, naver, daum 같은 인증된(?) 메일 계정만 받을수도 있곘지만 이건 아닌거 같고 ㅡ..ㅡ;;;; 어차피 여기서 제공하는 API 이용하면 automated 어렵지 않을거 같음. 그냥 stat 저장해놓고, 이상한 시도 있으면 걸러내는 식으로?
EmailStat
```[.linenums.lang-sql] CREATE TABLE `EmailStat` ( `emailHost` char(50) , `count` bigint DEFAULT 1 , PRIMARY KEY (`emailHost`) ); INSERT INTO `EmailStat` (`emailHost`) VALUES ("recoeve.net"); INSERT INTO `EmailStat` (`emailHost`) VALUES (?); ```/
#### TABLE `AuthToken`: IP & Token check One-time token 같은거 이용해서 프로그래밍적으로 계정 무한정 생성되는걸 막아야 할래나? 이건 급한게 아니니 나중으로 미룰까?
AuthToken
```[.linenums.lang-sql] CREATE TABLE `AuthToken` ( `t` datetime NOT NULL #DEFAULT utc_timestamp() , `ip` varchar(32) NOT NULL , `token` binary(128) NOT NULL #DEFAULT random_bytes(128) , `new` boolean NOT NULL DEFAULT true , PRIMARY KEY (`t`, `ip`) ); SET @now=utc_timestamp(); SET @token=random_bytes(128); SET @tokenStr=hex(@token); INSERT INTO `AuthToken` (`t`, `ip`, `token`) VALUES (@now, ip, @token); SELECT * FROM `AuthToken` WHERE `t`='time' and `ip`='ip from request'; # Token expires after 5 minutes. CHECK timediff(@now, `t`)<'00:05:00'; # But no DELETE for security reason. ```/
우선 아이디어는 로그인 페이지를 쏴줄때 (로그인 할때까지는 별 필요 없을듯하고 sign-up 에서 id/email check 할때 ajax 통해서 주면 될듯?), {'time', ip, 'random one-time authToken'} 을 저장해두고 {'time', 'random one-time authToken'} 을 포함한 'Log-in.html' 을 쏴줌. Log-in 요청이나 Sign-up 요청을 보낼 때 이 'random one-time authToken' 도 같이 보내야 요청을 받아주는 식. Token 은 일회용. 뭐 자동적으로 "매번 로그인 페이지 요청을 하고" Sign-up 요청을 하는 프로그램이라면 막을수는 없겠지만, 1차적으로 Sign-up 요청만 무한정 보내는건 막을 수 있기는 하고... ip를 저장해놓기 때문에 나중에라도 동일 ip에서 수많은 접근이 있었던 것은 걸러낼 수 있을 듯? (공유기 같은것들 때문에 동일 ip를 쓰는 사용자가 많을수도 있어서, ip 하나에 token 하나만 배정하는 식으로는 못할듯하고...) 그냥 `` table 에 `ip at registration stage` 도 저장해 놓고 판별할까? ``` SELECT count(*) FROM `Users` WHERE `ipReg`='ip of Sign-up request'; ```/ 위 결과가 10 이상이면 의심스러운 (suspicious) 상황일듯? 100 이상이면 거의 abusing 이 확실하고? Sign-up stage 에서 이거 판별해서 user class 를 다르게 시작해줘야 할듯? Abusing 계정들은 주기적으로 차단 및 삭제해 버리고. ### Log-in: Log-in process and 'Remember Me' 로그인 시도를 하면, idType userId userPwd rememberMe id | email ID/E-mail pwd_encrypted yes | nothing 와 같은 데이터가 보내지고, hidden으로는 ip log screenWidth screenHeight locationHref | Referer req.remoteAddress() web 640 480 http://recoeve.net 가 보내짐.
  1. 우선 "Referer==recoeve.net" 인지 확인? 필요없나? (iframe 으로 접근하는거 막기?? 우선 pending.)
  2. 모든 recoeve.net 사이트 접근시 cookie 확인. Session 이 있어야만 통과. 없다면 로그인 화면으로. (UserRemember 확인 과정은 여기서.) ```[.lang-sql] if (Cookie.I & Cookie.tCreate & Cookie.SSN & Cookie.token exist){ SET @now=utc_timestamp(); SELECT `session`=unhex(Cookie.SSN), `token`=unhex(Cookie.token) FROM `UserSession` WHERE `user_i`=Cookie.I and `tCreate`=Cookie.tCreate and timediff(@now, `tCreate`)<'3:00:00'; if (일치하면){ # session 로그인 통과. } if (Cookie 는 있었는데 일치하지 않았다면){ # pending (이런 일이 일어날 일이 거의 없을듯. Session 쿠키를 훔쳤다면 그냥 통과될거고;;;; 3시간 제한으로 어느정도 해결하자.) # INSERT INTO `LogInLogs` (`user_i`, `t`, `ip`, `log`, `success`, `desc`) # VALUES (@user_i, @now, ip, 'web', false, 'Session Log-in failed.'); } } else{ # Session cookie 가 없다면, window.location="http://recoeve.net/account/log-in?back=path" } ```/
  3. http://recoeve.net/account/log-in?back=path 으로 접속을 해오면, 우선 rmbd cookie 가 있는지 확인. ```[.linenums.lang-sql] if (Cookie.rmbdI & Cookie.rmbdT & Cookie.rmbdAuth & Cookie.rmbdToken exist){ SELECT `auth`=unhex(Cookie.rmbdAuth), `token`=unhex(Cookie.rmbdToken) FROM `UserRemember` WHERE `user_i`=Cookie.rmbdI and `tCreate`=Cookie.rmbdT; if (일치하면){ SET @now=utc_timestamp(); SET @newToken=random_bytes(32); UPDATE `UserRemember` SET `token`=@newToken, `tLast`=@now WHERE `user_i`=Cookie.rmbdI and `tCreate`=Cookie.rmbdT; Set-Cookie: rmbdToken=@token;max-age=30*24*60*60;path=account/log-in;domain=recoeve.net;HttpOnly # 추가적으로 session cookie 발급. INSERT INTO `LogInLogs` (`user_i`, `t`, `ip`, `log`, `success`, `desc`) VALUES (@user_i, @now, ip, 'web', true, 'Remembering you.'); } else{ DELETE FROM `UserRemember` WHERE `user_i`=Cookie.rmbdI and `tCreate`=Cookie.rmbdT; # 심각한 경고 INSERT INTO `LogInLogs` (`user_i`, `t`, `ip`, `log`, `success`, `desc`) VALUES (@user_i, @now, ip, 'web', false, '!!!'); # `tCreate` 와 `auth` 는 일치했다면, cookie 가 도난당했어서 이미 다른데에서 로그인을 성공했었을 가능성이 크고. 다 일치하지 않았다면, 그냥 단순 공격일 가능성이 큼. } } ```/
  4. rmbd cookie 가 없다면 로그인/가입 페이지를 쏴 줌. 'idType' 따라서 `` 에서 password 비교. (ID or E-mail 의 regEx 도 다시 테스트 할까? Javascript 변경해서 로그인 한건지 판별하게?) ```[.linenums.lang-sql] SELECT `i`, `pwd`=unhex(sha2(concat(`pwd_salt`, 'pwd_encrypted'))) FROM `Users` WHERE `idType of request`='userId from request'; SET @now=utc_timestamp(); SET @user_i=i; # if not found, i=0. ```/
  5. 맞다면 session cookie (HttpOnly) 발급. TABLE `` 에 session 저장. 개인 페이지로 이동. 틀릴 경우 TABLE `` 만 기록하고 로그인 실패 페이지. ```[.lang-sql] If (user is verified){ SET @session=random_bytes(32); SET @token=random_bytes(32); INSERT INTO `UserSession` (`user_i`, `tCreate`, `session`, `token`, `ip`) VALUES (@user_i, @now, @session, @token, ip); # with ";path=/;domain=recoeve.net;HttpOnly" Set-Cookie: I=@user_i Set-Cookie: tCreate=@now Set-Cookie: SSN=@session Set-Cookie: token=@token;max-age=3*60*60 개인페이지 전송. } else{ INSERT INTO `LogInLogs` (`user_i`, `t`, `ip`, `log`, `success`, `desc`) VALUES (@user_i, @now, ip, 'web', false, ''); } ```/
  6. (로그인 성공시) 'Remember Me' 가 체크되어 있었다면, TABLE `` 에 ```[.linenums.lang-sql] if ('Remember me' is checked){ SET @auth=random_bytes(32); SET @token=random_bytes(32); INSERT INTO `UserRemember` (`user_i`, `tCreate`, `auth`, `token`, `ip`, `log`, `sW`, `sH`) VALUES (@user_i, @now, @auth, @token, ip, 'web|app', @sW, @sH); INSERT INTO `LogInLogs` (`user_i`, `t`, `ip`, `log`, `success`, `desc`) VALUES (@user_i, @now, ip, 'web', true, 'Remembered'); # with ";max-age=30*24*60*60;path=account/log-in;domain=recoeve.net;HttpOnly" Set-Cookie: rmbdI=@user_i Set-Cookie: rmbdT=@now Set-Cookie: rmbdAuth=@auth Set-Cookie: rmbdToken=@token } else{ INSERT INTO `LogInLogs` (`user_i`, `t`, `ip`, `log`, `success`) VALUES (@user_i, @now, ip, 'web', true); } ```/
로그아웃 버튼이 눌리면 /account/log-out 으로 보내고, TABLE ``, TABLE `` ```[.linenums.lang-sql] # 모든 쿠키 지우기 # Session if (session cookie exists){ DELETE FROM `UserSession` WHERE ...; } # with ";path=/;domain=recoeve.net;HttpOnly" Set-Cookie: I=;max-age=-100 Set-Cookie: tCreate=;max-age=-100 Set-Cookie: SSN=;max-age=-100 Set-Cookie: token=;max-age=-100 # Remember Me if (rmbd cookie exists){ DELETE FROM `UserRemember` WHERE ...; } # with ";path=/account;domain=recoeve.net;HttpOnly" Set-Cookie: rmbdI=;max-age=-100 Set-Cookie: rmbdT=;max-age=-100 Set-Cookie: rmbdAuth=;max-age=-100 Set-Cookie: rmbdToken=;max-age=-100 ```/ #### TABLE `UserSession` 로그인이 이루어지고 나서 session cookie (HttpOnly) 를 이용해 session 을 유지시켜 줄 필요가 있는데... 여러 군데서 동시에 접속이 이루어질수도 있으니 테이블을 따로 만들어줘야 할듯?
UserSession
```[.linenums.lang-sql] CREATE TABLE `UserSession` ( `user_i` bigint NOT NULL , `tCreate` datetime NOT NULL #DEFAULT utc_timestamp() , `session` binary(32) NOT NULL #DEFAULT hex(random_bytes(32)) , `token` binary(32) NOT NULL #DEFAULT hex(random_bytes(32)) , `ip` char(32) NOT NULL , `tLast` datetime , FOREIGN KEY (`user_i`) REFERENCES `Users` (`i`) , PRIMARY KEY (`user_i`, `tCreate`) ); `session`: Session, binary(64), NOT NULL # Using hex(random_bytes(32)) `token`: Token (3 hours limit), binary(32) NOT NULL #DEFAULT hex(random_bytes(32)) `ip`: Secondary check. # IP 가 중간에 바뀌는 경우도 많아서 (공유기? 혹은 mobile) 곁다리로만 확인해야 할듯. # User-Agent 를 저장해 놓을까? 그닥 보안에 도움이 안될듯? req.headers().get("User-Agent"); // Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.103 Safari/537.36 ```/
#### TABLE `UserRemember` Remember Me 구현을 위해서 뭔가 one-time authorization token 을 만들어야 할듯?
UserRemember
```[.linenums.lang-sql] CREATE TABLE `UserRemember` ( `user_i` bigint NOT NULL , `auth` binary(32) NOT NULL #DEFAULT hex(random_bytes(32)) , `token` binary(32) NOT NULL #DEFAULT hex(random_bytes(32)) , `ip` char(32) , `log` char(3) , `sW` smallint unsigned , `sH` smallint unsigned , `tCreate` datetime NOT NULL #DEFAULT utc_timestamp() , `tLast` datetime , PRIMARY KEY (`user_i`, `tCreate`) , FOREIGN KEY (`user_i`) REFERENCES `Users` (`i`) ); `auth`: Authorization, binary(32), NOT NULL # Using hex(random_bytes(32)) `token`: One-time Token, binary(32), NOT NULL # Using hex(random_bytes(32)) `ip`: ip address, char(32) # req.remoteAddress() `log` char(3) # web, mob, app 등 저장. `sW` smallint unsigned # Screen Width `sH` smallint unsigned # Screen Height `tCreate`: Time of Creation, datetime, NOT NULL DEFAULT utc_timestamp() # 24시간 지나면 인증키 지우기? 다시 인증키 보내기도 가능하게? 일주일 후에도 사용자 인증이 없으면 아예 unregister? # Regularly 60일이 지난 인증키 지우기. DELETE FROM `UserRemember` WHERE 60>datediff(utc_timestamp(),`tCreate`); `tLast`: Time of Last use of authToken, datetime ```/
'Remember Me' 를 check 하고 로그인을 성공했다면,
  1. asdf
창을 닫고 다시 로그인할때
  1. Cookie에 저장된 {`user_i`, `authToken`, `tCreate`, `tLast`}를 확인하고, 없다면 'Log-in.html' 창.
  2. 있다면 DB에 저장된 데이터와 비교 시작.
    SELECT * FROM `UserRemember` WHERE `user_i`='user_i from Cookie' and `tCreate`='tCreate from Cookie';
  3. 우선 `tCreate`가 60일 내의 것인지 확인. 아니라면 DELETE the row. 이후 "60일이 지나서 다시 로그인을 필요로 한다."는 msg가 포함된 'Log-in.html' 창.
req.remoteAddress() 로 저장되어 있는 `ip`랑 일치하는지 확인, javascript로 {'host', `log`, `sW`, `sH`} 보내서 일치하는지 확인 후 인증. (req.headers() 의 Host는 확인할 필요 없겠지? Javascript로 확인하는 host 도 확인할 필요 없는건가?) 인증 실패하면 `UserRemember` table 에서 관련 `authToken` row 삭제. 인증에 성공하면 `authToken`, `tLast` UPDATE. 인증 되었다면, 창을 닫지 않고 remembering 하는 로그아웃을 누르면 `authToken` 삭제. #### TABLE `LogInLogs`: Log-in Logs 이상한 접속시도들 걸러내려면 저장해 놓긴 해야할거 같은데... 일정 기간 1년 정도만 저장해 놓고 오래된 데이터는 버려도 될듯?
LogInLogs
```[.linenums.lang-sql] CREATE TABLE `LogInLogs` ( `user_i` bigint NOT NULL , `t` datetime NOT NULL , `ip` varchar(32) NOT NULL , `log` char(3) NOT NULL , `success` boolean NOT NULL , `desc` varchar(255) , PRIMARY KEY (`user_i`, `t`) , FOREIGN KEY (`user_i`) REFERENCES `Users` (`i`) ); `user_i`: User index, bigint, NOT NULL, FOREIGN KEY (`user_i`) REFERENCES `Users` (`i`) `t`: Time of log-in try, datetime, NOT NULL `ip`: From which ip, varchar(32), NOT NULL // 이거 저장 가능한건가? // req.remoteAddress() 저장하면 될듯? `success`: Result of log-in, boolean, NOT NULL `log`: Log try at, char(3), NOT NULL # web, mob, app 등. `desc`: Description, varchar(255) # 간단한 설명을 적어놓자. // 이정도만 저장해놔도 되겠지? // To be checked, SELECT `t`, `ip`, `success` FROM `LogInLogs` WHERE `user_i`=? LIMIT 0, 30; SELECT `t`, `ip`, `success` FROM `LogInLogs` WHERE `user_i`=? ORDER BY `t` DESC LIMIT 0, 30; SELECT `t`, `ip`, `success` FROM `LogInLogs` WHERE `user_i`=(SELECT `i` FROM `Users` WHERE `id`=?) ORDER BY `t` DESC LIMIT 0, 30; ```/
### Follows, Mentions, and Blocks (보류)
이건 하지 말자. 그냥 vote up/down 으로 sim 을 늘리거나 줄이는 식으로 하고... 너무 트위터나 다른 서비스 따라하는 거인듯. Recoeve 의 기본 철학 (자동으로 소통 이어주기, block 없애기, abusing 막기, minor report 존중하기, slow/sincere communication, one way talk 지양하기) 과도 안맞고.
자동으로 자신과 비슷한 사람들을 찾아주는 서비스를 지향하는거라서 따로 follow 같은 기능을 넣거나 mention 같은 기능을 어떻게 구현할지는 아직...? 만들긴 해야할거 같은데, 고민이 필요할듯. ```[.linenums.lang-sql] CREATE TABLE `Follows` ( `i` bigint NOT NULL , `follows` bigint NOT NULL ); ```/ ```[.linenums.lang-sql] CREATE TABLE `Blocks` ( `i` bigint NOT NULL , `blocks` bigint NOT NULL ); ```/ ```[.linenums.lang-sql] CREATE TABLE `Mentions` ( `i` bigint NOT NULL , `mentions` bigint NOT NULL , `cmt` comment id , `read` boolean DEFAULT('false') ); ```/ Users 테이블에도 nFollowings, nFollowers, nBlocks 같은 것들이 필요할것도 같고... ## 개인 페이지 개인 페이지의 path 는 "/user/id" 이런식으로 구상해 놓을까나? ### Reco 만을 위한 페이지 Reco 를 할때 페이지에서 text 를 복사하거나 하면서 작성하는 경우가 많을텐데.... 아예 browser 처럼 페이지를 하나 구성해줘서, 제일 위에 http-uri 넣고 엔터치면 iframe 으로 아래에 보이게 하고. Reco, Indifferent, Later 등 버튼 보이게. Category, Points, Title, Description, Comment 작성란도 mouseover 하면 나타나도록? 아예 사이드/탑에 고정시켜 놓을까? 아무튼 pure 하게 이런 reco 를 위한 페이지를 만들어 줘야 할듯. ## Contents ### TABLE `Recos` URI based 평점 변화를 모두 기록해야 할까? 마지막 평점만 저장해야 할까? => 마지막 평점만 저장하자. 너무 복잡해짐. Reco 를 지우더라도 `uri`, `tFirst` 는 남겨놔야 할듯? Abusing 할때 지웠다가 똑같은 reco 를 다시 작성하면 recentest recoers 에 젤 위에 뜨기 때문에? 아닌가? `tLast` 기준으로 recentest recoers 뽑아내나? Reco 에 up/down vote 가능하게? Reply 도 가능하게???
Recos
```[.linenums.lang-sql] CREATE TABLE `Recos` ( `user_i` bigint NOT NULL , `uri` varchar(255) NOT NULL , `tFirst` datetime NOT NULL , `tLast` datetime NOT NULL , `cats` varchar(255) NOT NULL , `title` varchar(255) , `desc` text # 65,535 bytes , `cmt` text # 65,535 bytes , `val` varchar(10) , `vUp` int DEFAULT 0 , `vDw` int DEFAULT 0 , `nViews` int DEFAULT 0 #, `open` tinyint DEFAULT 0 , PRIMARY KEY (`user_i`, `uri`) , FOREIGN KEY (`user_i`) REFERENCES `Users` (`i`) ); SELECT * FROM `Recos` WHERE `user_i`=? and `uri`=?; INSERT INTO `Recos` (`user_i`, `uri`, `tFirst`, `tLast`, `cats`, `title`, `desc`, `cmt`, `val`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); `user_i`: bigint # 이거 bigint 로 바뀌면 update 가 복잡해질수도? 그냥 처음부터 bigint 로 해버려??? 아니면 나중엔 user identifier 로 `user_i2` int? smallint? 를 추가해 버리면 될듯도... `uri`: Uniform Resource Identifier # 일반적으로 "http-uri" 가 되겠지만, 어떤 identifier 든 올 수 있게 할까? `tFirst`: Time of the first reco on this content `tLast`: Time of the last update of reco on this content `cats`: Categorys # ex) Music--K-pop;Music--2014;Some tags # 어떤식으로 디자인 할지 고민 좀 해봐야 할듯. `title` varchar(255) # "#default" 가 입력되어 있으면 가장 많이 입력된 (`URIstatDefTitle` 참조해서) title 을 뽑아옴. `desc` text # Default 로도 들어가도록 하고 싶은데... `cmt`: Comment, text (최대 65535 문자길이) `val`: Recommendation value, Points, varchar(10) # ex) 10/10, 3.5/5, 6/10 # Value 없이 reco 할수도 있긴 함. `open`: Openness, tinyint # default 를 뭘로 하지? 완전 공개? 초반엔 공개할 수 있는것만 reco 하도록 할까나? 나중에 개인적인 것들도 reco 할 수 있게 update 해주고? # y=a*x+b => a=floor(y/x); b=y%x; # 10 : open. Recommendation 이 갈 때, id/comment 가 그대로 보임. # 5 : anonymously open. Recommendation 이 갈 때, id 는 보이지 않고 comment 등만 전달 됨. 내 페이지에 오면 볼수는 있음. # 0 : secret. Sim 만 계산되고 내 comment 는 전달되지 않음. 내 페이지에 오더라도 비공개. 나만 볼 수 있음. # -? : 내 페이지에 오면 누구나 볼 수 있음. # -10 : 내 페이지에 "내가 허가한 사람"이 왔을때 볼 수 있는가? (이건 너무 복잡해질듯? 바로 구현은 안하더라도 생각해 놓기는 하자.) # -19 : 내 페이지에 내가 왔을때에만 볼 수 있음. 내 comment 는 익명으로 전달됨. # -20 : 내 페이지에 내가 왔을때에만 볼 수 있음. 내 comment 도 전달 안됨. # -28 : 내 페이지에 내가 왔을때에도 아이디/비번으로 본인확인을 한번 더 거쳐야 함. 내 comment 는 익명으로 전달됨. # -29 : 내 페이지에 내가 왔을때에도 아이디/비번으로 본인확인을 한번 더 거쳐야 함. 내 comment 도 전달 안됨. # -30 : 내 페이지에 내가 왔을때에도 아이디/비번으로 본인확인을 한번 더 거쳐야 함. 내 val 도 전달 안됨. (따라서 weighted stat 에 반영이 안됨. Overall stat 에는 적용 됨. 이건 서버내에서 이루어지니까. But 암호화해서 서버에 저장하는건 아님. 접근권한만 외부에 안줄뿐.) # 최소공개 원칙으로? cat 에도 open 정도가 적혀있을텐데... 둘 중 더 적은 공개 따르도록 동작? ```/
처음 reco 가 작성되면, 에 저장하고 defaults 관련해서도 다 update 하고, similarity 도 계산해야 함. (DB 적으로 해야하는 연산이 꽤 많네.) 새로운 cat 이면 CatList 도 update 해야하고, 해당 cat 의 UriList 도 업데이트 해야 함. Reco 가 수정되거나 삭제될때도 defaults 값들 update 해야하고, similarity 도 update 해야 함. 같은 uri 에 대한 reco 가 이미 있는데, reco 시도가 있을때도 다뤄줘야 함. #### TABLE `DeletedRecos` 지운 Reco 들을 어느정도 저장해 놓을 필요도 있을듯? 모든 변화를 저장해 놓을까? `cats`, `val` 부분 변화들만 저장하는게 좋을듯. 너무 저장할게 많아짐. (뭐 text 만이라서 용량을 그리 차지할거 같진 않지만.. 어차피 title/desc/cmt 변화를 추적하고 싶은 사람은 거의 없을듯? 잊고 싶은 사람이 많고.) ```[.linenums.lang-sql] CREATE TABLE `Recos` ( `user_i` bigint NOT NULL , `uri` varchar(255) NOT NULL , `tDel` datetime NOT NULL , `cats` varchar(255) NOT NULL , `val` varchar(10) , PRIMARY KEY (`user_i`, `uri`, `tDel`) , FOREIGN KEY (`user_i`) REFERENCES `Users` (`i`) ); ```/ #### Buttons : Reco, Indifferent, Later 기본적으로 동작은 다 Reco 이긴 한데... one-버튼으로 자신이 관심 없는 것들 (Indifferent) 과 당장은 시간이 없어서 못읽었고, 평가 보류, 카테고리 보류 인 것들 (Later) 을 자동으로 Category 넣어서 one-클릭으로 reco 가 되도록 해줘야 겠음. Category 에 "Indifferent", "Later" 자동으로 넣어주는 식으로만 동작하게 하면 될듯? 그리고 sim 계산할때 이걸 반영할까나? 같은걸 Indifferent 에 넣은 사람은 비슷한 사람이라고 생각할수도 있으니? Category 가 갈려서 전체 Recos 에 대한 sim 계산하는거 아니면 영향이 없긴한데... 그냥 구현하지 말자 이건. 뭐 나중에 완전 customizing 시켜주고 싶으면, 본인이 만든 버튼으로 본인이 원하는 방식대로 1 버튼이 동작하도록 설계해 줄수도 있음. (그런데 비슷한 유저 경험을 공유하게 하려면 이렇게 안해주는게 더 좋을수도 있긴 한데... 뭐 하더라도 급한거 같지 않으니 나중에 하자.) #### Description and Comment format: SEE (Super Easy Edit?) Description 과 Comment 에 적절한 문법을 만들어서 쓸 수 있게 해줘야 할듯. 음악 같은 경우는 "Singer, Title, Dancers, Writer, Lyrics" 등이 있을 수 있는데... 문법은 좀 깊게 고민해봐야 할거 같은데... Comment 등 처음에 잘못 설정해 놓으면 제대로 못 써먹을듯. ```[.linenums] /* Multi-line Comment 도 가능. */ // Comment 도 가능하게 하자. #singer 아이유 (IU) #title 좋은 날 #Writer 작곡가 누구누구, 누구누구가 편곡 등. #Lyrics 이건 여러줄이 될듯 이런식으로... 쭉쭉쭉쭉쭉쭉.... 한칸이 띄어지기도 하고? span tag 같은것도 들어갈 수 있게? 나머지 tag 는 막아야 할듯? Case-insensitive 한 "#command". #Date 2014-11-15 #기타 등등등. #related // 관련된 다른 동영상들 한꺼번에? 뭐 따로 reco 를 할수도 있겠지만, 모아서 보고 싶을 때도 있으니까. 평가는 같을테고...? 연관 URI 추천해 줄때도 써먹을 수 있을거 같긴 한데... 내 neighbor 의 desc, cmt 를 일일이 살펴보면서 가져와야 할래나? 아니면 아예 해당 URL 에 대한 reco 가 없으면 지금의 val 과 같은 val 로 저장해 놓자. URL 어쩌구 저쩌구 URL 저쩌구 어쩌구 등등등. URL 얼씨구 절씨구. ```/ 뭐 대충 위와같이 작성하면, 알아서 잘 편집해서 보여주는걸로? 작성할땐 sublime 처럼 자동완성 기능 만들어줘야 할듯. 새로운 줄의 시작이 '#' 일 경우 처리 등. ### TABLE `CatList`: Category List User 가 Category 를 만들고 정리할텐데... 순서나 이런건 알파벳 순이 아니라 user 가 정하는 경우가 많을테니 순서를 정하는 UI 를 좀 생각해보고, 각 정렬 옵션은 나중에 구현을 생각해 봅시다. 어떻게 category 를 생성할까? 그냥 reco 에 새로운 cat 를 넣을 경우 새로운 category 를 생성해줘야하고, 순서는 제일 뒷쪽에 넣읍시다. (이전 category 들이 알파벳 순이 아닌경우도 있을 수 있는데, 이 경우 알파벳 순으로 집어넣을수가 없음. if 문 돌려서 알파벳 순인지 파악하고 할수도 있을텐데;;;;; 이건 뭐 우연히 알파벳 순서일때도 있을테고, 일관적이지 않아서 user 가 헷갈려할듯도 하니 그냥 제일 뒷쪽에 넣자.) CatList 가 있는 화면에서 삽입 같은 걸로 새로운 category 를 생성할수도 있을테고, 지울수도 있을테고 (이 땐 이 안에 reco 가 있을경우 reco 를 모두 지워야만 category 가 지워지도록. 해당 cat 안의 마지막 reco 가 지워지면 cat 도 지울지 물어봐야 할듯?), 이름만 수정할때도 있을거고, 위치를 조정 (Ex: "A--B" 에 있던걸 "C--D" 로 옮김.) 할때도 있을듯함. 뭐 다 구현해줘야 가능한 것이겠지만...
CatList
```[.linenums.lang-sql] CREATE TABLE `CatList` ( `user_i` bigint NOT NULL , `listName` varchar(255) NOT NULL DEFAULT 'upToDate' , `catList` mediumtext # 16,777,215 bytes , PRIMARY KEY (`user_i`, `listName`) , FOREIGN KEY (`user_i`) REFERENCES `Users` (`i`) ); INSERT INTO `CatList` (`user_i`, `catList`) VALUES (?, ?); SELECT * FROM `CatList` WHERE `user_i`=? and `listName`=?; "[Physics/Math] [Physics/Math]--Physics [Physics/Math]--Physics--Classical [Physics/Math]--Physics--Quantum [Physics/Math]--Physics--Relativity [Physics/Math]--Math [Physics/Math]--New Ideas in Physics [Physics/Math]--News [Physics/Math]--Etc. [IT/Programming] [IT/Programming]--HTML related [IT/Programming]--Algorithm and Database [IT/Programming]--Etc. [Music/Break]--music--2014--best--best of best [Music/Break]--music--2015 [Music/Break]--music--IU [Music/Break]--music--KARA" Reco를 작성하면 위와 같은 Category list 들이 쌓일텐데, 이걸 잘 정리해서 보여주려면 순서도 기억하고 있어야 하겠고 전체 cat list 도 빨리 뽑아낼수 있어야 함. Tab, enter 를 이용해서 간단하게 저장해놓고 update 도 쉽게쉽게 합시다. "[Physics/Math] Physics Classical Quantum Relativity Math New Ideas in Physics News Etc. [IT/Programming] HTML related Algorithm and Database Etc. [Music/Break] music 2014 best best of best 2015 IU KARA" 위와 같이 저장해서 처리. (상위폴더 없이 하위 폴더들이 만들어질수는 없음. 하위폴더 cat 이 만들어지면 자동으로 상위폴더까지 만듭시다. eg. Cat "[Music/Break]" 없이 Cat "[Music/Break]--music--2014" 가 처음 만들어졌다면, "[Music/Break]" 먼저 만들고, "[Music/Break]--music" 도 만들고, "[Music/Break]--music--2014" 를 만드는식.) `listName` 은 user 가 다른 방식의 정렬을 원해서 저장해 놓을 수 있도록. 뭐 대부분 쓰던것만 쓸테니 'upToDate' 하지 않은 `CatList`는 'upToDate' 참고해서 새로 생긴 cat 은 적절한 위치에 삽입. 사라진 cat 은 지우는 식으로 불러들일때 처리하자. 즉, 하나는 'upToDate' 가 있어야 함. ```/
우선 이런 Category list 가 있어야 하겠고. ### TABLE `UriList`
UriList
```[.linenums.lang-sql] CREATE TABLE `UriList` ( `user_i` bigint NOT NULL , `cat` varchar(255) NOT NULL #, `open` tinyint DEFAULT 100? , `uriList` mediumtext # 16,777,215 bytes , PRIMARY KEY (`user_i`, `cat`) , FOREIGN KEY (`user_i`) REFERENCES `Users` (`i`) ); INSERT INTO `UriList` (`user_i`, `cat`, `uriList`) VALUES (?, ?, ?); SELECT * FROM `UriList` WHERE `user_i`=? and `cat`=?; uriList 에 해당 user_i--cat 에 해당하는 "uri1 uri2 uri3" 등이 들어감. ('\n' separated) 순서는 user 가 원하는 순서대로 저장. 마우스 드래그 앤 드랍 등으로 위치 조정 가능하게 해줘야 할듯? ```/
URI list 의 순서도 user 가 정하는 경우가 많을테니 순서대로 URI list 저장해놓고 보여주는 식으로... 개인화 된 추천을 해줄때에도 이미 내가 Reco 한 URI 인지 확인할 필요는 있을테니, 전체 URI list 를 javascript 에 가지고 있는게 필요할듯? (계속 reco 가 쌓이면 user 당 reco 수가 얼마나 되려나? 많아지는 경우를 대비해서라도 URI list 를 매번 쏴줄수는 없을듯?) 그냥 그 cat 에 해당하는 URI list 만 그때그때 쏴주고 추천되는 uri 뽑아서 server 에서 확인하고 추천을 쏴주는 식이 되어야 할듯. ### TABLE `URIstat`
URIstat
```[.linenums.lang-sql] CREATE TABLE `URIstat` ( `uri` varchar(255) NOT NULL , `recentRecoers` text DEFAULT NULL , `recentRecoersWithVal` text DEFAULT NULL , `tUpdate` datetime DEFAULT NULL , `sumV100` bigint DEFAULT 0 # sum of round(value*100) , `nV` bigint DEFAULT 0 # number of values (not including null, i.e. equal to the sum c0~9.) , `cIndif` bigint DEFAULT 0 # val=null, hasCatIndif() : in Cat (`indifferent`) , `cLater` bigint DEFAULT 0 # val=null, else hasCatLater() : in Cat (`later`) , `cNull` bigint DEFAULT 0 # val=null, else , `c0` bigint DEFAULT 0 # 0<=`val`*10<=1 , `c1` bigint DEFAULT 0 , `c2` bigint DEFAULT 0 , `c3` bigint DEFAULT 0 , `c4` bigint DEFAULT 0 , `c5` bigint DEFAULT 0 , `c6` bigint DEFAULT 0 , `c7` bigint DEFAULT 0 , `c8` bigint DEFAULT 0 , `c9` bigint DEFAULT 0 # 9<`val`*10<=10 , PRIMARY KEY (`uri`) ); SELECT * FROM `URIstat` WHERE `uri`=?; INSERT INTO `URIstat` (`uri`, `tUpdate`) VALUES (?, ?); `c0`~`c9`: 10개 정도로 잘라서 stat 저장. 매번 SELECT count() 같은걸로 계산하는게 더 오래걸릴테니. `sumV` double or `sumV100` bigint # 100억 에 1 더하는 연산 같은것도 error 없이 잘 이루어 질래나? Error 뜰거 같으면 bigint 같은 datatype 쓰는게 더 나을듯도 한데... ```/
Personalized stat (stat of neighbors with weight) 는 javascript 로 user-side 에서 계산하도록 하는게 좋을듯. // 아무것도 입력 안했을 경우는 candidate 중 value 가 가장 높은거 default 로 쏴주고, 처음 입력할때는 candidate 6 개를 차례대로 보여주고 선택 가능하도록? 선택했다면 이 candidate 의 vote 를 올리고 복사해서 user reco 에 저장? 이상한 candidate 는 down vote 가능하게 해주고. 선택을 하지 않았어도 up vote 도 가능하게 해버릴까? 사소한 오타가 있어서 나중에 수정되면 반영 가능한가??? 복사해서 가져갔다면 반영이 안될텐데... Default candidates 에서 오타 수정은 어떻게 하지? 새로운 candidate 가 나타나면 어떻게 update 하고? 처음에 candidate 는 어떻게 뽑고??? 새로운 좋은 default candidate 가 나와도 lock-in 효과가 있도록 algorithm 이 동작하면 올라가기가 힘들텐데... 이것도 아예 vote 가 아니라 value 형태로 평가하게 해버릴까? 중복 투표가 안되게 하려면 vote 도 어딘가 저장해놓긴 해야하네;;;;; ### Defaults Default 값 (category, title, description) 관련해서는 stat TABLE 을 따로 만들어 놓는것이 좋아보이긴 하는데... Crawler 를 만들기는 그렇고... (뭐 정확하지도 않더만, 손도 많이 갈테고.. 그냥 user data 모아서 처리해주는식으로 짜자.) #### TABLE `URIstatDefCat`: Statistics of URI's default category Category 관련해서는 평가가 이루어지는게 아니니 그냥 count 만 저장해놓으면 될듯? 에서 reco 저장할때 (`uri`, `cat`) 뽑아와서 도 update. (`cat`=null or "" 인건 빼고. 이 경우는 추천해줄 category 도 없을테니?)
URIstatDefCat
```[.linenums.lang-sql] CREATE TABLE `URIstatDefCat` ( `uri` varchar(255) NOT NULL , `cat` varchar(255) NOT NULL # Default category candidate , `count` bigint DEFAULT 1 , PRIMARY KEY (`uri`, `cat`) ); SELECT * FROM `URIstatDefCat` WHERE `uri`=? and `cat`=?; SELECT * FROM `URIstatDefCat` WHERE `uri`=? ORDER BY `count` DESC LIMIT 5; # (`uri`, `cat`) set 이 없다면, INSERT INTO `URIstatDefCat` (`uri`, `cat`) VALUES (?, ?); # (`uri`, `cat`) set 이 이미 있다면, UPDATE `URIstatDefCat` SET `count`=`count`+1 WHERE `uri`=? and `cat`=?; ```/
#### TABLE `URIstatDefTitle`: Statistics of URI's default title Title 관련해서도 평가가 이루어지지는 않을듯. 에서 reco 저장할때 (`uri`, `title`) 뽑아와서 도 update. (`title`=null or "" 인건 빼고. 이 경우는 추천해줄 title 도 없을테니?)
URIstatDefTitle
```[.linenums.lang-sql] CREATE TABLE `URIstatDefTitle` ( `uri` varchar(255) NOT NULL , `title` varchar(255) NOT NULL # Default title candidate , `count` bigint DEFAULT 1 , PRIMARY KEY (`uri`, `title`) ); SELECT * FROM `URIstatDefTitle` WHERE `uri`=? and `title`=?; # (`uri`, `title`) set 이 없다면, INSERT INTO `URIstatDefTitle` (`uri`, `title`) VALUES (?, ?); # (`uri`, `title`) set 이 이미 있다면, UPDATE `URIstatDefTitle` SET `count`=`count`+1 WHERE `uri`=? and `title`=?; ```/
#### TABLE `URIstatDefDesc` and `RecoDefDesc`: Statistics of URI's default description Description 관련해서는 평가도 이루어 지도록 해놔서 refer 가 가능하도록 design 해야 할듯? 그냥 refer 만 하는 사람도 많을테니 많은 평가가 이루어지지는 않을듯하고... 새로운 좋은 description 이 떴을때, 사람들이 많이 refer 할 수 있도록 하려고 value 가 가능하도록 해 놓음. 뭐 일부러 찾아서 description refer 를 바꾸는 경우는 많지 않을테니 본인이 본인 desc 평가를 한걸 시작으로 neighbor 를 통해 전파되어야 할듯도? Description 에 "#referDesc user_desc" 이 들어가도록 명령어를 만들까나?
RecoDefDesc
```[.linenums.lang-sql] CREATE TABLE `RecoDefDesc` ( `user_i` bigint NOT NULL , `user_desc` bigint NOT NULL , `uri_desc` varchar(255) NOT NULL , `val` varchar(10) NOT NULL , PRIMARY KEY (`user_i`, `user_desc`, `uri_desc`) ); ```/
URIstatDefDesc
```[.linenums.lang-sql] CREATE TABLE `URIstatDefDesc` ( `user_i` bigint NOT NULL , `uri` varchar(255) NOT NULL , `desc` text NOT NULL # Default description candidate , `count` bigint DEFAULT 1 , `nV` bigint DEFAULT 0 # number of values (equal to the sum c0~9.) , `sumV100` bigint DEFAULT 0 # sum of round(value*100) , `c0` bigint DEFAULT 0 # 0<=`val`*10<=1 , `c1` bigint DEFAULT 0 , `c2` bigint DEFAULT 0 , `c3` bigint DEFAULT 0 , `c4` bigint DEFAULT 0 , `c5` bigint DEFAULT 0 , `c6` bigint DEFAULT 0 , `c7` bigint DEFAULT 0 , `c8` bigint DEFAULT 0 , `c9` bigint DEFAULT 0 # 9<`val`*10<=10 , PRIMARY KEY (`user_i`, `uri`) ); ```/
## Similarity: Neighbors of your kind Another you, people of your kind, friends. Find yourself. Agenda setting from your kind. Anti-abusing. 확증편향은 조심. 반대되는 의견들도 연결시켜줘야... ### TABLE `Neighbors` User_from 의 sim neighbor 로 몇명 정도가 적정할까나? 각 cat 당 1,000명 정도 limit?
Neighbors
```[.linenums.lang-sql] CREATE TABLE `Neighbors` ( `user_i` bigint NOT NULL , `cat_i` varchar(255) NOT NULL , `user_from` bigint NOT NULL , `cat_from` varchar(255) NOT NULL , `sumSim` bigint NOT NULL , `nSim` int NOT NULL , `simAvg100` int NOT NULL , `tUpdate` datetime NOT NULL , PRIMARY KEY (`user_i`, `cat_i`, `user_from`, `cat_from`) ); `tUpdate`: Time of update. `sumSim`: Similarity user_i--cat_i of from user_from--cat_from, bigint, NOT NULL // sim: (long)0~100 (100 이 가장 비슷한 것을 나타냄.) `nSim`: Number of contents used to calculate the similarity, int, NOT NULL `simAvg100` = Math.round(100*sumSim/(nSim>10?nSim:10)); # Get an neighbor SELECT * FROM `Neighbors` WHERE `user_i`=? and `cat_i`=? and `user_from`=? and `cat_from`=?; # Get neighbors SELECT * FROM `Neighbors` WHERE `user_from`=? and `cat_from`=?; # Get neighbors ORDERED by simAvg100 SELECT * FROM `Neighbors` WHERE `user_from`=? and `cat_from`=? ORDER BY `simAvg100` DESC; #LIMIT 100? # Get followers SELECT * FROM `Neighbors` WHERE `user_i`=? and `cat_i`=?; # Put an Neighbor INSERT INTO `Neighbors` (`user_i`, `cat_i`, `user_from`, `cat_from`, `sumSim`, `nSim`, `simAvg100`, `tUpdate`) VALUES (?, ?, ?, ?, ?, ?, ?, ?); ```/
Reco 를 나중에 수정하기도 하는데 이걸 제대로 처리할 수 있나?? Sim 을 update 해야 하는 상황은...
  • 새로운 reco 를 작성. 나의 neighbor 와 내가 neighbor 인 사람들의 sim 을 update 해야함. 또한 새로운 사람들 (내가 reco 한 uri 의 recentest recoers) 과의 sim 도 계산해서 neighbor 를 update 해야하고.
  • `uri` 를 바꿀수도 (이건 지우고 다른 reco 를 작성한 것과 같을듯) 아예 reco 를 지울수도 있고. 지우는 경우도 neighbor 의 sim 을 update 해야 함.
  • `cat` 를 수정할수도 있음. 내 cat (+super cats) 의 sim 을 update 해야 함.
  • `val` 를 수정할 경우도 sim 이 수정되어야 함.
  • `cmt` 만 수정할 때에는 sim 의 변화는 없음.
  • `uri`, `cat`, `val` 모두를 수정할때가 복잡해 지는데... (기존걸 지웠다가 새로운 reco 를 작성하는 걸로 모두 처리 가능할듯.)
Reco 를 작성하거나 수정할때마다 바로바로 sim 을 update 해야 할듯? 이러면 쉽게 될수도 있을거 같긴 한데... (Error 가 발생하면 어찌 해결할 수 있나? 나와 neighbor 가 거의 동시에 reco 를 작성하거나 수정하면?) ## Personalized recommendation 그동안 저장된 neighbors 를 바탕으로 각 cat 별로 새로운 reco 들을 추천해줄 수 있음. Neighbor 를 명확하게 하기 위해서는 좋아하는 컨텐츠 (user 의 val 이 높은 것) 싫어하는 컨텐츠 (user 의 val 이 낮은 것) 이 적절하게 섞여있어야 함. 또한 자신이 관심은 있지만 이런 저질스러운 컨텐츠에는 낮은 점수를 줘서 평점을 떨어트려 놓고 싶다거나, 가끔 내가 싫어하는 것들은 뭐가 있을까를 확인하고 싶기도 할테니, "내 예상 평점이 높은 것들 3개 + 예상 평점이 낮은 것들 1개" 정도로 섞어서 cat 별로 보여주는게 좋아보임. Home (or Personalized recommendations) 화면에서 depth 1 cat 별로 "3+1" 정도의 recommendation 을 보여줍시다. 버튼 두개가 있어서 더 확장해서 볼수도 있도록... 최고 예상 평점 3개가 있다면 그 다음 3개를 보여주거나 최저 예상 평점 위의 3개를 보여주는... 각 cat 을 클릭해서 들어가면, 그 cat 에 해당하는 자신의 reco 를 보여주고, 그 cat 별로 PR 7+3 을 보여줍시다. 여기서도 위아래로 확장이 가능하도록 설계하고... All category 에서는 각 cat 별로 5개 정도의 user reco 를 저장해서 보여주면 될듯? ## HTTP-URI structure ### Pure files (favicon.ico, jquery, and so on) "/favicon.ico" : favicon.ico "/jquery.min.js" : FileMap.get("jquery.min.js", "df") ### Path="/" 그냥 recoeve.net 으로 접속했을때 뜨는 페이지. 로그인 한 상태라면 본인의 reco page 로. 로그아웃 상태라면 로그인 및 가입 페이지인 "/account/log-in" 으로. ### Path="/get-URI-cats-val" with method "POST" "POST" 로 user_i 랑 cat_i 보내면, 그 "user_i, cat_i" 에 해당하는 UriList 뽑아서 "cats, val" 과 함께 쏴주기. ### path.startsWith("/user/") 특정 user 의 page/reco/CatList/UriList 등을 가져올때 쓰일 path. #### "user/user-id" user-id 의 페이지를 띄워 줌. #### "user/user-id/get-Recos" with method "POST" user-id 의 reco 들 받아올때 쓰임. "POST" 로 "uri" 들을 보내면 그 URI 에 해당하는 user-id 의 reco 들을 text/plain 으로 보내줌. POST data 는 StrArray 형태로 아래와 같이 "uri" column 이 들어 있어야 함. ``` uri http://...1 http://...2 ```/ #### "user/user-id/get-CatList" ? CatList 는 async 하게 (비동기적으로) 받을 필요가 없을테니, 이건 따로 만들필요가 없을듯? 각 페이지 쏴줄때 FileMapWithVar 로 미리 낑겨서 보내면 되니까. #### "user/get-myCatList" 이거 굳이 필요할때 delayed 되어서 따로 받아야 할래나? 그냥 처음에 FileMapWithVar 로 쏴버릴까나? #### "user/user-id/get-UriList" with method "POST" User 의 Reco 가 점점 늘어나면 UriList 크기도 점점 커져서 특정 cat 의 UriList 만을 async 하게 받을 필요가 있을듯. ```[lang-sql] `uriList` mediumtext # 16,777,215 bytes = 65536 * 256 bytes = 16 MB ```/ POST data 는 StrArray 형태로 아래와 같이 "cat" column 이 들어 있어야 함. ``` cat from check cat1 100 (16진수 숫자?) abcd cat2 1000 something ```/ from 이나 check 는 optional 한 부분. if (from==null) { from=0; }, if (check==null) { check="\n"; } 이용 분기해서 처리합시다. Response 는 해당 cat 의 UriList string 을 받아서 from 부터 max 250 KB 부분까지 (string.length 체크해서) 다음과 같이 내보냄. check 가 틀렸을 경우는 from=0 부터 max 250 KB 를 보냄. ``` cat UriList cutP (cut position) cat1 "uri1\nuri2..." 0,- // - for the end of string cat2 "uri50\nuri51..." 0,19248 (16진수 숫자) ```/ #### "user/user-id/get-Neighbors" with method "POST" user_from=user-id, cat_from=cat (POST) 인 neighbors data 보내기. "user_i cat_i sumSim nSim simAvg100 tUpdate" data 를 StrArray 형태로 보냄. #### "user/user-id/c-o-UriList" with method "POST" c-o : change orders ``` cat c-o-UriList cutP cat1 "uri2\nuri1..." 0,- ```/ 위와 같은 StrArray 를 받으면 cutP 에 해당하는 cat-UriList 가 c-o-UriList 랑 uri set 이 같은지 확인하고 같을경우 그 substring 을 c-o-UriList 로 replace 한 뒤 저장. ### path.startsWith("/account/") 계정 관련, log-in/log-out 등등에 쓰일 path. #### "/account/log-in" 가장 기본적인 Log-in/Sign-up page. ## File System Vertx 에서 파일을 그냥 쏴주는 방법은 ```[.linenums] req.response().sendFile("favicon.ico"); req.response().sendFile("home.html"); req.response().sendFile("image.jpg"); ```/ 와 같다. ### class FileMap 그런데 요청이 자주오는 파일의 경우 JAVA 단에서 미리 읽어들여 놓은 다음 (메모리에 저장해놓고 그때그때 바로바로, 파일을 다시 읽어들이는 작업없이) 쏴주는게 빠를거 같아서 FileMap class 를 만들었음. 추가로 언어별로 html 파일을 다 따로 만들긴 너무 귀찮아지니, lang.txt 파일에 언어별 변환 저장해놓고, [--var name--] 형태로 format 화 된 부분 replace 해서 Map(lang) 형태로 저장되도록. 불러들이는 방법은 ```[.linenums] 우선 class FileMap private static final String[] fileNames 에 파일 이름을 등록해놔야 하고. 파일 이름에 해당하는 파일이 적절한 폴더 (private static String filePath) 안에 있어야 함. "lang.txt" 에는 StrArray 형태로 [--var--] 에 해당하는 대체 text 가 저장되어 있어야 함. (google spreadsheet 이용해서 편집+복붙 함.) 다른 JAVA class (Vertx server) 에서 FileMap.get(String fileName, String lang); 같은 형태로 호출해서 쓰면 됨. ```/ ### class FileMapWithVar 위의 class FileMap 은 미리 [--lang variable--] 을 다 replace 해놓고 메모리상에서 대기하는 것이고, HTTP request 가 왔을때, user 에 따라서 대체해줘야 하는 변수들도 있을 수 있을거임. 이 때를 위해 만든게 class FileMapWithVar. 사용방법은 거의 똑같음. ```[.linenums] 우선 class FileMapWithVar private static final String[] fileNames 에 파일 이름을 등록해놔야 하고. 파일 이름에 해당하는 파일이 적절한 폴더 (private static String filePath) 안에 있어야 함. "lang.txt" 에는 StrArray 형태로 [--var--] 에 해당하는 대체 text 가 저장되어 있어야 함. (google spreadsheet 이용해서 편집+복붙 함.) 다른 JAVA class (Vertx server) 에서 FileMap.get(String fileName, String lang, Map<String,String> varMap); 같은 형태로 호출해서 쓰면 됨. 그러면 {--var name--} format 의 text 가 varMap 참고해서 replace 된 String 이 return 됨. 즉 varMap.put("{--var1--}", "value1"); varMap.put("{--var2--}", "value2"); 형태로 varMap 을 만들어서 넣어줘야 함. ```/ ## RRA
  1. dev.mysql.com; and MySQL 5.6 Reference Manual
  2. JAVA SE 8 - API docs
  3. SQL (Structured Query Language) 을 배워보자
  4. Etc.

저작자 표시 비영리 변경 금지
신고
Posted by 냥냥 kipid
comments powered by Disqus