news 2026/10/20 2:26:28

Java通讯录管理系统课程设计:Swing+JDBC实现增删改查与分层架构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java通讯录管理系统课程设计:Swing+JDBC实现增删改查与分层架构

简介:这是一份面向Java初学者与课程设计学习者的通讯录管理系统完整项目源码,对应Java课程设计综合题,要求采用JDBC连接数据库,界面可选Swing GUI或JSP技术实现。系统涵盖用户注册登录、通讯录信息的浏览、查找与增删改维护等核心功能,适合用来练习面向对象编程思想,理解类的设计、继承与多态,并掌握Swing窗体、面板、按钮、文本框等组件的使用及事件处理机制。资源包共5个文件,包含4个java源码文件与1个sql数据库脚本,压缩包约8KB,其中java文件分别对应登录界面、注册界面、通讯录主界面与数据库连接工具类,sql文件用于建库建表,结构清晰便于对照学习。目前已有1562人学习下载。通过这份资源,读者可获得一套可直接运行的课程设计参考方案,学习JDBC连接MySQL、数据库表结构设计及增删改查操作,并体会模块化拆分系统的设计思路,适合作为课程设计起步或期末项目参考。

1. 通讯录管理系统:从课程设计到能跑起来的 Java 项目

很多同学的 Java 课程设计都卡在同一个地方:功能想得挺全,代码一跑就报错,界面和数据库各玩各的。通讯录管理系统就是最典型的例子——看起来只是增删改查,真动手写的时候,联系人字段怎么设计、数据存哪里、界面怎么刷新,每一步都能踩坑。这个项目适合正在做 Java 课程设计、需要一份能直接跑通的参考实现的同学,也适合想拿它当模板改造成图书管理、学生信息管理的开发者。它用 Java 实现,覆盖联系人录入、查询、修改、删除、列表展示这些核心功能,数据持久化方案常见的有两种:文件存储和数据库存储。下面从项目定位、目录结构、核心代码到二次开发,完整拆一遍。

2. 项目定位与技术选型:为什么用 Java 做通讯录

2.1 这个项目到底解决什么问题

通讯录管理系统的本质是一个带持久化的 CRUD 应用。用户打开程序后,能录入联系人信息(姓名、电话、邮箱、分组等),能按姓名或电话搜索,能修改已有记录,能删除不再需要的联系人,退出程序后数据不丢失。听起来简单,但课程设计的要求通常不止于此——老师会看你的代码结构是否清晰、有没有分层、异常处理是否到位、界面是否可用。

这个项目的价值在于:它足够小,小到一个人几天能写完;又足够完整,完整到能体现 Java 面向对象、集合框架、IO 流或 JDBC、Swing 或 JavaFX 界面编程这些核心知识点。很多同学做课程设计时容易贪大,选个电商系统或者社交平台,结果功能没写完,代码一团乱。通讯录管理系统反而是最稳的选择,功能边界清晰,扩展方向也多。

从适用人群来看,如果你是刚学完 Java 基础、正在做课程设计的学生,这个项目能让你把零散的知识点串起来。如果你已经工作,想找个练手项目熟悉某个框架,通讯录管理系统也能当脚手架用。

2.2 技术选型:Swing + JDBC 还是 JavaFX + 文件存储

技术选型直接决定你后面写代码的难度和老师验收时的印象分。常见的组合有两种:

第一种是 Swing + JDBC + MySQL。Swing 是 Java 自带的桌面 GUI 库,不需要额外配置,资料多,遇到问题好搜。JDBC 连 MySQL 是课程设计里最标准的数据库方案,老师一看就知道你学过数据库。缺点是 Swing 界面比较丑,需要自己调布局,MySQL 还要装环境、建库建表。

第二种是 JavaFX + 文件存储(JSON 或 CSV)。JavaFX 界面更现代,Scene Builder 拖拽布局效率高。文件存储省去了装数据库的麻烦,用 Jackson 或 Gson 序列化联系人列表就行。缺点是 JavaFX 在部分 JDK 版本里需要单独引入模块,文件存储的并发和查询性能不如数据库。

我一般会推荐第一种组合,因为课程设计的验收场景通常是老师在你电脑上跑一遍,看功能是否完整、代码是否规范。Swing + JDBC 的代码结构更传统,分层清晰,老师一眼就能看出你用了 MVC 模式。而且数据库操作能体现你对 SQL 的掌握,加分项更多。

下面是一个典型的联系人表结构设计:

CREATE TABLE contact ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, phone VARCHAR(20) NOT NULL, email VARCHAR(100), group_name VARCHAR(30), remark VARCHAR(200), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );

字段说明:id是自增主键,方便后续修改和删除时定位记录;name和phone设为非空,因为通讯录的核心就是这两项;group_name用来分类,比如“家人”“同事”“客户”;create_time记录创建时间,方便按时间排序。如果老师要求支持多个电话,可以再加一张contact_phone表做一对多关联,但课程设计一般不需要这么复杂。

2.3 分层架构:别把代码全塞进一个类里

很多同学写课程设计时习惯把所有代码写在一个Main类里,按钮事件里直接写 SQL,界面和业务逻辑混在一起。这样写当时爽,后面改一个字段要翻几百行代码,老师一看就皱眉头。

合理的做法是分层。常见的是三层结构:表现层(UI)、业务逻辑层(Service)、数据访问层(DAO)。表现层只负责展示和接收用户输入,业务逻辑层处理校验和流程控制,数据访问层只管和数据库打交道。实体类(Model)单独放一个包,贯穿三层。

这样分的好处是:改界面不影响业务逻辑,换数据库不影响界面。老师问你“如果换成文件存储怎么办”,你可以说“只需要改 DAO 层的实现,Service 和 UI 不用动”。这就是加分项。

3. 目录结构与核心模块拆解:从入口到数据落库

3.1 项目目录结构

一个标准的 Maven 项目结构如下:

contact-manager/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/contact/ │ │ │ ├── Main.java // 程序入口 │ │ │ ├── model/ │ │ │ │ └── Contact.java // 联系人实体 │ │ │ ├── dao/ │ │ │ │ ├── ContactDao.java // DAO 接口 │ │ │ │ └── ContactDaoImpl.java // JDBC 实现 │ │ │ ├── service/ │ │ │ │ └── ContactService.java // 业务逻辑 │ │ │ ├── ui/ │ │ │ │ ├── MainFrame.java // 主窗口 │ │ │ │ └── ContactDialog.java // 新增/编辑弹窗 │ │ │ └── util/ │ │ │ └── DBUtil.java // 数据库连接工具 │ │ └── resources/ │ │ └── db.properties // 数据库配置 └── pom.xml

这个结构里,model包放实体类,dao包放数据访问代码,service包放业务逻辑,ui包放界面,util包放工具类。每个包职责单一,找代码的时候直接按包名定位。

3.2 实体类与数据库连接

先看实体类Contact.java:

package com.example.contact.model; public class Contact { private int id; private String name; private String phone; private String email; private String groupName; private String remark; // 无参构造,JDBC 反射需要 public Contact() {} // 新增时用的构造,不需要 id public Contact(String name, String phone, String email, String groupName, String remark) { this.name = name; this.phone = phone; this.email = email; this.groupName = groupName; this.remark = remark; } // getter 和 setter 省略,实际项目里要写全 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } // ... 其他 getter/setter }

实体类的作用是承载数据。注意这里保留了一个无参构造,因为后面用 JDBC 从ResultSet映射对象时,通常会先new一个空对象再逐个set。如果你用 MyBatis 或 Hibernate,无参构造也是必须的。

再看数据库连接工具DBUtil.java:

package com.example.contact.util; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.util.Properties; public class DBUtil { private static String url; private static String username; private static String password; // 静态块在类加载时读取配置文件 static { try (InputStream in = DBUtil.class.getClassLoader() .getResourceAsStream("db.properties")) { Properties props = new Properties(); props.load(in); url = props.getProperty("db.url"); username = props.getProperty("db.username"); password = props.getProperty("db.password"); Class.forName(props.getProperty("db.driver")); } catch (Exception e) { throw new RuntimeException("数据库配置加载失败", e); } } public static Connection getConnection() throws Exception { return DriverManager.getConnection(url, username, password); } }

这段代码的关键点:配置文件db.properties放在resources目录下,打包后会自动进入 classpath。静态块只执行一次,避免每次获取连接都读文件。Class.forName加载驱动,MySQL 8 以上用com.mysql.cj.jdbc.Driver。连接用完记得关闭,后面 DAO 里会用 try-with-resources 处理。

db.properties内容示例:

db.driver=com.mysql.cj.jdbc.Driver db.url=jdbc:mysql://localhost:3306/contact_db?useSSL=false&serverTimezone=UTC db.username=root db.password=123456

3.3 DAO 层:增删改查的 JDBC 实现

DAO 层是课程设计的核心,老师重点看的就是这里。先定义接口:

package com.example.contact.dao; import com.example.contact.model.Contact; import java.util.List; public interface ContactDao { void insert(Contact contact) throws Exception; void update(Contact contact) throws Exception; void delete(int id) throws Exception; Contact findById(int id) throws Exception; List<Contact> findAll() throws Exception; List<Contact> searchByName(String keyword) throws Exception; }

接口定义好之后,实现类ContactDaoImpl.java里写具体 SQL:

package com.example.contact.dao; import com.example.contact.model.Contact; import com.example.contact.util.DBUtil; import java.sql.*; import java.util.ArrayList; import java.util.List; public class ContactDaoImpl implements ContactDao { @Override public void insert(Contact contact) throws Exception { String sql = "INSERT INTO contact(name, phone, email, group_name, remark) " + "VALUES(?, ?, ?, ?, ?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, contact.getName()); ps.setString(2, contact.getPhone()); ps.setString(3, contact.getEmail()); ps.setString(4, contact.getGroupName()); ps.setString(5, contact.getRemark()); ps.executeUpdate(); } } @Override public List<Contact> findAll() throws Exception { String sql = "SELECT * FROM contact ORDER BY create_time DESC"; List<Contact> list = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { list.add(mapRow(rs)); } } return list; } @Override public List<Contact> searchByName(String keyword) throws Exception { String sql = "SELECT * FROM contact WHERE name LIKE ? OR phone LIKE ?"; List<Contact> list = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { String pattern = "%" + keyword + "%"; ps.setString(1, pattern); ps.setString(2, pattern); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { list.add(mapRow(rs)); } } } return list; } // 把 ResultSet 当前行映射成 Contact 对象 private Contact mapRow(ResultSet rs) throws SQLException { Contact c = new Contact(); c.setId(rs.getInt("id")); c.setName(rs.getString("name")); c.setPhone(rs.getString("phone")); c.setEmail(rs.getString("email")); c.setGroupName(rs.getString("group_name")); c.setRemark(rs.getString("remark")); return c; } }

这里有几个关键点。第一,用PreparedStatement而不是Statement,防止 SQL 注入,这是课程设计的必查项。第二,try-with-resources自动关闭连接、语句和结果集,避免资源泄漏。第三,mapRow方法把结果集映射抽出来,多个查询方法复用,减少重复代码。第四,搜索用LIKE加%通配符,支持模糊匹配姓名和电话。

update和delete方法结构类似,只是 SQL 不同,这里不再重复贴。注意update的 SQL 要带WHERE id = ?,delete同理。

3.4 Service 层:校验与业务逻辑

Service 层不是简单转发 DAO 调用,它要处理业务规则。比如新增联系人时,姓名和电话不能为空,电话格式要校验,重复电话要提示。这些逻辑放在 Service 里,UI 层只管调用。

package com.example.contact.service; import com.example.contact.dao.ContactDao; import com.example.contact.dao.ContactDaoImpl; import com.example.contact.model.Contact; import java.util.List; public class ContactService { private ContactDao dao = new ContactDaoImpl(); public void addContact(Contact contact) throws Exception { // 校验必填字段 if (contact.getName() == null || contact.getName().trim().isEmpty()) { throw new IllegalArgumentException("姓名不能为空"); } if (contact.getPhone() == null || contact.getPhone().trim().isEmpty()) { throw new IllegalArgumentException("电话不能为空"); } // 校验电话格式,简单用正则 if (!contact.getPhone().matches("\\d{7,15}")) { throw new IllegalArgumentException("电话格式不正确"); } dao.insert(contact); } public List<Contact> listAll() throws Exception { return dao.findAll(); } public List<Contact> search(String keyword) throws Exception { if (keyword == null || keyword.trim().isEmpty()) { return dao.findAll(); } return dao.searchByName(keyword.trim()); } public void deleteContact(int id) throws Exception { dao.delete(id); } }

Service 层的校验逻辑是老师常问的点。为什么不在 UI 层校验?因为 UI 层可能被替换,比如从 Swing 换成 Web 界面,校验逻辑不应该跟着重写。放在 Service 层,任何调用方都能复用同一套规则。

3.5 UI 层:Swing 主窗口与表格刷新

UI 层用 Swing 实现,主窗口包含一个JTable展示联系人列表,顶部有搜索框和按钮,底部有新增、编辑、删除按钮。核心是表格的数据模型和刷新逻辑。

package com.example.contact.ui; import com.example.contact.model.Contact; import com.example.contact.service.ContactService; import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.awt.*; import java.util.List; public class MainFrame extends JFrame { private ContactService service = new ContactService(); private DefaultTableModel tableModel; private JTable table; private JTextField searchField; public MainFrame() { setTitle("通讯录管理系统"); setSize(800, 500); setDefaultCloseOperation(EXIT_ON_CLOSE); setLocationRelativeTo(null); initUI(); refreshTable(); } private void initUI() { // 表格列名 String[] columns = {"ID", "姓名", "电话", "邮箱", "分组", "备注"}; tableModel = new DefaultTableModel(columns, 0) { @Override public boolean isCellEditable(int row, int column) { return false; // 表格只读,编辑走弹窗 } }; table = new JTable(tableModel); // 顶部搜索栏 JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); searchField = new JTextField(15); JButton searchBtn = new JButton("搜索"); searchBtn.addActionListener(e -> doSearch()); topPanel.add(new JLabel("姓名/电话:")); topPanel.add(searchField); topPanel.add(searchBtn); // 底部操作按钮 JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JButton addBtn = new JButton("新增"); JButton editBtn = new JButton("编辑"); JButton deleteBtn = new JButton("删除"); addBtn.addActionListener(e -> openDialog(null)); editBtn.addActionListener(e -> { int row = table.getSelectedRow(); if (row < 0) { JOptionPane.showMessageDialog(this, "请先选中一行"); return; } int id = (int) tableModel.getValueAt(row, 0); openDialog(id); }); deleteBtn.addActionListener(e -> doDelete()); bottomPanel.add(addBtn); bottomPanel.add(editBtn); bottomPanel.add(deleteBtn); add(topPanel, BorderLayout.NORTH); add(new JScrollPane(table), BorderLayout.CENTER); add(bottomPanel, BorderLayout.SOUTH); } private void refreshTable() { try { List<Contact> list = service.listAll(); tableModel.setRowCount(0); // 清空旧数据 for (Contact c : list) { tableModel.addRow(new Object[]{ c.getId(), c.getName(), c.getPhone(), c.getEmail(), c.getGroupName(), c.getRemark() }); } } catch (Exception ex) { JOptionPane.showMessageDialog(this, "加载失败:" + ex.getMessage()); } } private void doSearch() { try { List<Contact> list = service.search(searchField.getText()); tableModel.setRowCount(0); for (Contact c : list) { tableModel.addRow(new Object[]{ c.getId(), c.getName(), c.getPhone(), c.getEmail(), c.getGroupName(), c.getRemark() }); } } catch (Exception ex) { JOptionPane.showMessageDialog(this, "搜索失败:" + ex.getMessage()); } } private void doDelete() { int row = table.getSelectedRow(); if (row < 0) { JOptionPane.showMessageDialog(this, "请先选中一行"); return; } int id = (int) tableModel.getValueAt(row, 0); int confirm = JOptionPane.showConfirmDialog(this, "确定删除?"); if (confirm == JOptionPane.YES_OPTION) { try { service.deleteContact(id); refreshTable(); } catch (Exception ex) { JOptionPane.showMessageDialog(this, "删除失败:" + ex.getMessage()); } } } private void openDialog(Integer id) { // 打开新增/编辑弹窗,传 null 表示新增 ContactDialog dialog = new ContactDialog(this, id); dialog.setVisible(true); refreshTable(); // 弹窗关闭后刷新表格 } public static void main(String[] args) { SwingUtilities.invokeLater(() -> new MainFrame().setVisible(true)); } }

这段代码里,DefaultTableModel的isCellEditable返回false,让表格只读,避免用户直接在表格里改数据导致状态不一致。refreshTable每次先setRowCount(0)清空再重新填充,这是 Swing 表格刷新的标准做法。搜索和删除操作后都调用刷新,保证界面和数据同步。

ContactDialog是新增/编辑弹窗,里面放JTextField接收输入,点保存时调用service.addContact或service.updateContact。这里不再展开,结构类似。

4. 避坑与常见问题:课程设计里最容易翻车的地方

4.1 数据库连不上,报 ClassNotFoundException

现象:程序启动时报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver。

原因:MySQL 驱动 jar 包没有加到项目依赖里。如果你用 Maven,pom.xml里没写mysql-connector-java依赖;如果是普通项目,WEB-INF/lib或 classpath 里没有驱动 jar。

解决:Maven 项目在pom.xml加依赖:

<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>

普通项目把 jar 包放到lib目录,然后在 IDE 里右键添加到 classpath。注意 MySQL 8 以上驱动类名是com.mysql.cj.jdbc.Driver,不是旧的com.mysql.jdbc.Driver。

4.2 中文乱码,姓名存进去变成问号

现象:联系人姓名是中文,存到数据库后变成???,或者界面显示乱码。

原因:数据库字符集不是utf8mb4,或者 JDBC URL 没指定编码。

解决:建库时指定字符集:

CREATE DATABASE contact_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

JDBC URL 加上useUnicode=true&characterEncoding=utf8:

db.url=jdbc:mysql://localhost:3306/contact_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC

如果已经建了库,用ALTER DATABASE contact_db CHARACTER SET utf8mb4;修改。

4.3 表格数据不刷新,改了数据库界面没变

现象:新增联系人后,数据库里有了,但表格还是旧数据。

原因:refreshTable没有在新增/编辑/删除后调用,或者tableModel没有清空旧数据直接追加。

解决:每次数据变更后调用refreshTable(),并且刷新时先tableModel.setRowCount(0)清空。不要用tableModel.fireTableDataChanged()单独触发,因为数据源没变的话界面不会更新。

4.4 关闭连接报错,或者连接数过多

现象:程序运行一段时间后报Too many connections,或者关闭窗口时抛异常。

原因:DAO 里获取的Connection没有关闭,或者关闭顺序不对。

解决:统一用try-with-resources,把Connection、PreparedStatement、ResultSet都放在 try 的括号里,JVM 会自动按相反顺序关闭。不要手动在finally里写close(),容易漏或者顺序错。

4.5 搜索没结果,但数据库里明明有数据

现象:输入姓名搜索,返回空列表。

原因:SQL 里用了=而不是LIKE,或者%拼错了位置。

解决:模糊搜索用LIKE ?,参数拼成"%" + keyword + "%"。如果搜电话,注意电话字段是字符串类型,不要当数字处理。另外检查keyword有没有trim(),用户输入前后空格会导致匹配失败。

5. 二次开发与改造:从通讯录到通用管理系统

5.1 换数据库:从 MySQL 到 SQLite

课程设计验收时,老师可能不想装 MySQL。把数据库换成 SQLite 是最省事的改造。SQLite 不需要服务端,一个文件就是数据库。

改造步骤:第一,pom.xml把 MySQL 驱动换成sqlite-jdbc。第二,db.properties改驱动和 URL:

db.driver=org.sqlite.JDBC db.url=jdbc:sqlite:contact.db db.username= db.password=

第三,建表 SQL 里AUTO_INCREMENT改成AUTOINCREMENT,DATETIME DEFAULT CURRENT_TIMESTAMP改成DEFAULT (datetime('now'))。DAO 层代码几乎不用动,因为 JDBC API 是统一的。这就是分层的好处——换存储只改配置和少量 SQL。

5.2 加功能:分组筛选与导入导出

通讯录管理系统最容易扩展的方向是分组和导入导出。分组筛选只需要在 Service 层加一个listByGroup(String group)方法,DAO 里加WHERE group_name = ?查询,UI 顶部加一个下拉框选择分组。

导入导出用 CSV 格式最方便。导出时遍历List<Contact>,每行拼成name,phone,email,group,remark写入文件。导入时用BufferedReader逐行读取,按逗号拆分,调用service.addContact。注意 CSV 里如果字段本身含逗号,要用双引号包裹,简单做法是导出前把逗号替换成中文逗号。

// 导出 CSV 的核心逻辑 public void exportToCsv(List<Contact> list, String filePath) throws Exception { try (BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(filePath), "UTF-8"))) { writer.write("姓名,电话,邮箱,分组,备注\n"); for (Contact c : list) { writer.write(String.join(",", c.getName(), c.getPhone(), c.getEmail(), c.getGroupName(), c.getRemark()) + "\n"); } } }

5.3 改造验证:怎么确认改完没坏

每次改造后,按这个顺序验证:先跑findAll看列表能不能加载,再跑insert看新增后表格是否刷新,然后跑search看模糊匹配是否正常,最后跑delete看删除后数据是否消失。如果换了数据库,重点验证建表语句和自增主键是否兼容。如果加了导入功能,用一个包含中文和特殊字符的 CSV 测试,确认编码没问题。

我一般会在改造前把原始版本跑一遍,记下每个功能的预期结果,改完后逐项对比。这样即使出了问题,也能快速定位是哪一步改坏了。

5.4 一个实用技巧:用接口隔离变化

如果你打算把这个项目写进简历,或者作为毕业设计的起点,建议把 DAO 层设计成可替换的。定义一个ContactRepository接口,然后分别写JdbcContactRepository和FileContactRepository两个实现。Service 层依赖接口而不是具体实现,通过工厂或依赖注入决定用哪个。

public interface ContactRepository { void save(Contact contact) throws Exception; List<Contact> findAll() throws Exception; // ... 其他方法 } public class RepositoryFactory { public static ContactRepository create(String type) { if ("file".equals(type)) { return new FileContactRepository(); } return new JdbcContactRepository(); } }

这样老师问你“如果不用数据库怎么办”,你可以直接切换实现,代码不用大改。这个技巧在课程设计答辩时很加分,因为它体现了你对面向接口编程的理解。

从那以后我每次做课程设计,都会先把接口定好再写实现,哪怕功能再简单也强制走一遍。这样后面加功能或者换方案的时候,不会因为牵一发动全身而翻车。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 2:26:17

C/S架构从零到实战:通信协议、长连接与性能排查指南

1. 内容整体设计与思路拆解1.1 为什么2024年了还在聊C/S架构看到“C/S架构0”这个标题&#xff0c;我第一反应是——又有人在技术群里把C/S和B/S吵起来了。其实做了十几年软件&#xff0c;C/S架构从未退出过战场&#xff0c;只是很多人把它当成了“过时”的代名词。C/S架构&…

作者头像 李华
网站建设 2026/10/20 2:26:17

WinForms多选下拉框:含CheckBox的ComboBox自定义实现

简介&#xff1a;这是一份面向Windows桌面应用开发者的自定义UI控件实现资源&#xff0c;聚焦于解决多选下拉列表交互难题——将复选框&#xff08;Checkbox&#xff09;功能深度集成至组合框&#xff08;Combobox&#xff09;中&#xff0c;适用于偏好设置、条件过滤、权限配置…

作者头像 李华
网站建设 2026/10/20 2:25:33

路由器DHCP与Telnet配置实战

一、实验目的&#xff1a;1、在路由器上配置DHCP服务&#xff0c;使4太PC自动获取IP地址、子网掩码、网关2、在路由器配置Telnet远程登陆功能&#xff0c;实现其他设备远程管理路由器3、验证PC自动获取地址、远程登陆路由器功能是否正常二、实验拓扑拓扑简述&#xff1a;路由器…

作者头像 李华
网站建设 2026/10/20 2:22:40

XX银行-数据库恢复演练失败

问题现象恢复演练失败&#xff0c;数据库未拉起数据库文件分析检查发现一个文件路径不对处理后&#xff0c;可以开库处理/优化建议针对现有情况&#xff0c;建议如下&#xff1a; 恢复前&#xff0c;mount状态先看一遍所有文件的路径是否正常转化set lin 200 pages 999 col nam…

作者头像 李华
网站建设 2026/10/20 2:22:31

Linu虚拟机系统安装详细教程(不废话版)

安装VMware workstation pro 软件 下载地址Fusion and Workstation | VMware 点击下载&#xff0c;按照正常的下载流程就行 创建虚拟机 打开VMware workstation pro软件 创建新的虚拟机 选择安装兼容性 稍后安装操作系统 选择安装类型 设置存储路径及名称最好不要安装在c盘&…

作者头像 李华