Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- Executor
- 한글조사처리
- RSA
- Callable
- 암호학
- 이클립스 플러그인 개발
- ACAP
- DAMO
- sha1
- Executors
- AES
- date
- 한글조사
- Freemaker
- Runnable
- PKCS#8
- JCE
- StringUtils
- Java
- String
- 자바 암호화
- 자바
- ORM
- Instrumentation
- Log4J
- mac
- PKCS
- IPTV
- xlet
- Postman
Archives
- Today
- Total
오늘은 어디로 갈까...
Just For Fun 본문
현재 일하고 있는 곳에서는 가끔 이벤트를 하곤한다.
해당글에 지정한 시간에 맞춰, 의견쓰기(댓글)를 한 사람에게 선착순으로 공연 티켓을 주는것이다.
뭐 정직원을 대상으로 하는 이벤트라, 본인같은 일용직 노동자에게 의미가 없는 일이긴 하지만, 기분이 우울한 관계로 & 기분전환의 차원으로, 자동으로 댓글다는 프로그램을 만들어보겠다.
1. 준비물
- 준비물은 다음과 같다.
JDK 1.5 이상
HttpClient v3.1 (http://hc.apache.org/httpclient-3.x/index.html)
+ Commons Codec (http://commons.apache.org/codec/)
+ Commons Logging (http://commons.apache.org/logging/)
+ Log4J (http://logging.apache.org/log4j/1.2/index.html)
HttpCleaner v2.1 (http://htmlcleaner.sourceforge.net/)
- HtpClient는 웹브라우저 같은 역할을 하고, Commons Codec은 HttpClient 내에서 사용한다. Common Logging과 Log4J도 HtpClient 내에서 사용함으로 그냥 묻어가는 기분으로 사용한다.
- HttpCleaner는 대상 html을 쉽게 분석(parsing)하기 위해 필요하다.
2. 자바 프로젝트 생성
- 이클립스에 자바 프로젝트(Java Project)를 생성한다. 글 제목이 Just For Fun 이니, kr.kangwoo.jff로 이름을 짓겠다. --;
- log4j를 사용하기로 했으니, 적당한 log4j.xml 파일을 만들어서 src 폴더에 넣어주자.
- 한 클래스에 몰빵하는식으로 구현할 수 있으나, 쪼개는것(?)을 좋아하는 본인의 성격상 여러 클래스로 나누겠다.
JFFClient : HttpClient를 이용해서 해당 서버에 접속 후, 작업을 처리한다.
JFFClientConfig : 접속할 서버 주소, 처리할 작업 정보를 저장하고 있다.
JFFException : RuntimException을 상속받은 클래스로서, JFF 처리시 발생한 에러를 나타낸다.
Action : 처리할 작업을 나타내는 인터페이스
LoginAction : 로그인 작업
WriteCommentAction : 댓글 쓰기 작업
JFFTask : 지정한 시간에 작업 처리하기 위한 클래스(JFFClient를 생성후 실행한다.)
JFFMain : 메인 클래스, 지정한 시간에 해당 작업 처리를 지시한다.
3. JFFException 클래스 만들기
- RuntimeException을 상속받아 만든다. 단순히 메시지 처리용이다. Exception을 남용(?)하면 오버헤드(overhead)가 발생하기는 하나, 제어(?)하기에 편해서 본인은 남용을 하겠다.
4. Action 인터페이스 만들기
- 처리할 작업을 나타내는 인터페이스를 만들자. HttpClient를 넘겨받아 원하는 일을 처리하게 만드는 간단한 인터페이스이다.
5. JFFClientConfig 클래스 만들기
- 호스트 명, 포트 번호, 처리할 작업 목록을 수용할 수 있는 클래스이다.
- 작업은 순차적으로 처리하기 위해 List 객체에 담는다.
6. JFFClient 클래스 만들기
- JFFClientConfig 클래스를 첨자로 넘겨받아, HttpClient를 생성후, 호스트 명, 포트번호, 프로토콜을 지정해주고, 세션을 유지하기 위해서 쿠키(Cookie)를 사용가능하게 지정해준다.
- 예의상(?) close() 메소드를 만들었으나.... 아무일도 안한다. ^^;
- 여기서는 응답 html이 간단해서 예전에 만들었던, StringUtils을 이용해서 간단히 처리하였다.
7. LoginAction 클래스 만들기
- 댓글을 남기기 위해서는 로그인을 먼저 해야한다. 그래서 로그인을 하는 작업 클래스를 만든다.
- 사이트마다 다르겠지만, 이곳에서는 "id"와 "pwd"라는 파라메터를 POST 방식으로 전송하면 로그인 처리가 일어난다.
- 세션처리는 HttpClient가 알아서(?) 함으로, 여기서는 로그인이 성공했는지 실패했는지를 판단하는 부분만 구현하면 된다.
8. WriteCommentAction 클래스 만들기
- 본격적인 일(?)을 하는 클래스이다.
- 댓글 작성에 필요한 파라메터들을 넘겨받아, 댓글 작성을 하도록 한다.
- 지정한 시간에 선착순이므로, 시간(Date)도 넘겨받아야한다.
- 불행히도 서버의 시간과 PC의 시간 차이가 발생함으로, 적당히 댓글을 작성하다가, 지정 시간에 댓글 작성을 성공하면 중지시킨다.
- 댓글 작성이 성공하면 지정시간 이전에 작성한 댓글들은 삭제하도록한다.
- 댓글 실패시 실패 횟수를 계산하여 중지 시키는 로직을 추가하는게 좋을거 같지만, 귀찮아서 통과~한다.
9. JFFTask 클래스 만들기
- 작업은 프로그램이 시작시 바로 실행되는게 아니라, 지정한 시간에 실행되어야한다. 그래서 쓰레드로 스케줄링을 해야하는데, 여기서는 자바에서 기본적으로 제공하는 java.util.Timer 클래스를 사용하기로 한다. 이 Timer 클래스에 추가할 수 있는 작업 클래스는 java.util.Task 클래스를 상속받아야함으로, 이 Task 클래스를 상속받은 JFFTask 클래스를 만들겠다.
- run() 메소드에 처리할 작업을 구현하면 된다.
- JFFClient를 생성후, execute()메소드를 실행하게 한다.
10. JFFMain 클래스 만들기
- 실제적으로 프로그램을 실행하는 메인 클래스이다.
- 내부적으로 java.util.Timer 클래스를 생성하여, JFFTask를 실행하게 한다.
11. 실행해보기
- JFFClientConfig 클래스를 생성해 서버 정보 및 처리할 작업 진행한다.
- JFFMain에 JFFClientConfig을 넘겨주고, schudle() 메소드를 이용해 지정한 시간에 실행하게 한다.
- 아래는 댓글 시간과 실행 시간 값이 동일하다. 그래서 PC의 시간이 서버의 시간보다 빨라야 원하는 결과를 얻을 수 있겠다.
12. 넋두리
- 테스트 케이스를 만들어서 작업을 하였지만, 보여주기 민망하여 기본(?) 소스만 설명하였다. ^^;
- 구현보다 구조를 생각하는데 더 많은 시간을 소비하였지만, 별로 좋은 구조가 아니다. (이런걸 시간낭비라고 한다. ㅠㅠ)
- 단일 쓰레드에서만 정상적인 작동을 보장한다.
- 기분 전환용 프로그램이라, A/S는 없다.
해당글에 지정한 시간에 맞춰, 의견쓰기(댓글)를 한 사람에게 선착순으로 공연 티켓을 주는것이다.
뭐 정직원을 대상으로 하는 이벤트라, 본인같은 일용직 노동자에게 의미가 없는 일이긴 하지만, 기분이 우울한 관계로 & 기분전환의 차원으로, 자동으로 댓글다는 프로그램을 만들어보겠다.
1. 준비물
- 준비물은 다음과 같다.
JDK 1.5 이상
HttpClient v3.1 (http://hc.apache.org/httpclient-3.x/index.html)
+ Commons Codec (http://commons.apache.org/codec/)
+ Commons Logging (http://commons.apache.org/logging/)
+ Log4J (http://logging.apache.org/log4j/1.2/index.html)
HttpCleaner v2.1 (http://htmlcleaner.sourceforge.net/)
- HtpClient는 웹브라우저 같은 역할을 하고, Commons Codec은 HttpClient 내에서 사용한다. Common Logging과 Log4J도 HtpClient 내에서 사용함으로 그냥 묻어가는 기분으로 사용한다.
- HttpCleaner는 대상 html을 쉽게 분석(parsing)하기 위해 필요하다.
2. 자바 프로젝트 생성
- 이클립스에 자바 프로젝트(Java Project)를 생성한다. 글 제목이 Just For Fun 이니, kr.kangwoo.jff로 이름을 짓겠다. --;
- log4j를 사용하기로 했으니, 적당한 log4j.xml 파일을 만들어서 src 폴더에 넣어주자.
- 한 클래스에 몰빵하는식으로 구현할 수 있으나, 쪼개는것(?)을 좋아하는 본인의 성격상 여러 클래스로 나누겠다.
JFFClient : HttpClient를 이용해서 해당 서버에 접속 후, 작업을 처리한다.
JFFClientConfig : 접속할 서버 주소, 처리할 작업 정보를 저장하고 있다.
JFFException : RuntimException을 상속받은 클래스로서, JFF 처리시 발생한 에러를 나타낸다.
Action : 처리할 작업을 나타내는 인터페이스
LoginAction : 로그인 작업
WriteCommentAction : 댓글 쓰기 작업
JFFTask : 지정한 시간에 작업 처리하기 위한 클래스(JFFClient를 생성후 실행한다.)
JFFMain : 메인 클래스, 지정한 시간에 해당 작업 처리를 지시한다.
3. JFFException 클래스 만들기
- RuntimeException을 상속받아 만든다. 단순히 메시지 처리용이다. Exception을 남용(?)하면 오버헤드(overhead)가 발생하기는 하나, 제어(?)하기에 편해서 본인은 남용을 하겠다.
package kr.kangwoo.jff.client; public class JFFException extends RuntimeException { /** * */ private static final long serialVersionUID = 7725404710131495314L; public JFFException() { super(); } public JFFException(String message) { super(message); } public JFFException(String message, Throwable cause) { super(message, cause); } public JFFException(Throwable cause) { super(cause); } }
4. Action 인터페이스 만들기
- 처리할 작업을 나타내는 인터페이스를 만들자. HttpClient를 넘겨받아 원하는 일을 처리하게 만드는 간단한 인터페이스이다.
package kr.kangwoo.jff.client; import org.apache.commons.httpclient.HttpClient; public interface Action { void doAction(HttpClient client) throws JFFException; }
5. JFFClientConfig 클래스 만들기
- 호스트 명, 포트 번호, 처리할 작업 목록을 수용할 수 있는 클래스이다.
- 작업은 순차적으로 처리하기 위해 List 객체에 담는다.
package kr.kangwoo.jff.client; import java.util.ArrayList; import java.util.List; public class JFFClientConfig { private String host; private int port = 80; private String protocol = "http"; private List<Action> actions; public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getProtocol() { return protocol; } public void setProtocol(String protocol) { this.protocol = protocol; } public ListgetActions() { return actions; } public void setActions(List actions) { this.actions = actions; } public boolean addAction(Action action) { if (this.actions == null) { this.actions = new ArrayList<Action>(); } return this.actions.add(action); } }
6. JFFClient 클래스 만들기
- JFFClientConfig 클래스를 첨자로 넘겨받아, HttpClient를 생성후, 호스트 명, 포트번호, 프로토콜을 지정해주고, 세션을 유지하기 위해서 쿠키(Cookie)를 사용가능하게 지정해준다.
- 예의상(?) close() 메소드를 만들었으나.... 아무일도 안한다. ^^;
- 여기서는 응답 html이 간단해서 예전에 만들었던, StringUtils을 이용해서 간단히 처리하였다.
package kr.kangwoo.jff.client; import java.util.List; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.cookie.CookiePolicy; public class JFFClient { private HttpClient client; private List<Action> actions; public JFFClient(JFFClientConfig config) { client = new HttpClient(); client.getHostConfiguration().setHost(config.getHost(), config.getPort(), config.getProtocol()); client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); actions = config.getActions(); } public void close() { } public void execute() throws JFFException { if (actions != null) { for (Action action : actions) { action.doAction(client); } } } }
7. LoginAction 클래스 만들기
- 댓글을 남기기 위해서는 로그인을 먼저 해야한다. 그래서 로그인을 하는 작업 클래스를 만든다.
- 사이트마다 다르겠지만, 이곳에서는 "id"와 "pwd"라는 파라메터를 POST 방식으로 전송하면 로그인 처리가 일어난다.
- 세션처리는 HttpClient가 알아서(?) 함으로, 여기서는 로그인이 성공했는지 실패했는지를 판단하는 부분만 구현하면 된다.
package kr.kangwoo.jff.client; import kr.kangwoo.util.StringUtils; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class LoginAction implements Action { private Log log = LogFactory.getLog(LoginAction.class); private String userId; private String userPassword; public LoginAction(String userId, String userPassword) { this.userId = userId; this.userPassword = userPassword; } public void doAction(HttpClient client) throws JFFException { if (log.isDebugEnabled()) { log.debug("로그인(" + userId + ", " + StringUtils.repeat("*", StringUtils.defaultIfNull(userPassword).length()) + ")"); } int statusCode = 0; String bodyString = null; try { NameValuePair id = new NameValuePair("id", userId); NameValuePair pwd = new NameValuePair("pwd", userPassword); PostMethod loginMethod = new PostMethod("/ioffice/Login_bk.jsp"); loginMethod.setRequestBody(new NameValuePair[] {id, pwd}); statusCode = client.executeMethod(loginMethod); if (statusCode == HttpStatus.SC_OK) { bodyString = loginMethod.getResponseBodyAsString(); parse(bodyString); if (log.isDebugEnabled()) { log.debug("로그인 성공"); } } else { throw new JFFException("로그인에 실패하였습니다. (응답코드:" + statusCode + ")"); } } catch (JFFException e) { log.debug("로그인 실패"); throw e; } catch (Exception e) { log.debug("로그인 실패"); throw new JFFException("로그인 하는 중 에러가 발생했습니다.", e); } } protected void parse(String html) { if (html != null) { String script = StringUtils.trim(StringUtils.substringBetween(html, "<script>", "</script>")); if (StringUtils.equals(script, "document.location = \"/ioffice/index.jsp\";")) { // 로그인 성공 } else { String msg = StringUtils.substringBetween(script, "alert(\"", "\");"); if (StringUtils.isNotBlank(msg)) { msg = "로그인에 실패하였습니다. (" + msg + ")"; } else { msg = "로그인에 실패하였습니다."; } throw new JFFException(msg); } } } }
8. WriteCommentAction 클래스 만들기
- 본격적인 일(?)을 하는 클래스이다.
- 댓글 작성에 필요한 파라메터들을 넘겨받아, 댓글 작성을 하도록 한다.
- 지정한 시간에 선착순이므로, 시간(Date)도 넘겨받아야한다.
- 불행히도 서버의 시간과 PC의 시간 차이가 발생함으로, 적당히 댓글을 작성하다가, 지정 시간에 댓글 작성을 성공하면 중지시킨다.
- 댓글 작성이 성공하면 지정시간 이전에 작성한 댓글들은 삭제하도록한다.
- 댓글 실패시 실패 횟수를 계산하여 중지 시키는 로직을 추가하는게 좋을거 같지만, 귀찮아서 통과~한다.
package kr.kangwoo.jff.client; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import kr.kangwoo.util.StringUtils; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.htmlcleaner.HtmlCleaner; import org.htmlcleaner.TagNode; public class WriteCommentAction implements Action { private Log log = LogFactory.getLog(WriteCommentAction.class); private SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm"); private String userId; private String userName; private String formNo; private String patCd; private String opContents; private Date commentDate; private long delay = 1 * 1000; public WriteCommentAction(String userId, String userName, String formNo, String patCd, String opContents, Date commentDate) { this.userId = userId; this.userName = userName; this.formNo = formNo; this.patCd = patCd; this.opContents = opContents; this.commentDate = commentDate; } public void doAction(HttpClient client) throws JFFException { boolean isRunning = true; Set<String> delSet = new HashSet<String>(); List<Comment> comments = null; // 작성 시작 do { if (log.isInfoEnabled()) { log.info("덧글쓰기 시작"); } try { comments = writeComment(client); if (comments != null) { if (log.isInfoEnabled()) { log.info("덧글쓰기 완료 (" + comments.size() + ")"); } for (Comment comment : comments) { if (comment.date.getTime() >= commentDate.getTime()) { if (log.isInfoEnabled()) { log.info("[" + comment.no + "] " + comment.content + " (" + format.format(comment.date) + ") OK"); } isRunning = false; break; } else { if (delSet.contains(comment.no)) { } else { delSet.add(comment.no); if (log.isInfoEnabled()) { log.info("[" + comment.no + "] " + comment.content + " (" + format.format(comment.date) + ") pass"); } } } } Thread.sleep(delay); } } catch (Exception e) { log.error("댓글을 작성하는 중 에러가 발생했습니다.", e); } } while(isRunning); // 삭제 시작 if (log.isInfoEnabled()) { log.info(delSet.size() + "개의 댓글을 삭제하겠습니다."); } for (String commentNo : delSet) { try { comments = deleteComment(client, commentNo); if (log.isInfoEnabled()) { log.info("[" + commentNo + "] 댓글을 삭제하였습니다."); } Thread.sleep(delay); } catch (Exception e) { log.error("댓글을 삭제하는 중 에러가 발생했습니다. (" + commentNo + ")", e); } } } protected List<Comment> writeComment(HttpClient client) throws HttpException, IOException { List<Comment> comments = null; String job = "update"; StringBuilder query = new StringBuilder(); query.append("timeStamp=").append(System.currentTimeMillis()); query.append("&form_no=").append(formNo); query.append("&pat_cd=").append(patCd); query.append("&user_id=").append(userId); query.append("&user_name=").append(encodeURIComponent(userName)); query.append("&op_contents=").append(encodeURIComponent(opContents)); query.append("&job=").append(job); query.append("&seq=").append("0"); GetMethod getMethod = new GetMethod("/ioffice/page/Forum/Forum_op.jsp?" + query.toString()); int statusCode = client.executeMethod(getMethod); if (statusCode == HttpStatus.SC_OK) { comments = getMyComments(getMethod.getResponseBodyAsString(), opContents); } else { throw new JFFException("서버로 부터 정상적인 응답을 받지 못했습니다. (STATUS_CODE=" + statusCode + ")"); } return comments; } protected List<Comment> deleteComment(HttpClient client, String seq) throws HttpException, IOException { List<Comment> comments = null; String job = "delete"; StringBuilder query = new StringBuilder(); query.append("timeStamp=").append(System.currentTimeMillis()); query.append("&form_no=").append(formNo); query.append("&pat_cd=").append(patCd); query.append("&user_id=").append(userId); query.append("&user_name=").append(encodeURIComponent(userName)); query.append("&job=").append(job); query.append("&seq=").append(seq); query.append("&auth=").append("3"); GetMethod getMethod = new GetMethod("/ioffice/page/Forum/Forum_op.jsp?" + query.toString()); int statusCode = client.executeMethod(getMethod); if (statusCode == HttpStatus.SC_OK) { comments = getMyComments(getMethod.getResponseBodyAsString(), opContents); } else { throw new JFFException("서버로 부터 정상적인 응답을 받지 못했습니다. (STATUS_CODE=" + statusCode + ")"); } return comments; } protected String encodeURIComponent(String s) throws UnsupportedEncodingException { return URLEncoder.encode(s, "UTF-8"); } public List<Comment> getMyComments(String input, String opContents) { List<Comment> result = new ArrayList<Comment>(); HtmlCleaner cleaner = new HtmlCleaner(); try { TagNode node = cleaner.clean(input); Object[] objArray = node.evaluateXPath("//table//tr//td[@align='left']//div"); for (Object obj: objArray) { TagNode t = (TagNode)obj; if (StringUtils.startsWith(t.getText().toString(), opContents)) { Comment c = convertComment(t); if (c != null) { result.add(c); } } } } catch (Exception e) { throw new JFFException("HTML을 분석하는중 에러가 발생했습니다.", e); } return result; } public Comment convertComment(TagNode tagNode) throws ParseException { Comment comment = null; TagNode[] spanTags = tagNode.getChildTags(); if (spanTags != null && spanTags.length == 2) { comment = new Comment(); String onclick = spanTags[1].getAttributeByName("onclick"); comment.no = StringUtils.substringBetween(onclick, "(", ");"); String text = tagNode.getText().toString(); comment.content = StringUtils.substringBefore(text, " "); String dateStr = StringUtils.substringBetween(text, " (", ")"); comment.date = format.parse(dateStr); } return comment; } class Comment { String content; Date date; String no; } }
9. JFFTask 클래스 만들기
- 작업은 프로그램이 시작시 바로 실행되는게 아니라, 지정한 시간에 실행되어야한다. 그래서 쓰레드로 스케줄링을 해야하는데, 여기서는 자바에서 기본적으로 제공하는 java.util.Timer 클래스를 사용하기로 한다. 이 Timer 클래스에 추가할 수 있는 작업 클래스는 java.util.Task 클래스를 상속받아야함으로, 이 Task 클래스를 상속받은 JFFTask 클래스를 만들겠다.
- run() 메소드에 처리할 작업을 구현하면 된다.
- JFFClient를 생성후, execute()메소드를 실행하게 한다.
package kr.kangwoo.jff.client; import java.util.TimerTask; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class JFFTask extends TimerTask { private Log log = LogFactory.getLog(JFFTask.class); private JFFClientConfig config; public JFFTask(JFFClientConfig config) { this.config = config; } @Override public void run() { if (log.isDebugEnabled()) { log.debug("JFFTask 시작"); } JFFClient client = null; try { client = new JFFClient(config); client.execute(); } catch (Exception e) { log.error("작업 실행 중 에러가 발생했습니다.", e); } finally { if (client != null) try {client.close();} catch(JFFException je) {} } if (log.isDebugEnabled()) { log.debug("JFFTask 종료"); } } }
10. JFFMain 클래스 만들기
- 실제적으로 프로그램을 실행하는 메인 클래스이다.
- 내부적으로 java.util.Timer 클래스를 생성하여, JFFTask를 실행하게 한다.
package kr.kangwoo.jff.client; import java.util.Calendar; import java.util.Date; import java.util.Timer; public class JFFMain extends Timer { private Timer timer; private JFFTask task; public JFFMain(JFFClientConfig config) { timer = new Timer(); task = new JFFTask(config); } public void schedule(long delay) { timer.schedule(task, delay); } public void schedule(Date time) { timer.schedule(task, time); } public void schedule(long delay, long period) { timer.schedule(task, delay, period); } public void schedule(Date firstTime, long period) { timer.schedule(task, firstTime, period); } }
11. 실행해보기
- JFFClientConfig 클래스를 생성해 서버 정보 및 처리할 작업 진행한다.
- JFFMain에 JFFClientConfig을 넘겨주고, schudle() 메소드를 이용해 지정한 시간에 실행하게 한다.
- 아래는 댓글 시간과 실행 시간 값이 동일하다. 그래서 PC의 시간이 서버의 시간보다 빨라야 원하는 결과를 얻을 수 있겠다.
public static void main(String[] args) throws InterruptedException { String host = "test.host.com"; String userId = "kangwoo"; String userPassword = "123456"; String userName = "강우"; String formNo = "FR2009-03-27100057"; String patCd = "0"; String opContents = "자유인/댓글입니다."; Date commentDate = toDate(12, 00, 0); JFFClientConfig config = new JFFClientConfig(); config.setHost(host); config.addAction(new LoginAction(userId, userPassword)); config.addAction(new WriteCommentAction(userId, userName, formNo, patCd, opContents, commentDate)); JFFMain main = new JFFMain(config); main.schedule(commentDate); } public static Date toDate(int hour, int minute, int second) { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minute); calendar.set(Calendar.SECOND, second); calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); }
12. 넋두리
- 테스트 케이스를 만들어서 작업을 하였지만, 보여주기 민망하여 기본(?) 소스만 설명하였다. ^^;
- 구현보다 구조를 생각하는데 더 많은 시간을 소비하였지만, 별로 좋은 구조가 아니다. (이런걸 시간낭비라고 한다. ㅠㅠ)
- 단일 쓰레드에서만 정상적인 작동을 보장한다.
- 기분 전환용 프로그램이라, A/S는 없다.