Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 91 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,91 @@
# team5-java-cli-board
# team5-java-cli-board

## 📁 프로젝트 구조

```
📦 src
┣ 📂 main
┃ ┗ 📂 java
┃ ┗ 📂 com
┃ ┗ 📂 back
┃ ┣ 📂 org.example
┃ ┃ ┣ 📂 article
┃ ┃ ┃ ┣ 📂 controller
┃ ┃ ┃ ┃ ┗ 📜 ArticleController.java // 게시글 입출력 및 사용자 흐름 제어
┃ ┃ ┃ ┣ 📂 entity
┃ ┃ ┃ ┃ ┗ 📜 Article.java // 게시글 데이터 모델 (id, title, content, regDate)
┃ ┃ ┃ ┣ 📂 repository
┃ ┃ ┃ ┃ ┗ 📜 ArticleRepository.java // 게시글 데이터 CRUD 처리
┃ ┃ ┃ ┗ 📂 service
┃ ┃ ┃ ┗ 📜 ArticleService.java // 게시글 핵심 비즈니스 로직
┃ ┃ ┗ 📂 system
┃ ┃ ┗ 📂 controller
┃ ┃ ┗ 📜 SystemController.java // 종료, 도움말 등 시스템 명령 처리
┃ ┣ 📜 App.java // 사용자 입력 수신 및 컨트롤러 라우팅
┃ ┣ 📜 Main.java // 프로그램 진입점 (Entry Point)
┃ ┗ 📜 Rq.java // CLI 명령어 파싱 유틸 (actionName / params)
┗ 📂 test
```

## ✅ 주요 구현

### 1. 계층 분리 (Layered Architecture)
- Controller / Service / Repository 계층을 분리하여 각 클래스가 단일 책임만 갖도록 설계했습니다.
- Controller는 입출력만, Service는 비즈니스 로직만, Repository는 데이터 처리만 담당합니다.

### 2. 의존성 주입
- `App`에서 Repository → Service → Controller 순으로 직접 생성하여 주입합니다.
- 각 계층은 인터페이스가 아닌 구체 클래스에 의존하지만, 생성 책임을 `App` 한 곳에서 관리합니다.


-----------------------------------------
## ▶️ 실행 방법

프로그램 실행 후 아래 명령어를 입력하여 사용합니다.

| 명령어 | 기능 |
|---|---|
| `작성` | 게시글 작성 |
| `리스트` | 게시글 목록 보기 (최신순) |
| `세부내용?id=1` | 1번 게시글 상세 보기 |
| `수정?id=1` | 1번 게시글 수정 |
| `삭제?id=1` | 1번 게시글 삭제 |
| `찾기?keyword=자바` | 제목/내용에 '자바'가 포함된 게시글 검색 |
| `종료` | 프로그램 종료 |

-----------------------------------------
## 💬 실행 예시

```
명령어: 작성
제목: 자바 공부
내용: 자바 텍스트 게시판 만들기
=> 게시글이 등록되었습니다.

명령어: 리스트
번호 | 제목 | 등록일
----------------------------------------
1 | 자바 공부 | 2025-08-03

명령어: 세부내용?id=1
번호: 1
제목: 자바 공부
내용: 자바 텍스트 게시판 만들기
등록일: 2025-08-03

명령어: 수정?id=1
제목 (현재: 자바 공부): Java 게시판
내용 (현재: 자바 텍스트 게시판 만들기): 콘솔 기반으로 구현
=> 게시글이 수정되었습니다.

명령어: 삭제?id=1
=> 게시글이 삭제되었습니다.

명령어: 종료
프로그램을 종료합니다.
```
-----------------------------
## 부가 설명

강의 시간에 배운 내용들을 토대로 최대한 간결하고 명확한 구조로 구현하려 노력했습니다!
8 changes: 7 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
id("java")
}

group = "org.example"
group = "com.ll"
version = "1.0-SNAPSHOT"

repositories {
Expand All @@ -13,6 +13,12 @@ dependencies {
testImplementation(platform("org.junit:junit-bom:6.0.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

compileOnly("org.projectlombok:lombok:1.18.38")
annotationProcessor("org.projectlombok:lombok:1.18.38")

testCompileOnly("org.projectlombok:lombok:1.18.38")
testAnnotationProcessor("org.projectlombok:lombok:1.18.38")
}

tasks.test {
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#Mon Apr 13 17:36:22 KST 2026
#Thu Apr 16 23:54:23 KST 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
rootProject.name = "team5-java-cli-board"
rootProject.name = "Article_project"
42 changes: 42 additions & 0 deletions src/main/java/org/example/App.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,46 @@
package org.example;

import org.example.article.ArticleController;
import org.example.article.ArticleRepository;
import org.example.article.ArticleService;
import org.example.system.SystemController;

import java.util.Scanner;

public class App {
private final Scanner scanner;
private final ArticleController articleController;
private final SystemController systemController;

public App() {
this.scanner = new Scanner(System.in);

ArticleRepository articleRepository = new ArticleRepository();
ArticleService articleService = new ArticleService(articleRepository);
this.articleController = new ArticleController(articleService, scanner);
this.systemController = new SystemController();
}

public void run() {

while (true) {
System.out.print("명령어: ");
String input = scanner.nextLine().trim();

if (input.isEmpty()) continue;

Rq rq = new Rq(input);

switch (rq.getActionName()) {
case "작성" -> articleController.write();
case "리스트" -> articleController.list();
case "세부내용" -> articleController.detail(rq);
case "수정" -> articleController.update(rq);
case "삭제" -> articleController.delete(rq);
case "찾기" -> articleController.search(rq);
case "종료" -> { systemController.exit(); return; }
default -> systemController.unknown();
}
}
}
}
4 changes: 0 additions & 4 deletions src/main/java/org/example/Article.java

This file was deleted.

4 changes: 2 additions & 2 deletions src/main/java/org/example/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//TIP 코드를 <b>실행</b>하려면 <shortcut actionId="Run"/>을(를) 누르거나
// 에디터 여백에 있는 <icon src="AllIcons.Actions.Execute"/> 아이콘을 클릭하세요.
public class Main {
static void main() {
}
public static void main(String[] args) {
new App().run();
}
}
49 changes: 49 additions & 0 deletions src/main/java/org/example/Rq.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,53 @@
package org.example;

import java.util.HashMap;
import java.util.Map;

public class Rq {
private final String actionName;
private final Map<String, String> paramsMap;

public Rq(String cmd) {
paramsMap = new HashMap<>();

String[] cmdBits = cmd.split("\\?", 2);
actionName = cmdBits[0].trim();
String queryString;
if (cmdBits.length > 1) {
queryString = cmdBits[1].trim();
} else {
queryString = "";
}

String[] queryStringBits = queryString.split("&");

for (String queryParam : queryStringBits) {
String[] queryParamBits = queryParam.split("=", 2);
String key = queryParamBits[0].trim();
String value;
if (queryParamBits.length > 1) {
value = queryParamBits[1].trim();
} else {
value = "";
}

if (value.isEmpty()) {
continue;
}

paramsMap.put(key, value);
}
}

public String getActionName() {
return actionName;
}

public String getParam(String paramName, String defaultValue) {
if (paramsMap.containsKey(paramName)) {
return paramsMap.get(paramName);
} else {
return defaultValue;
}
}
}
15 changes: 15 additions & 0 deletions src/main/java/org/example/article/Article.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.example.article;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class Article {
private final int id;
private String title;
private String content;
private final String regDate;
}
121 changes: 121 additions & 0 deletions src/main/java/org/example/article/ArticleController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package org.example.article;

import org.example.Rq;

import java.util.List;
import java.util.Optional;
import java.util.Scanner;

public class ArticleController {
private final ArticleService articleService;
private final Scanner scanner;

public ArticleController(ArticleService articleService, Scanner scanner) {
this.articleService = articleService;
this.scanner = scanner;
}

public void write() {
System.out.print("제목: ");
String title = scanner.nextLine().trim();

System.out.print("내용: ");
String content = scanner.nextLine().trim();

if (title.isEmpty() || content.isEmpty()) {
System.out.println("제목과 내용을 모두 입력해주세요.");
return;
}

articleService.write(title, content);
System.out.println("게시글이 등록되었습니다.");
}

public void list() {
List<Article> list = articleService.getList();

if (list.isEmpty()) {
System.out.println("등록된 게시글이 없습니다.");
return;
}

System.out.println("번호 / 제목 / 등록일");
System.out.println("-".repeat(40));
for (Article article : list) {
System.out.printf("%d / %s / %s%n",
article.getId(), article.getTitle(), article.getRegDate());
}
}

public void detail(Rq rq) {
int id = Integer.parseInt(rq.getParam("id", "0"));
Optional<Article> optArticle = articleService.getById(id);

if (optArticle.isEmpty()) {
System.out.println(id + "번 게시글은 존재하지 않습니다.");
return;
}

Article article = optArticle.get();
System.out.println("번호: " + article.getId());
System.out.println("제목: " + article.getTitle());
System.out.println("내용: " + article.getContent());
System.out.println("등록일: " + article.getRegDate());
}

public void update(Rq rq) {
int id = Integer.parseInt(rq.getParam("id", "0"));
Optional<Article> optArticle = articleService.getById(id);

if (optArticle.isEmpty()) {
System.out.println(id + "번 게시글은 존재하지 않습니다.");
return;
}

Article article = optArticle.get();

System.out.printf("(현재 제목 : %s) -> ", article.getTitle());
String newTitle = scanner.nextLine().trim();

System.out.printf("(현재 내용: %s) -> ", article.getContent());
String newContent = scanner.nextLine().trim();

articleService.update(id, newTitle, newContent);
System.out.println("게시글이 수정되었습니다.");
}

public void delete(Rq rq) {
int id = Integer.parseInt(rq.getParam("id", "0"));
boolean deleted = articleService.delete(id);

if (deleted) {
System.out.println("게시글이 삭제되었습니다.");
} else {
System.out.println(id + "번 게시글은 존재하지 않습니다.");
}
}

public void search(Rq rq) {
String keyword = rq.getParam("keyword", "");

if (keyword.isEmpty()) {
System.out.println("사용법: search?keyword=키워드");
return;
}

List<Article> results = articleService.search(keyword);

if (results.isEmpty()) {
System.out.println("검색 결과가 없습니다: '" + keyword + "'");
return;
}

System.out.println("검색 결과 (" + results.size() + "건): '" + keyword + "'");
System.out.println("번호 / 제목 / 등록일");
System.out.println("-".repeat(40));
for (Article article : results) {
System.out.printf("%d / %s / %s%n",
article.getId(), article.getTitle(), article.getRegDate());
}
}
}
Loading