my.ini
, my.cnf
파일로 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) 은 되어야 할듯?
- 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}; } ```/ - 중복 체크가 끝났다면 전체 데이터를 받아서 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`=?; } ```/ - 등록한 e-mail 로 id/vefiKey 가 들어간 http://recoeve.net/account/verify/id/verification-Key 를 쏴 줌.
- 등록과정이 모두 잘 진행되었다면, "signed-up.html" 을 쏴줌. 또한 이 페이지에서 다시 log-in 페이지로 몇 초 후 이동되도록...
- 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); ```/ - 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; ```/
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 의 약자. 돈 거래 처럼. 뭔가 거래 형식으로 주고받을 수 있게 짜야겠음.
```/
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
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 (?, ?, ?, ?, ?);
```/
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`)
);
```/
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 (?);
```/
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.
```/
- 우선 "Referer==recoeve.net" 인지 확인? 필요없나? (iframe 으로 접근하는거 막기?? 우선 pending.)
- 모든 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" } ```/
- 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 가 도난당했어서 이미 다른데에서 로그인을 성공했었을 가능성이 크고. 다 일치하지 않았다면, 그냥 단순 공격일 가능성이 큼. } } ```/
- 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. ```/ - 맞다면 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, ''); } ```/ - (로그인 성공시) '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); } ```/
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
```/
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
```/
- asdf
- Cookie에 저장된 {`user_i`, `authToken`, `tCreate`, `tLast`}를 확인하고, 없다면 'Log-in.html' 창.
- 있다면 DB에 저장된 데이터와 비교 시작.
SELECT * FROM `UserRemember` WHERE `user_i`='user_i from Cookie' and `tCreate`='tCreate from Cookie';
- 우선 `tCreate`가 60일 내의 것인지 확인. 아니라면 DELETE the row. 이후 "60일이 지나서 다시 로그인을 필요로 한다."는 msg가 포함된 'Log-in.html' 창.
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;
```/
이건 하지 말자. 그냥 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 정도가 적혀있을텐데... 둘 중 더 적은 공개 따르도록 동작?
```/
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' 가 있어야 함.
```/
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 가 원하는 순서대로 저장. 마우스 드래그 앤 드랍 등으로 위치 조정 가능하게 해줘야 할듯?
```/
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 쓰는게 더 나을듯도 한데...
```/
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`=?;
```/
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`=?;
```/
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
, `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)
, `avgV100` double(5,2) DEFAULT 0 # average = sumV100/nV
, `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`)
);
```/
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
, `avgSim100` 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
`avgSim100` = 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 를 작성. 나의 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 를 작성하는 걸로 모두 처리 가능할듯.)
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
'[Recoeve.net]' 카테고리의 다른 글
Recoeve setting (0) | 2019.07.30 |
---|---|
Recoeve.net as a playlist of YouTubes and videos (0) | 2019.04.01 |
Log-in encrypt and remembering, or keep connected, or session (0) | 2019.03.06 |
Recoeve Database setup (0.1 version) (1) | 2019.03.06 |
Recoeve.net 설명서 (Guide to Recoeve.net) (0) | 2019.03.06 |
개인화 된 추천 시스템 (Personalized recommendation system) (1) | 2019.03.06 |
Introducing what we are making : Recoeve.net (0) | 2019.03.04 |
사람들의 평균적인 소비구조 (0) | 2019.03.04 |
root/arrow/linked array 같은 디자인 통해서 merge 구현.
circular root 는 조심해야겠고.
전체를 track 하면서 check 하도록 해야할듯?