현재 일하고 있는 곳에서는 가끔 이벤트를 하곤한다.
해당글에 지정한 시간에 맞춰, 의견쓰기(댓글)를 한 사람에게 선착순으로 공연 티켓을 주는것이다.
뭐 정직원을 대상으로 하는 이벤트라, 본인같은 일용직 노동자에게 의미가 없는 일이긴 하지만, 기분이 우울한 관계로 & 기분전환의 차원으로, 자동으로 댓글다는 프로그램을 만들어보겠다.
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 List getActions() {
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는 없다.
commons-codec-1.3.jar