diff --git a/.idea/gradle.xml b/.idea/gradle.xml index a332a14..446c193 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,10 +1,17 @@ + diff --git a/README.md b/README.md index f124d83..e3b54ed 100644 --- a/README.md +++ b/README.md @@ -1 +1,91 @@ -# team5-java-cli-board \ No newline at end of file +# 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 +=> κ²Œμ‹œκΈ€μ΄ μ‚­μ œλ˜μ—ˆμŠ΅λ‹ˆλ‹€. + +λͺ…λ Ήμ–΄: μ’…λ£Œ +ν”„λ‘œκ·Έλž¨μ„ μ’…λ£Œν•©λ‹ˆλ‹€. +``` +----------------------------- +## λΆ€κ°€ μ„€λͺ… + +κ°•μ˜ μ‹œκ°„μ— 배운 λ‚΄μš©λ“€μ„ ν† λŒ€λ‘œ μ΅œλŒ€ν•œ κ°„κ²°ν•˜κ³  λͺ…ν™•ν•œ ꡬ쑰둜 κ΅¬ν˜„ν•˜λ € λ…Έλ ₯ν–ˆμŠ΅λ‹ˆλ‹€! diff --git a/build.gradle.kts b/build.gradle.kts index f263430..c722bc8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ plugins { id("java") } -group = "org.example" +group = "com.ll" version = "1.0-SNAPSHOT" repositories { @@ -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 { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 35e57a1..1ab5f13 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/settings.gradle.kts b/settings.gradle.kts index 6100016..b5d0a38 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "team5-java-cli-board" \ No newline at end of file +rootProject.name = "Article_project" \ No newline at end of file diff --git a/src/main/java/org/example/App.java b/src/main/java/org/example/App.java index 666a561..65622a2 100644 --- a/src/main/java/org/example/App.java +++ b/src/main/java/org/example/App.java @@ -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(); + } + } + } } diff --git a/src/main/java/org/example/Article.java b/src/main/java/org/example/Article.java deleted file mode 100644 index 1c835b9..0000000 --- a/src/main/java/org/example/Article.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.example; - -public class Article { -} diff --git a/src/main/java/org/example/Main.java b/src/main/java/org/example/Main.java index ebc0467..d50dcc9 100644 --- a/src/main/java/org/example/Main.java +++ b/src/main/java/org/example/Main.java @@ -3,7 +3,7 @@ //TIP μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜λ €λ©΄ 을(λ₯Ό) λˆ„λ₯΄κ±°λ‚˜ // 에디터 여백에 μžˆλŠ” μ•„μ΄μ½˜μ„ ν΄λ¦­ν•˜μ„Έμš”. public class Main { - static void main() { - } + public static void main(String[] args) { + new App().run(); } } diff --git a/src/main/java/org/example/Rq.java b/src/main/java/org/example/Rq.java index d8fb6ea..2b8cb5b 100644 --- a/src/main/java/org/example/Rq.java +++ b/src/main/java/org/example/Rq.java @@ -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 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; + } + } } diff --git a/src/main/java/org/example/article/Article.java b/src/main/java/org/example/article/Article.java new file mode 100644 index 0000000..3b5afe1 --- /dev/null +++ b/src/main/java/org/example/article/Article.java @@ -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; +} diff --git a/src/main/java/org/example/article/ArticleController.java b/src/main/java/org/example/article/ArticleController.java new file mode 100644 index 0000000..f5a6e7b --- /dev/null +++ b/src/main/java/org/example/article/ArticleController.java @@ -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
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
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
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
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()); + } + } +} diff --git a/src/main/java/org/example/article/ArticleRepository.java b/src/main/java/org/example/article/ArticleRepository.java new file mode 100644 index 0000000..52a3383 --- /dev/null +++ b/src/main/java/org/example/article/ArticleRepository.java @@ -0,0 +1,46 @@ +package org.example.article; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class ArticleRepository { + private final List
articles; + private int nextId; + + public ArticleRepository() { + this.articles = new ArrayList<>(); + this.nextId = 1; + } + + public Article create(String title, String content, String regDate) { + Article article = new Article(nextId++, title, content, regDate); + articles.add(article); + return article; + } + + public List
findAll() { + return new ArrayList<>(articles); + } + + public Optional
findById(int id) { + return articles.stream() + .filter(a -> a.getId() == id) + .findFirst(); + } + + public List
findByKeyword(String keyword) { + List
result = new ArrayList<>(); + for (Article a : articles) { + if (a.getTitle().contains(keyword) + || a.getContent().contains(keyword)) { + result.add(a); + } + } + return result; + } + + public boolean deleteById(int id) { + return articles.removeIf(a -> a.getId() == id); + } +} diff --git a/src/main/java/org/example/article/ArticleService.java b/src/main/java/org/example/article/ArticleService.java new file mode 100644 index 0000000..e6f8cae --- /dev/null +++ b/src/main/java/org/example/article/ArticleService.java @@ -0,0 +1,53 @@ +package org.example.article; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class ArticleService { + private final ArticleRepository articleRepository; + + public ArticleService(ArticleRepository articleRepository) { + this.articleRepository = articleRepository; + } + + public Article write(String title, String content) { + return articleRepository.create(title, content, getCurrentDate()); + } + + public List
getList() { + List
list = articleRepository.findAll(); + Collections.reverse(list); + return list; + } + + public Optional
getById(int id) { + return articleRepository.findById(id); + } + + public boolean update(int id, String newTitle, String newContent) { + Optional
optArticle = articleRepository.findById(id); + if (optArticle.isEmpty()) return false; + + Article article = optArticle.get(); + if (!newTitle.isEmpty()) article.setTitle(newTitle); + if (!newContent.isEmpty()) article.setContent(newContent); + return true; + } + + public boolean delete(int id) { + return articleRepository.deleteById(id); + } + + public List
search(String keyword) { + List
result = articleRepository.findByKeyword(keyword); + Collections.reverse(result); + return result; + } + + private String getCurrentDate() { + return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } +} diff --git a/src/main/java/org/example/system/SystemController.java b/src/main/java/org/example/system/SystemController.java new file mode 100644 index 0000000..9d3d33b --- /dev/null +++ b/src/main/java/org/example/system/SystemController.java @@ -0,0 +1,12 @@ +package org.example.system; + +public class SystemController { + + public void exit() { + System.out.println("ν”„λ‘œκ·Έλž¨μ„ μ’…λ£Œν•©λ‹ˆλ‹€."); + } + + public void unknown() { + System.out.println("λͺ…λ Ήμ–΄λ₯Ό λ‹€μ‹œ μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } +}