☰
Java课设通讯录管理系统开发全攻略:存储选型、代码实现与避坑指南
2026/10/14 17:25:00 网站建设 项目流程

简介:一套面向Java课程设计场景的通讯录管理系统完整项目,适合正在完成数据库与GUI综合实训的本科生或自学者,也适合需要快速搭建同类管理系统的开发者参考。系统实现用户注册与登录、通讯录信息浏览、查找、增加、删除、修改等核心功能,采用JDBC连接MySQL数据库完成数据持久化,界面基于Swing GUI组件构建,模块划分清晰,有助于学习Java面向对象设计、事件处理以及JDBC数据库编程。压缩包共5个文件,包含4个Java源文件,分别承担数据库连接工具、登录界面、注册界面和通讯录主界面,另附1个SQL脚本用于初始化表结构,导入数据库后即可与代码配合运行。整个资源包仅8KB,体量精简,便于逐文件阅读和导入IDE调试。已有1562人学习下载,对于需要完整可运行课设源码、梳理设计与实现思路的同学具有较好的参考价值。

1. Java课程设计里那个最容易低估的题目:通讯录管理系统

如果你在Java课程设计选题里又一次看到“通讯录管理系统”,别急着嫌它老。这个题目看起来平淡,却几乎是出现频率最高的课设题之一,因为它刚好把面向对象设计、集合与泛型、异常处理、文件IO这几块考核重点全部串在一起,比做一个花哨的贪吃蛇更能看出你对完整流程的把控。我见过不少做课设的人在答辩时被问到“这个项目分了几层”当场卡壳,也见过把通讯录做成带持久化、Swing界面和分组统计的版本直接拿高分。这篇笔记不聊虚的,只讲怎么把一个能交差、能扛住追问的通讯录管理系统一步步做出来:需求怎么拆、存储怎么选、核心代码怎么写、界面和文件读写有哪些坑,以及最后哪些小改动能帮你提分。

2. 先想清楚再做:通讯录管理系统的功能边界与存储选型

动手前先定边界,这比写第一行代码更重要。很多课设翻车不是因为功能太少,而是需求模糊:先写代码再想需求,最后发现要么少了删除,要么数据根本没保存。通讯录这个题目边界很清楚,先把“存哪些字段”和“怎么存”这两个问题定下来,后面所有代码都是顺着这两条线长出来的。

2.1 通讯录该存哪些字段:从课设评审角度做需求建模

最基本的通讯录,字段不需要多,但要想清楚为什么是这个而不是别的。下面是我在实际课设里习惯用的字段表:

字段类型建议约束备注
nameString必填,不能为空白姓名或昵称
phonesList<String>至少一个手机/座机用List支持一人多号
emailString可选,格式校验正则校验,别做太严
groupNameString可选,默认“未分组”分组展示用
remarkString可选备注
createdAtLocalDateTime自动生成排序和统计用

字段不是越多越好。性别、生日、头像、地址这些字段加进去,界面会复杂很多,评审却不会因为你字段多就给高分。真正会被追问的点是:一个人有两个号码怎么办?所以phones用List<String>而不用单个String字段,这是通讯录和“名片夹”的本质区别。

邮箱校验用一个正则就够了,给个可复制的写法:

public static boolean isValidEmail(String email) { return email != null && email.matches("^[\\w.-]+@[\\w-]+(\\.[\\w-]+)+$"); }

逻辑说明:matches方法要求整个字符串完全匹配正则,不是“包含匹配”。\w在Java字符串里需要写成转义后的形式,等价于英文字母、数字和下划线。参数说明:email传null时直接返回false,不会抛空指针。

2.2 为什么我推荐“纯文件 + 对象序列化”而不是一上来就上数据库

很多同学一听到“管理系统”就默认要MySQL加JDBC,觉得这才够高级。但课程设计的时间通常只有几周,答辩环境不可控,最稳妥的方案其实是文件持久化。我一般会把两种方案的差异直接摆在桌面上:

对比维度对象序列化文件JDBC + MySQL
外部依赖无,JDK自带需要MySQL服务和驱动jar
代码量约30行读写代码连接、预编译、结果集处理,起步100行+
答辩风险拷贝项目就能跑换一台机器连不上库,当场翻车
数据规模万级以下足够适合大数据量、复杂查询
适合场景课设、小工具企业开发、题目强制要求数据库

通讯录课设的数据量撑死几百上千条联系人,ArrayList和HashMap完全够用,文件读写耗时才几十毫秒。用数据库不等于高分,评审更在意你是不是把数据流讲清楚了:新增数据怎么写进去、启动时怎么读出来、删除后怎么保持一致。

如果你的课设题目明确要求“使用数据库”,那先确认是不是硬性要求。不是的话,文件方案更省心;是的话,我建议先把文件版本跑通,再在store层加一套JDBC实现,答辩现场优先演示文件模式,避免数据库服务没启动这种尴尬。这不是偷懒,是给整个项目留一条兜底路径。

2.3 最小可行功能清单:先做到能交差

拿到题目后别急着写代码,先对着清单确认自己有没有遗漏。一个能交差的通讯录管理系统,至少要做到这六件事:

  1. 添加联系人:姓名必填,电话至少一个,邮箱可空,添加后立即写入文件。
  2. 删除联系人:按姓名删除,有重名时列出候选让用户选择。
  3. 修改联系人:先查到人,再改字段,改完重新保存。
  4. 查询联系人:支持按姓名精确或模糊匹配,返回结果列表。
  5. 列出全部:按添加先后或姓名排序展示。
  6. 退出不丢数据:每次增删改都同步落盘,而不是退出时一次性保存。

这个清单的验收标准只有一条:程序重启以后,上次添加的数据还在。这一条能筛掉一大堆“删了没保存”“文件写不进去”的问题。删除和查询是答辩时最容易被试的两项,优先做扎实。

3. 用Java把通讯录搭起来:核心分层与关键代码

代码不要全堆在一个类里,这是我给所有做课设的人的第一条建议。一个通讯录管理系统按三层拆就够了:entity放实体类,store放文件读写,service放业务逻辑,界面单独放一个包。三层职责清楚,答辩时介绍项目结构也顺理成章。

3.1 Contact实体类:把数据模型先定稳

实体类是一切的地基。字段定稳,后面的工具类和数据层都不会乱。下面是一个建议的Contact类:

package entity; import java.io.Serializable; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; public class Contact implements Serializable { private static final long serialVersionUID = 1L; private String name; private List<String> phones = new ArrayList<>(); private String email; private String groupName = "未分组"; private String remark; private LocalDateTime createdAt = LocalDateTime.now(); public Contact() {} public Contact(String name, String phone) { this.name = name; this.phones.add(phone); } public String getFirstPhone() { return phones.isEmpty() ? "" : phones.get(0); } // 其他 getter/setter 由 IDE 自动生成 }

逻辑说明:implements Serializable是为了后面用ObjectOutputStream直接把整个List写入文件;serialVersionUID必须写死,这是给后期改类结构留的后悔药。phones用List<String>而不是String,前面说过,一人多号是通讯录最常见的数据形态。createdAt用LocalDateTime而不是字符串,排序时可以直接比较时间。

参数说明:getFirstPhone方法给表格展示用,取第一个号码作为主电话;无号码时返回空字符串,避免界面显示“null”。构造器只接收name和phone,方便测试和后期扩展。

3.2 文件读写:用对象流做持久化

存储层只做两件事:把联系人列表写进文件,启动时读回来。这段代码是很多课设的翻车重灾区,问题往往不在逻辑,而在细节:

package store; import entity.Contact; import java.io.*; import java.util.ArrayList; import java.util.List; public class ContactStore { private static final String FILE = "contacts.dat"; public void save(List<Contact> contacts) { try (ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(FILE))) { out.writeObject(contacts); } catch (IOException e) { e.printStackTrace(); } } @SuppressWarnings("unchecked") public List<Contact> load() { File file = new File(FILE); if (!file.exists()) { return new ArrayList<>(); } try (ObjectInputStream in = new ObjectInputStream( new FileInputStream(FILE))) { return (List<Contact>) in.readObject(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException("联系人数据读取失败", e); } } }

逻辑说明:try-with-resources会自动关闭流,无论代码是否抛异常,这是Java 7以后的标准写法。load方法在文件不存在时返回空集合,而不是抛异常,保证第一次运行程序不会闪退。读取时强转成List<Contact>,因为ObjectInputStream.readObject返回的是Object类型。

参数说明:FILE用的是相对路径,在IDE里运行会写到项目根目录,打包成jar后路径会变,这个问题第5章专门说。catch里直接抛RuntimeException而不是吞掉异常,这样启动时如果文件损坏,你能看到明确的错误提示,而不是程序静默黑屏。如果你担心文件损坏导致整个系统起不来,可以在调用层catch RuntimeException并提示用户删除数据文件重建。

3.3 服务层:把增删改查和模糊搜索做对

服务层是连接界面和数据层的桥梁。这里最容易犯的错是把业务逻辑写在界面里,最后界面代码膨胀到没人愿意读。下面这个ContactService是常用的骨架:

package service; import entity.Contact; import store.ContactStore; import java.util.ArrayList; import java.util.List; public class ContactService { private final List<Contact> contacts = new ArrayList<>(); private final ContactStore store = new ContactStore(); public ContactService() { contacts.addAll(store.load()); } public boolean add(Contact c) { if (c.getName() == null || c.getName().trim().isEmpty()) { return false; // 姓名必填 } contacts.add(c); store.save(contacts); return true; } public List<Contact> search(String keyword) { if (keyword == null || keyword.trim().isEmpty()) { return new ArrayList<>(contacts); // 返回副本,避免外部修改 } String key = keyword.trim().toLowerCase(); List<Contact> result = new ArrayList<>(); for (Contact c : contacts) { if (c.getName().toLowerCase().contains(key) || c.getEmail().toLowerCase().contains(key)) { result.add(c); } } return result; } public boolean deleteByName(String name) { return contacts.removeIf(c -> c.getName().equals(name)); } }

逻辑说明:构造方法里调用store.load(),把磁盘上的联系人全部读进内存。之后所有操作都直接操作这个List,每次写完后马上store.save(contacts),保证“增删改后立即落盘”。search方法里keyword为空时返回的是new ArrayList<>(contacts)而不是contacts本身,这是防御式编程,避免调用方拿到原始引用后改了内部数据。

参数说明:removeIf内部基于Iterator实现,边遍历边删除是安全的,不会抛ConcurrentModificationException。add方法只做了最核心的姓名校验,更完整的校验可以放在界面层提示用户。删除逻辑直接按姓名匹配,如果允许重名,这个方法会删掉第一个匹配项;更稳妥的做法是先查出所有同名联系人让用户选。

3.4 跑通一个最小控制台入口

在做Swing界面之前,先写一个几十行的控制台版本,用来验证service和store是否正常。这个小小的入口能让你在图形界面之前就把数据流摸清,后面调界面问题时不用怀疑底层。

import service.ContactService; import entity.Contact; import java.util.Scanner; public class ConsoleMain { public static void main(String[] args) { ContactService service = new ContactService(); Scanner scanner = new Scanner(System.in); while (true) { System.out.println("请输入数字:1=添加 2=删除 3=搜索 4=全部 5=退出"); String choice = scanner.nextLine(); if ("1".equals(choice)) { System.out.print("姓名:"); String name = scanner.nextLine().trim(); System.out.print("电话:"); String phone = scanner.nextLine().trim(); service.add(new Contact(name, phone)); } else if ("5".equals(choice)) { break; } else { System.out.println("输入有误"); } } scanner.close(); } }

逻辑说明:这里用“1”.equals(choice)而不是choice.equals("1"),是因为choice可能为null,常量放在前面能避免空指针。命令行输入的数字是字符串,所以要比较字符串而不是int。参数说明:scanner.nextLine()读到的是整行文本,trim()去掉首尾空格,避免用户误输入产生脏数据。

4. 把界面做出来:控制台之外,用Swing做一个能演示的通讯录窗口

控制台版本能通过验收,但很多课设题目会带“可视化界面”要求。这时候首选Swing,不是因为它时尚,而是它随JDK一起发布,不需要额外引入任何库,答辩环境出问题的概率最低。JavaFX虽然界面更现代,但需要单独配置模块,学生机器上没装JavaFX SDK就得现场装,风险很高。

4.1 图形界面选型:为什么课设首选Swing

对比三种方案,选择就变得很直接:

方案依赖学习成本答辩风险界面效果
控制台无低低,但没界面无
SwingJDK自带中低简洁可用
JavaFX需要单独配置高高美观现代

Swing老归老,但JTable加DefaultTableModel这个组合做通讯录非常顺手:数据天然是二维表格,联系人列表每行一个人,列对应姓名、电话、邮箱、分组。Swing没有数据绑定,所以需要手动刷新表格,这个思路在答辩时反而容易解释清楚。

4.2 主窗口骨架:JFrame加BorderLayout加JTable

主界面没必要做得很复杂,一个顶部工具栏、一个中间表格、一个底部状态栏就是完整的通讯录界面。核心代码如下:

import javax.swing.*; import java.awt.*; public class ContactFrame extends JFrame { private final service.ContactService service = new service.ContactService(); private final DefaultTableModel model = new DefaultTableModel(new Object[]{"姓名", "电话", "邮箱", "分组"}, 0); private final JTable table = new JTable(model); public ContactFrame() { setTitle("通讯录管理系统"); setSize(720, 480); setDefaultCloseOperation(EXIT_ON_CLOSE); add(new JScrollPane(table), BorderLayout.CENTER); refreshTable(); } private void refreshTable() { model.setRowCount(0); for (entity.Contact c : service.search("")) { model.addRow(new Object[]{ c.getName(), String.join(",", c.getPhones()), c.getEmail(), c.getGroupName() }); } } }

逻辑说明:DefaultTableModel构造器的第一个参数是列名数组,第二个参数初始行数设为0,表示从空表开始。JTable本身不能滚动,必须放进JScrollPane才能有表头和滚动条,这是一个新人常踩的点。BorderLayout把面板划分为上下左右中,这里只用了中间区域。

参数说明:refreshTable每次先setRowCount(0)清空旧数据,再逐条addRow。如果忘了清空,每次刷新表格都会在原来基础上叠加新行,越点越多。电话列表c.getPhones()直接打印会带ArrayList的方括号,String.join(",", ...)把多个号码用逗号拼成字符串,显示效果才正常。

4.3 增删按钮的交互:弹窗输入、校验与刷新

新增联系人用JOptionPane弹窗收集多个字段,比连续弹四个输入框体验好得多。下面是添加联系人按钮的监听逻辑:

private void addButtonAction() { JTextField nameField = new JTextField(10); JTextField phoneField = new JTextField(15); JTextField emailField = new JTextField(15); Object[] fields = {"姓名", nameField, "电话", phoneField, "邮箱", emailField}; int result = JOptionPane.showConfirmDialog(this, fields, "添加联系人", JOptionPane.OK_CANCEL_OPTION); if (result != JOptionPane.OK_OPTION) { return; // 用户点了取消或关闭窗口 } String name = nameField.getText().trim(); String phone = phoneField.getText().trim(); if (name.isEmpty()) { JOptionPane.showMessageDialog(this, "姓名不能为空"); return; } entity.Contact c = new entity.Contact(name, phone); c.setEmail(emailField.getText().trim()); if (service.add(c)) { refreshTable(); } }

JButton的点击监听可以直接用lambda写:btnAdd.addActionListener(e -> addButtonAction())。逻辑说明:showConfirmDialog返回值的类型是int,必须等于OK_OPTION才继续,用户点取消时返回CANCEL_OPTION,此时直接return。校验分两层:界面层给用户友好提示,服务层add方法里还有兜底校验。

删除按钮比新增更需要注意边界:先判断用户有没有选中某一行。table.getSelectedRow()返回选中行的行号,没选中时返回-1;拿这个负数去model.getValueAt会抛数组越界异常。所以删除前必须有类似这样的判断:

int row = table.getSelectedRow(); if (row < 0) { JOptionPane.showMessageDialog(this, "请先选择要删除的联系人"); return; } String name = (String) model.getValueAt(row, 0); service.deleteByName(name); refreshTable();

逻辑说明:这里演示了“先选行再取值”的流程,拿到选中行的姓名后再交给service层删除。还有一个细节:Swing里表格可能被用户拖动列排序,界面上显示的行顺序和model里的顺序不一致,getSelectedRow返回的是视图行号,取值时用table.convertRowIndexToModel(row)转换更稳妥。课设数据量小,这个坑遇到概率不高,但说一句能让答辩老师知道你想过这件事。

4.4 排序与显示细节:让界面更像一个系统

最后再加一个小改动:每次刷新表格时按姓名排序。Java里对中文排序不能直接用String的compareTo,那是Unicode码点顺序,效果和字典序差很远。用Collator可以按中文语言习惯排:

import java.text.Collator; import java.util.Comparator; import java.util.Locale; Comparator<entity.Contact> byName = Comparator.comparing( entity.Contact::getName, Collator.getInstance(Locale.CHINA)); contacts.sort(byName);

逻辑说明:Collator.getInstance(Locale.CHINA)拿到的比较器会按中文拼音和笔画混合的顺序排,课设里展示“我考虑过中文排序”已经够用。真正的纯拼音排序需要额外的拼音库,这个就不建议在课设阶段引入了。参数说明:这里只是示例,实际调用时把contacts替换成service层暴露的列表,或者直接在服务层的load方法里排序。

5. 通讯录管理系统避坑:课程设计里最常见的五个翻车现场

前面代码都能跑通,但课设答辩翻车通常不在功能,而在一些隐蔽问题。这一章总结五个高频坑,都是“现象—原因—解决”的结构,你离交差往往就差这几步。

5.1 数据文件写进去了,重启却读不出来:serialVersionUID没定义

现象:程序运行后添加几个联系人,关闭再启动,联系人列表全没了;严重的时候直接抛java.io.InvalidClassException。

原因:Contact类实现了Serializable但没有写serialVersionUID。Java序列化时会给类计算一个默认ID,这个ID和类的内部结构绑定,你只要修改了Contact类,哪怕只是加了一个字段,ID就变了,旧文件里的数据再反序列化时会判定“类版本不一致”而失败。课程设计开发期间改字段非常频繁,所以这个问题几乎必踩。

解决:在Contact类上写private static final long serialVersionUID = 1L。只要你不故意改这个数字,类结构的小改动不会影响读取。如果你已经生成了旧文件且类结构大改,最简单的解决方法是把contacts.dat删掉重新来,课程设计阶段数据不值钱,不需要写迁移逻辑。

5.2 删除联系人时抛ConcurrentModificationException:for循环边遍历边删

现象:删除一个联系人时,控制台或界面抛ConcurrentModificationException,程序没退出,但数据没删掉。

原因:用for-each遍历List,在循环体里调用list.remove()。for-each底层是Iterator,删除后迭代器的游标失效,下一次循环调用next()时就抛异常。

解决:改用removeIf,或者显式使用Iterator:

Iterator<Contact> it = contacts.iterator(); while (it.hasNext()) { Contact c = it.next(); if (c.getName().equals(name)) { it.remove(); break; } }

逻辑说明:iterator.remove()是安全的,它会在删除后同步维护迭代器状态。break表示只删除第一个匹配项,不写break则会删除所有同名联系人。参数说明:name在比较前记得trim一下,避免用户输入“张三 ”和存储的“张三”匹配不上。

5.3 Swing界面刷新没反应或者直接卡死:忘掉事件调度线程

现象:点击添加按钮后,如果涉及大量数据读取或网络操作,窗口变白板,操作系统提示“未响应”;或者点了按钮界面没变化,过一会所有结果突然一起刷新出来。

原因:Swing是单线程模型,所有界面更新必须在事件分发线程(Event Dispatch Thread)上执行。如果你在ActionListener里做了耗时操作,事件线程被堵住,界面就卡死。反过来,如果你在后台线程里直接调用refreshTable,又会触发线程安全问题。

解决:课程设计的数据量下,文件读写很快,通常不会卡死。但如果你非要演示“耗时场景”,用SwingWorker是标准做法:

new SwingWorker<Void, Void>() { @Override protected Void doInBackground() { service.add(contact); // 后台线程执行 return null; } @Override protected void done() { refreshTable(); // 回到事件线程刷新界面 } }.execute();

逻辑说明:doInBackground里做耗时操作,done方法在操作完成后自动切回事件分发线程,这时候才能安全刷新表格。这个写法在通讯录系统里有点杀鸡用牛刀,但答辩时能说出来,说明你理解Swing的线程模型。

5.4 打包成可运行jar之后,联系人文件写到哪去了:相对路径问题

现象:在IDE里运行一切正常,导出jar包双击运行,添加联系人后重启数据丢失,或者项目目录下找不到contacts.dat文件。

原因:代码里用的是相对路径contacts.dat,它相对于程序的工作目录。在IDE里运行,工作目录是项目根目录,文件写在这里;双击jar时,工作目录可能是用户目录、桌面或其他位置,文件就写到了那里,用户根本不知道,甚至可能因为目录没有写权限直接报错。

解决:把数据文件放到用户目录下的固定位置,跨平台也安全:

private static final String FILE = System.getProperty("user.home") + "/.contacts_app/contacts.dat";

逻辑说明:user.home返回当前系统用户的主目录,Windows是C:\Users\用户名,Linux和macOS是/home/用户名,Java做了跨平台处理。参数说明:目录名.contacts_app建议启动时创建,否则首次写文件会报NoSuchFileException。如果你课设答辩时只演示IDE运行,不改问题也不大,但我建议改掉,这个细节能体现工程意识,评审看到会加分。

5.5 题目要求必须用数据库:JDBC连接失败翻车

现象:用了MySQL加JDBC,答辩现场运行到连接数据库时报ClassNotFoundException或CommunicationsException。

原因:常见有三种。驱动jar没有打进classpath;JDBC URL写错,比如MySQL 8需要指定时区;本机MySQL服务根本没启动,或者账号密码不对。这三个问题任何一个都能让程序在连接阶段直接崩掉。

解决:如果课设强制用数据库,至少保证三件事。第一,驱动jar放对位置,并显式加载Driver类:

Class.forName("com.mysql.cj.jdbc.Driver");

第二,URL写全参数:

String url = "jdbc:mysql://localhost:3306/contacts" + "?useSSL=false&serverTimezone=Asia/Shanghai" + "&characterEncoding=utf8";

第三,在main方法里先做一次独立连接测试,连接成功再进入主界面,失败就弹一个明确的中文提示,而不是让堆栈刷屏。逻辑说明:useSSL=false是因为本地开发不需要SSL加密;serverTimezone是MySQL 8的必填参数,不写会报时区错误;characterEncoding=utf8是为了避免中文乱码。

如果是我做强制数据库的课设,我会先把文件版本完整跑通,再把store层替换成JDBC实现,这样万一现场数据库崩了,还能用文件模式兜底演示。这不是骗分,是工程上的降级策略。

6. 让通讯录管理系统从能用变得更好用:三个低成本改动

基础功能做完之后,还有几个改动不大但很提分的点,评审一眼就能看出你不是只抄了个模板。

6.1 给联系人增加分组统计

Contact类里已经有groupName字段,剩下的只是把它用起来。在主界面加一个下拉框,列出“全部/同学/同事/家人/未分组”,选择后只显示对应组的联系人。这个改动只需要在service层加一个带分组的查询方法,界面多一个JComboBox,半小时能做完。答辩时可以顺势讲“我用分组字段做了二级视图”,比单纯一个列表体面不少。

6.2 导出CSV,让数据能被Excel打开

序列化文件只有程序自己能读,导出成CSV则能让用户在Excel里直接看到联系人列表,是一个很实用的加分功能:

public void exportCsv(File file) throws IOException { try (BufferedWriter writer = Files.newBufferedWriter( file.toPath(), StandardCharsets.UTF_8)) { writer.write("姓名,电话,邮箱,分组\n"); for (Contact c : contacts) { writer.write(String.join(",", c.getName(), String.join(";", c.getPhones()), c.getEmail(), c.getGroupName()) + "\n"); } } }

逻辑说明:用UTF-8编码写文件,避免Excel打开时中文乱码。电话里的多个号码用分号隔开,不会破坏CSV的列结构。参数说明:如果字段本身包含英文逗号,CSV会错列,正确做法是用双引号包裹该字段,通讯录场景一般不会出现,知道有这回事就行。

6.3 重名检查:体现业务规则的细节

现在的add方法只要有姓名就允许添加,但通讯录遇到同名联系人怎么办?一个很简单的业务规则是“添加前检查重名,弹窗提示并让用户确认是否继续”。在service层加一个existsName方法,界面层调用它提示用户,这比什么都没做强太多。更规范的做法是把新增和修改共用的校验逻辑抽成一个validate方法,界面层只管收集输入,业务约束全部收敛在service层。这个分层习惯能让你的代码结构明显比别人清晰。

我自己做这类课设的习惯是:先把实体类和文件路径定死,再写服务层,最后才挂界面。这样中途改需求不会牵一发动全身。如果你正在某个坑里挣扎,别急着推翻重来,先把数据文件的读写逻辑单独验证好,再回头看界面刷新,问题往往就出在这条线上。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询