一、项目概述
本文记录一个基于 Java
的日历记事本系统开发过程,主要解决阳历与阴历日期管理、事件存储与查询、异常处理等问题,涉及
Hutool 工具库、OpenCSV 文件解析及设计模式的应用。
- 核心库版本
- Hutool:5.8.38(日期处理与工具类)
- OpenCSV:5.7.0(CSV 文件读写)
- Apache Commons Lang3:3.17.0(通用工具类,OpenCSV 依赖)
二、采用的设计模式
1.
单例模式(Singleton)
2. 策略模式(Strategy)
3.
观察者模式(Observer)
三、项目目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| EventNotepad ├─ JRE System Library [JavaSE-22] # Java 22运行时环境 ├─ src │ └─ com.calendar.notepad │ ├─ Main.java # 主程序入口(控制台交互) │ ├─ lunar │ │ └─ LunarUtil.java # 阴历工具类(阳历转阴历、农历键生成) │ ├─ model │ │ └─ Event.java # 事件实体(标题、日期、内容) │ ├─ observer │ │ ├─ EventNotifier.java # 事件通知器(观察者模式,管理事件变更通知) │ │ └─ Observer.java # 观察者接口(定义更新方法) │ ├─ service │ │ └─ NotepadManager.java # 核心管理类(单例,事件增删改查、存储策略、阴历映射) │ └─ strategy │ ├─ CsvStorageStrategy.java # CSV存储实现(读写events.csv) │ └─ StorageStrategy.java # 存储策略接口(可扩展JSON、数据库等存储) ├─ Referenced Libraries # IDE自动管理的依赖(如JRE库) ├─ lib # 第三方库(手动导入) │ ├─ commons-lang3-3.17.0.jar # Apache Commons Lang3(OpenCSV依赖) │ ├─ hutool-all-5.8.38.jar # Hutool(农历计算、日期工具) │ └─ opencsv-5.7.0.jar # OpenCSV(CSV文件读写) └─ events.csv # 事件数据存储文件(CSV格式)
|
四、阳历转阴历功能实现
注:本次代码编程时,考虑到客户可能不会maven,使用的是本地导入的Hutool。
1 2 3 4 5 6
| <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.38</version> </dependency>
|
2. 工具类LunarUtil实现
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 29 30 31 32
| package com.calendar.notepad.lunar;
import cn.hutool.core.date.ChineseDate; import java.util.Date;
public class LunarUtil {
public static String solarToLunarString(Date solarDate) { if (solarDate == null) return "无效日期"; try { ChineseDate lunar = new ChineseDate(solarDate); return lunar.getChineseYear() + "年" + lunar.getChineseMonthName() + lunar.getChineseDay(); } catch (Exception e) { System.err.println("阴历转换失败: " + e.getMessage()); return "转换失败"; } }
public static String getLunarKey(Date solarDate) { ChineseDate lunar = new ChineseDate(solarDate); return lunar.getChineseYear() + "-" + lunar.getMonth() + "-" + lunar.getChineseDay(); } }
|
3. 业务逻辑集成
在
中维护阴历事件映射:
1 2 3 4 5 6 7 8 9 10 11
| private Map<String, List<Event>> lunarEventMap = new HashMap<>();
private void updateLunarEventMap(Event event) { String key = LunarUtil.getLunarKey(event.getDate()); lunarEventMap.computeIfAbsent(key, k -> new ArrayList<>()).add(event); }
public List<Event> queryByLunarDate(Date solarDate) { String key = LunarUtil.getLunarKey(solarDate); return lunarEventMap.getOrDefault(key, new ArrayList<>()); }
|
五、Eclipse 导入本地 JAR
包教程
1. 下载 JAR 包
- OpenCSV:从Maven
中央仓库下载
opencsv-5.7.0.jar。
- Apache Commons
Lang3:下载
commons-lang3-3.12.0.jar。
2. 添加到项目
步骤 1:创建lib目录
在 Eclipse 项目根目录右键 → New Folder →
命名为lib。
步骤 2:复制 JAR 包
将下载的 JAR 文件复制到lib目录中。
步骤 3:配置构建路径
- 右键项目 →
Build Path →
Configure Build Path。
- 在
Libraries选项卡中,点击Add JARs。
- 选择
lib目录下的所有 JAR 包,点击OK。
验证
- 导入语句生效(如
import com.opencsv.CSVReader;)。
- 项目无红叉错误。
六、常见问题及解决方案
1. 依赖缺失问题
错误:CSVReader cannot be resolved to a type
- 原因:未添加 OpenCSV 依赖。
- 解决:手动导入 JAR 包或添加 Maven 依赖。
错误:NoClassDefFoundError: org/apache/commons/lang3/ObjectUtils
- 原因:OpenCSV 依赖 Apache Commons Lang3。
- 解决:添加
commons-lang3依赖。
2. 日期解析异常
错误:ArrayIndexOutOfBoundsException
- 原因:Hutool
处理非法日期(如
null或超出范围的日期)。
- 解决
- 在
CsvStorageStrategy中验证日期格式(yyyy-MM-dd)。
- 在
LunarUtil中添加日期范围检查(如 1900-2100 年)。
3. IDE 缓存问题
- 错误:类已导入但仍提示未找到(如
Date cannot be resolved)。
- 解决
- Eclipse:
Project → Clean。
- IntelliJ
IDEA:
File → Invalidate Caches / Restart。
七、源码展示
Main.java #
主程序入口(控制台交互)
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
| package com.calendar.notepad;
import com.calendar.notepad.lunar.LunarUtil; import com.calendar.notepad.model.Event; import com.calendar.notepad.observer.Observer; import com.calendar.notepad.service.NotepadManager; import com.calendar.notepad.strategy.CsvStorageStrategy; import com.calendar.notepad.strategy.StorageStrategy; import cn.hutool.core.date.DateUtil; import java.util.Date; import java.util.List; import java.util.Scanner;
public class Main { public static void main(String[] args) { NotepadManager manager = NotepadManager.getInstance(); StorageStrategy csvStrategy = new CsvStorageStrategy(); manager.setStorageStrategy(csvStrategy); manager.loadEvents(); manager.registerObserver(new ConsoleObserver());
Scanner scanner = new Scanner(System.in);
while (true) { System.out.println("\n===== 日历记事本系统 ====="); System.out.println("1. 添加事件"); System.out.println("2. 修改事件"); System.out.println("3. 删除事件"); System.out.println("4. 查看事件列表"); System.out.println("5. 按阳历查询事件"); System.out.println("6. 按阴历查询事件"); System.out.println("7. 查看当月日期范围"); System.out.println("0. 退出"); System.out.print("请选择操作: ");
try { int choice = scanner.nextInt(); scanner.nextLine();
switch (choice) { case 1: System.out.print("请输入事件标题: "); String title = scanner.nextLine(); System.out.print("请输入事件日期 (yyyy-MM-dd): "); String dateStr = scanner.nextLine(); System.out.print("请输入事件内容: "); String content = scanner.nextLine();
try { Date date = DateUtil.parse(dateStr); manager.addEvent(new Event(title, date, content)); System.out.println("事件添加成功!"); } catch (Exception e) { System.out.println("日期格式错误! 请使用 yyyy-MM-dd 格式"); } break;
case 2: System.out.print("请输入要修改的事件标题: "); String oldTitle = scanner.nextLine(); Event oldEvent = findEventByTitle(oldTitle, manager.getEventList());
if (oldEvent != null) { System.out.print("请输入新的事件标题 (留空则不修改): "); String newTitle = scanner.nextLine(); if (!newTitle.isEmpty()) oldEvent.setTitle(newTitle);
System.out.print("请输入新的事件日期 (yyyy-MM-dd, 留空则不修改): "); String newDateStr = scanner.nextLine(); if (!newDateStr.isEmpty()) { try { Date newDate = DateUtil.parse(newDateStr); oldEvent.setDate(newDate); } catch (Exception e) { System.out.println("日期格式错误!"); } }
System.out.print("请输入新的事件内容 (留空则不修改): "); String newContent = scanner.nextLine(); if (!newContent.isEmpty()) oldEvent.setContent(newContent);
manager.updateEvent(oldEvent, oldEvent); System.out.println("事件修改成功!"); } else { System.out.println("未找到该事件!"); } break;
case 3: System.out.print("请输入要删除的事件标题: "); String deleteTitle = scanner.nextLine(); Event eventToDelete = findEventByTitle(deleteTitle, manager.getEventList());
if (eventToDelete != null) { manager.deleteEvent(eventToDelete); System.out.println("事件删除成功!"); } else { System.out.println("未找到该事件!"); } break;
case 4: System.out.println("\n===== 事件列表 ====="); List<Event> allEvents = manager.getEventList(); if (allEvents.isEmpty()) { System.out.println("暂无事件记录!"); } else { for (Event event : allEvents) { String lunarDate = LunarUtil.solarToLunarString(event.getDate()); System.out.println(event + " (阴历: " + lunarDate + ")"); } } break;
case 5: System.out.print("请输入要查询的阳历日期 (yyyy-MM-dd): "); String queryDateStr = scanner.nextLine(); try { Date queryDate = DateUtil.parse(queryDateStr); List<Event> eventsOnDate = manager.queryBySolarDate(queryDate); printEvents("阳历 " + queryDateStr, eventsOnDate); } catch (Exception e) { System.out.println("日期格式错误!"); } break;
case 6: System.out.println("\n===== 按阴历查询事件 ====="); System.out.println("方式1: 输入阳历日期自动转换为阴历"); System.out.println("方式2: 直接输入阴历信息"); System.out.print("请选择查询方式 (1/2): "); int queryType = scanner.nextInt(); scanner.nextLine();
if (queryType == 1) { System.out.print("请输入阳历日期 (yyyy-MM-dd): "); String solarDateStr = scanner.nextLine(); try { Date solarDate = DateUtil.parse(solarDateStr); List<Event> events = manager.queryByLunarDate(solarDate); String lunarDateStr = LunarUtil.solarToLunarString(solarDate); printEvents("阴历 " + lunarDateStr, events); } catch (Exception e) { System.out.println("日期格式错误!"); } } else if (queryType == 2) { System.out.print("请输入阴历年份: "); int lunarYear = scanner.nextInt(); System.out.print("请输入阴历月份: "); int lunarMonth = scanner.nextInt(); System.out.print("请输入阴历日期 (如: 初一): "); String lunarDay = scanner.next(); scanner.nextLine();
List<Event> events = manager.queryByLunarDate(lunarYear, lunarMonth, lunarDay); printEvents("阴历 " + lunarYear + "年" + lunarMonth + "月" + lunarDay, events); } else { System.out.println("无效的选择!"); } break;
case 7: Date today = new Date(); String lunarToday = LunarUtil.getFullLunarInfo(today); System.out.println("\n===== 当月日期范围 ====="); System.out.println("今日阳历日期: " + DateUtil.format(today, "yyyy-MM-dd")); System.out.println("今日阴历日期: " + lunarToday); break;
case 0: System.out.println("感谢使用日历记事本系统,再见!"); scanner.close(); System.exit(0);
default: System.out.println("无效的选择,请重新输入!"); } } catch (java.util.InputMismatchException e) { System.out.println("输入无效,请输入数字!"); scanner.nextLine(); } } }
private static Event findEventByTitle(String title, List<Event> events) { for (Event event : events) { if (event.getTitle().equals(title)) { return event; } } return null; }
private static void printEvents(String dateDescription, List<Event> events) { System.out.println("\n===== " + dateDescription + " 的事件 ====="); if (events.isEmpty()) { System.out.println("该日期没有事件记录!"); } else { events.forEach(System.out::println); } }
static class ConsoleObserver implements Observer { @Override public void update(String message) { System.out.println("[通知] " + message); } } }
|
LunarUtil.java #
阴历工具类(阳历转阴历、农历键生成)
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| package com.calendar.notepad.lunar;
import cn.hutool.core.date.ChineseDate; import cn.hutool.core.date.DateUtil; import java.util.Date;
public class LunarUtil {
public static String solarToLunarString(Date solarDate) { ChineseDate lunar = new ChineseDate(solarDate); return lunar.getChineseYear() + "年" + lunar.getChineseMonthName() + lunar.getChineseDay(); }
public static int getLunarYear(Date solarDate) { return new ChineseDate(solarDate).getChineseYear(); }
public static int getLunarMonth(Date solarDate) { return new ChineseDate(solarDate).getMonth(); }
public static String getLunarDay(Date solarDate) { return new ChineseDate(solarDate).getChineseDay(); }
public static String getLunarKey(Date solarDate) { ChineseDate lunar = new ChineseDate(solarDate); return lunar.getChineseYear() + "-" + lunar.getMonth() + "-" + lunar.getChineseDay(); }
public static String getFullLunarInfo(Date solarDate) { ChineseDate lunar = new ChineseDate(solarDate); String festivals = lunar.getFestivals(); return lunar.toString() + (festivals.isEmpty() ? "" : " (" + festivals + ")"); } }
|
Event.java #
事件实体(标题、日期、内容)
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| package com.calendar.notepad.model;
import com.calendar.notepad.lunar.LunarUtil; import cn.hutool.core.date.DateUtil; import java.util.Date;
public class Event { private String title; private Date date; private String content;
public Event(String title, Date date, String content) { this.title = title; this.date = date; this.content = content; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public Date getDate() { return date; }
public void setDate(Date date) { this.date = date; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
@Override public String toString() { return "Event{title='" + title + "', date=" + DateUtil.format(date, "yyyy-MM-dd") + ", lunarDate=" + LunarUtil.solarToLunarString(date) + ", content='" + content + "'}"; } }
|
EventNotifier.java
# 事件通知器(观察者模式,管理事件变更通知)
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
| package com.calendar.notepad.observer;
import java.util.ArrayList; import java.util.List;
public class EventNotifier { private List<Observer> observers = new ArrayList<>();
public void registerObserver(Observer observer) { observers.add(observer); }
public void removeObserver(Observer observer) { observers.remove(observer); }
public void notifyObservers(String message) { for (Observer observer : observers) { observer.update(message); } } }
|
Observer.java #
观察者接口(定义更新方法)
1 2 3 4 5 6 7 8
| package com.calendar.notepad.observer;
public interface Observer { void update(String message); }
|
NotepadManager.java
# 核心管理类(单例,事件增删改查、存储策略、阴历映射)
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
| package com.calendar.notepad.service;
import com.calendar.notepad.lunar.LunarUtil; import com.calendar.notepad.model.Event; import com.calendar.notepad.observer.EventNotifier; import com.calendar.notepad.strategy.StorageStrategy; import java.util.*;
public class NotepadManager { private static NotepadManager instance; private List<Event> eventList; private Map<String, List<Event>> lunarEventMap; private StorageStrategy storageStrategy; private EventNotifier eventNotifier;
private NotepadManager() { eventList = new ArrayList<>(); eventNotifier = new EventNotifier(); lunarEventMap = new HashMap<>(); }
public static synchronized NotepadManager getInstance() { if (instance == null) { instance = new NotepadManager(); } return instance; }
public void setStorageStrategy(StorageStrategy strategy) { this.storageStrategy = strategy; }
public void addEvent(Event event) { eventList.add(event); updateLunarEventMap(event); eventNotifier.notifyObservers("添加了事件: " + event.getTitle()); if (storageStrategy != null) { storageStrategy.saveEvents(eventList); } }
public void updateEvent(Event oldEvent, Event newEvent) { int index = eventList.indexOf(oldEvent); if (index != -1) { removeFromLunarEventMap(oldEvent); eventList.set(index, newEvent); updateLunarEventMap(newEvent); eventNotifier.notifyObservers("更新了事件: " + oldEvent.getTitle()); if (storageStrategy != null) { storageStrategy.saveEvents(eventList); } } }
public void deleteEvent(Event event) { eventList.remove(event); removeFromLunarEventMap(event); eventNotifier.notifyObservers("删除了事件: " + event.getTitle()); if (storageStrategy != null) { storageStrategy.saveEvents(eventList); } }
public List<Event> getEventList() { return eventList; }
public List<Event> queryBySolarDate(Date date) { List<Event> result = new ArrayList<>(); for (Event event : eventList) { if (event.getDate().equals(date)) { result.add(event); } } return result; }
public List<Event> queryByLunarDate(Date solarDate) { String key = LunarUtil.getLunarKey(solarDate); return lunarEventMap.getOrDefault(key, new ArrayList<>()); }
public List<Event> queryByLunarDate(int lunarYear, int lunarMonth, String lunarDay) { String key = lunarYear + "-" + lunarMonth + "-" + lunarDay; return lunarEventMap.getOrDefault(key, new ArrayList<>()); }
public void registerObserver(com.calendar.notepad.observer.Observer observer) { eventNotifier.registerObserver(observer); }
public void removeObserver(com.calendar.notepad.observer.Observer observer) { eventNotifier.removeObserver(observer); }
public void loadEvents() { if (storageStrategy != null) { this.eventList = storageStrategy.loadEvents(); rebuildLunarEventMap(); } }
private void updateLunarEventMap(Event event) { String key = LunarUtil.getLunarKey(event.getDate()); lunarEventMap.computeIfAbsent(key, k -> new ArrayList<>()).add(event); }
private void removeFromLunarEventMap(Event event) { String key = LunarUtil.getLunarKey(event.getDate()); List<Event> events = lunarEventMap.get(key); if (events != null) { events.remove(event); if (events.isEmpty()) { lunarEventMap.remove(key); } } }
private void rebuildLunarEventMap() { lunarEventMap.clear(); for (Event event : eventList) { updateLunarEventMap(event); } } }
|
CsvStorageStrategy.java
# CSV存储实现(读写events.csv)
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| package com.calendar.notepad.strategy;
import com.calendar.notepad.model.Event; import com.opencsv.CSVReader; import com.opencsv.CSVWriter; import com.opencsv.exceptions.CsvValidationException; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List;
public class CsvStorageStrategy implements StorageStrategy { private static final String FILE_PATH = "events.csv"; private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
@Override public List<Event> loadEvents() { List<Event> events = new ArrayList<>(); try (CSVReader reader = new CSVReader(new FileReader(FILE_PATH))) { String[] nextLine; while ((nextLine = reader.readNext()) != null) { if (nextLine.length == 3) { try { Date date = DATE_FORMAT.parse(nextLine[1]); events.add(new Event(nextLine[0], date, nextLine[2])); } catch (ParseException e) { System.err.println("无效日期格式: " + nextLine[1]); } } } } catch (FileNotFoundException e) { } catch (CsvValidationException e) { System.err.println("CSV格式验证失败: " + e.getMessage()); } catch (IOException e) { e.printStackTrace(); } return events; }
@Override public void saveEvents(List<Event> events) { try (CSVWriter writer = new CSVWriter(new FileWriter(FILE_PATH))) { for (Event event : events) { String[] line = { event.getTitle(), DATE_FORMAT.format(event.getDate()), event.getContent() }; writer.writeNext(line); } } catch (IOException e) { e.printStackTrace(); } } }
|
StorageStrategy.java
# 存储策略接口(可扩展JSON、数据库等存储)
1 2 3 4 5 6 7 8 9 10 11 12
| package com.calendar.notepad.strategy;
import com.calendar.notepad.model.Event; import java.util.List;
public interface StorageStrategy { void saveEvents(List<Event> events); List<Event> loadEvents(); }
|
八、总结
本项目通过 Hutool 简化日期处理,OpenCSV
实现数据持久化,并结合设计模式提升代码可维护性。核心关键点包括:
- 单例模式确保全局唯一实例。
- 策略模式解耦存储逻辑。
- Hutool 的
ChineseDate实现阳历转阴历。
- 多层防御机制处理非法日期(解析时验证、工具类过滤、加载后清理)。
通过合理使用工具库和设计模式,有效提升了开发效率和代码健壮性。