尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Java Web名片管理系统源码解析:从部署到改造

发布时间:2026/9/16 20:16:12

资讯中心
01
ARTICLE

Java Web名片管理系统源码解析:从部署到改造

Java Web名片管理系统源码解析:从部署到改造
简介这是一套基于Java Web技术栈开发的名片管理系统完整源码面向计算机、数学、电子信息等专业的学生尤其适合作为课程设计、期末大作业或毕业设计的参考项目。系统涵盖用户登录、名片增删改查、回收站管理等典型业务模块代码结构包含数据访问层、Servlet控制器、JSP视图层及XML配置文件能够帮助学习者理解Java Web分层开发思路。压缩包共一百四十五个文件主要包含五十个class编译类、二十六个java源文件、二十个xml配置文件、十八个png图片、十个jpg图片、六个jsp页面、六个css样式、五个jar依赖库等资源整体约八十九点九八兆字节。源文件、页面与样式图片分类清晰便于直接部署调试或对照学习。目前已有一定学习热度约一百九十五人浏览使用适合需要参考完整项目实现或在此基础上二次开发的学习者。1. 名片管理系统到底在练什么名片管理系统大概是 Java Web 入门阶段出现频率最高的练手项目。它没有分布式组件和复杂算法却把 Web 开发最核心的三件事都占了用户登录与权限控制、数据库的增删改查、页面与后端之间的参数流转。从网上下载一份基于 java web 的名片管理系统源码.zip解压后看到的通常是一个 Eclipse 或 IDEA 工程里面是 JSP 页面、Servlet 类、JDBC 工具类和一个 SQL 初始化脚本。对刚学完 Java SE 想进入 Web 开发的初学者对期末需要交课程设计的学生以及想快速过一遍 Java Web 全链路准备面试的在职开发这套源码都值得拆开读一遍。下面以最常见的 JSP Servlet MySQL Tomcat 组合为例把解压源码、搭好环境、跑通项目、动手改造这四段路走一遍。2. 名片表结构与 Java Web 分层设计先定边界再写代码2.1 三层架构与包结构划分拿到源码第一步看包结构比看任何一个类的实现都重要。一个结构清晰的 Java Web 工程src 下必然按三层架构组织controller 放 Servletservice 放业务逻辑dao 放数据访问model 放实体filter 放过滤器util 放工具类和分页封装。典型包结构如下src/main/java ├── com.example.contact │ ├── controller │ │ ├── LoginServlet.java │ │ ├── LogoutServlet.java │ │ ├── ContactListServlet.java │ │ ├── ContactAddServlet.java │ │ ├── ContactEditServlet.java │ │ └── ContactDeleteServlet.java │ ├── service │ │ └── ContactService.java │ ├── dao │ │ ├── ContactDao.java │ │ └── UserDao.java │ ├── model │ │ ├── Contact.java │ │ └── User.java │ ├── filter │ │ └── LoginFilter.java │ └── util │ ├── DBUtil.java │ └── PageBean.java这个结构的核心是依赖方向controller 调 serviceservice 调 daomodel 在各层间传递。很多早期源码把 JDBC 直接写在 Servlet 里类少了好几个但后续加批量导入要改 Servlet加Excel 导出还要改 Servlet一个类几百行维护成本不断叠加。分层不是为了让类变多而是把参数接收、业务规则、SQL 操作切成三段改动只落在该落的那一层。2.1.1 Servlet 路由与请求方法区分Servlet 3.0 之后用 WebServlet 注解注册路由不必在 web.xml 中逐个声明。常见做法是同一个路径同时处理 GET 和 POST编辑页的加载与提交分别对应 doGet 与 doPostWebServlet(/contact/edit) public class ContactEditServlet extends HttpServlet { private ContactService contactService new ContactService(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String id req.getParameter(id); Contact contact contactService.getById(Integer.parseInt(id)); req.setAttribute(contact, contact); req.getRequestDispatcher(/WEB-INF/jsp/contact_form.jsp).forward(req, resp); } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); Contact contact new Contact(); contact.setId(Integer.parseInt(req.getParameter(id))); contact.setName(req.getParameter(name)); contact.setCompany(req.getParameter(company)); contactService.update(contact); resp.sendRedirect(req.getContextPath() /contact/list); } }doGet 负责进编辑页时把已有记录查出并回显doPost 负责接收表单参数并更新数据库。一个值得记住的细节是更新成功后用 sendRedirect 重定向而不是 forward 转发。重定向发出的是第二次独立 GET 请求刷新地址栏只会重复拉列表不会再把表单内容 POST 一遍从机制上避免了刷新页面导致重复提交。2.2 名片表字段设计别只设计 name 和 phone名片管理系统的核心表是用户表和联系人表。用户表管登录联系人表是业务主表。字段设计直接决定后续能扩展的功能一套比较均衡的结构如下CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_contact ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, name VARCHAR(50) NOT NULL, company VARCHAR(100), title VARCHAR(50), phone VARCHAR(20), email VARCHAR(100), remark VARCHAR(255), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_contact_user FOREIGN KEY (user_id) REFERENCES t_user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段类型说明idINT自增主键user_idINT NOT NULL所属用户实现多用户数据隔离nameVARCHAR(50) NOT NULL姓名必须校验非空companyVARCHAR(100)公司名称可空phoneVARCHAR(20)手机号用 VARCHAR 而不是 BIGINTemailVARCHAR(100)邮箱提交时做格式校验created_atDATETIME创建时间由数据库默认值填充updated_atDATETIME更新时间配合 ON UPDATE 自动维护字段设计有两个容易被忽略的点。第一手机号用 VARCHAR 存储因为号码可能有 86 前缀和分机号等字符BIGINT 会把这些信息丢掉超过 19 位还会溢出。第二联系人表加上 user_id 外键并建索引即使源码头上去只有单用户登录也为多用户改造留好了结构。建表时顺手解决这些几乎零成本的问题后续就不用迁移数据。2.3 数据库连接池与 DAO 层的 JDBC 封装连接池是 Java Web 项目绕不过去的配置。每个请求都手动创建 Connection 再关闭并发稍高就会出现大量 TIME_WAITMySQL 线程频繁创建销毁响应时间明显变长。Druid 是常见选择一份 druid.properties 配置如下driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/contact_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai usernameroot password123456 initialSize5 maxActive20 maxWait10000对应的 DBUtil 类一般这样写public class DBUtil { private static final DruidDataSource dataSource new DruidDataSource(); static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(druid.properties)) { Properties props new Properties(); props.load(in); dataSource.setDriverClassName(props.getProperty(driverClassName)); dataSource.setUrl(props.getProperty(url)); dataSource.setUsername(props.getProperty(username)); dataSource.setPassword(props.getProperty(password)); } catch (IOException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }URL 里三个参数要逐一确认useUnicodetrue 配合 characterEncodingutf8 解决中文乱码serverTimezoneAsia/Shanghai 解决 MySQL 8.x 驱动对时区的强制要求。源码里如果还是 com.mysql.jdbc.Driver需要改成 com.mysql.cj.jdbc.Driver旧驱动类在 Connector/J 8.x 中已被移除。参数含义分别是initialSize 为初始化连接数maxActive 为最大活跃连接数maxWait 为等待超时毫秒数。本地开发用默认值线上再按并发调整一般 maxActive 在 20 到 50 之间足够小业务。DAO 层的 JDBC 封装核心是 PreparedStatement。固定结构是获取连接预编译 SQL设置参数执行映射结果集释放资源。把这段逻辑收敛到 BaseDao 里业务方法只需要传 SQL 和参数列表职责就清晰了。3. 登录鉴权、名片分页与表单回显CRUD 的三块硬骨头3.1 基于 Session 的登录态与过滤器拦截登录成功后把当前用户放入 Session后续请求用 Filter 检查 Session 是否存在这是 JSP Servlet 体系中最经典也最可靠的鉴权做法。WebFilter(/contact/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; Object loginUser req.getSession().getAttribute(loginUser); if (loginUser null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }过滤器拦截所有 /contact/ 开头的业务请求取不到 loginUser 就重定向到登录页取到就放行。这里藏着一个经典坑拦截路径写成 /* 会把登录页、CSS、JS、图片全部拦下来用户未登录时访问 login.jsp 又被重定向到 login.jsp形成死循环跳转。正确做法是过滤器只匹配业务路径前缀静态资源单独放行。登录接口的完整逻辑通常长这样WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); User user userService.login(username, password); if (user null) { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } else { req.getSession().setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /contact/list); } } }失败提示统一用用户名或密码错误不区分用户不存在和密码错误这是防账号枚举的标准做法。登录失败用 forward 让 JSP 读取 error 变量登录成功用 sendRedirect 让浏览器从列表页重新开始刷新时不会重复提交登录请求。3.2 分页查询 SQL 与 PageBean 封装名片列表分页是整套系统中查询压力最大的功能。MySQL 分页语法是 LIMIT offset, count但页码从 1 开始所以 offset 必须换算成 (currentPage - 1) * pageSizeSELECT id, name, company, phone, email FROM t_contact WHERE user_id ? ORDER BY id DESC LIMIT ?, ?;参数赋值顺序要严格遵守 SQL 中占位符顺序先放 user_id再放 offset最后放 pageSize。实际开发中经常在这三个参数顺序上对不上号建议打印参数列表核对。分页查询 DAO 写两个方法一个负责 count 总数一个负责取当前页数据。PageBean 封装示意public class PageBeanT { private int currentPage; private int pageSize; private int totalCount; private int totalPage; private ListT list; public PageBean(int currentPage, int pageSize) { this.currentPage currentPage 1 ? 1 : currentPage; this.pageSize pageSize; } public int getOffset() { return (currentPage - 1) * pageSize; } public void setTotalCount(int totalCount) { this.totalCount totalCount; this.totalPage totalCount % pageSize 0 ? totalCount / pageSize : totalCount / pageSize 1; } }totalPage 的计算收在 setTotalCount 里语义集中且不会在页面里重复算。这里有一个高频错误只写了 list 查询没写 count 查询前端翻页组件的共 N 页数值永远不对。count 和 list 必须使用完全一致的 where 条件搜索关键词时尤其要当心否则过滤后总页数与列表内容对不上。前端传 currentPage 时不要直接 Integer.parseInt先捕获 NumberFormatException 回退到第 1 页否则带脏参数访问会直接 500。联动搜索时一个可复用的拼接方式StringBuilder sql new StringBuilder(SELECT * FROM t_contact WHERE user_id ?); ListObject params new ArrayList(); params.add(userId); String keyword req.getParameter(keyword); if (keyword ! null !keyword.isBlank()) { sql.append( AND (name LIKE ? OR company LIKE ? OR phone LIKE ?)); String likeValue % keyword.trim() %; params.add(likeValue); params.add(likeValue); params.add(likeValue); } sql.append( ORDER BY id DESC LIMIT ?, ?); params.add(pageBean.getOffset()); params.add(pageBean.getPageSize()); ListContact list contactDao.findByCondition(sql.toString(), params);LIKE 匹配的百分号是在 Java 侧拼到参数值里而不是直接拼进 SQL 字符串。二者最终执行结果一样但参数化写法保持 SQL 结构固定名字里带单引号、百分号时不会被当成语法内容从根上避开注入问题。params 的添加顺序必须与 SQL 中 ? 的出现顺序完全一致这是动态拼接最容易出错的位置。3.3 添加与编辑共用表单的数据回显新增和编辑页面差别极小通常共用 contact_form.jsp用隐藏字段 id 区分状态。回显数据后表单用 EL 表达式判断对象是否存在% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % form action${empty contact ? add : edit} methodpost input typehidden nameid value${contact.id} label姓名/label input typetext namename value${contact.name} label公司/label input typetext namecompany value${contact.company} label电话/label input typetext namephone value${contact.phone} button typesubmit保存/button /form${empty contact} 为真时 action 指向 add为假时指向 edit。添加请求不读隐藏 id编辑请求用这个 id 定位记录。表单回显有一个常见误区校验失败返回页面时数据应该回显用户刚提交的值而不是数据库旧值。此时需从 request.getParameter 取值填回 input而不是重新按 id 查库。删除操作也留个心眼链接直删实现最快但容易被爬虫预加载误触发课程设计阶段能意识到这个风险就够了更稳的做法是 POST 提交删除表单。4. 名片系统源码部署从源码包到启动成功4.1 环境版本与导入步骤解压源码包后先看目录结构再选择导入方式。带 .idea 的 IntelliJ 工程直接 Open带 .classpath 和 .project 的 Eclipse 工程用 Import Existing Projects若只有 src 和 webapp 目录就需要新建 Web 工程后把两个目录拷进去。常见的版本组合组件推荐版本说明JDK1.8 或 11多数课程设计源码基于 JDK 8Tomcat8.5 / 9.0对应 Servlet 3.1 / 4.0MySQL5.7 / 8.08.0 需换驱动并补时区参数Connector/J8.0.x低版本驱动无法连接 MySQL 8Maven3.6仅在源码带 pom.xml 时需要导入后的顺序一般是修改数据库账号密码创建数据库并执行 SQL 脚本在 IDE 里配置 Tomcat 的 Deployment 工件启动后访问 http://localhost:8080/context_path/login.jsp。context_path 必须与代码中 getContextPath() 的返回值一致不匹配时登录成功后重定向 404。4.2 运行时高频异常排查报错信息原因处理方式Port 8080 was already in use端口被占用换端口或结束占用进程ClassNotFoundException: com.mysql.jdbc.Driver驱动类名不对改 com.mysql.cj.jdbc.Driver确认 jar 在 lib 下The server time zone value ...MySQL 8 时区URL 加 serverTimezoneAsia/Shanghaijava.sql.SQLException: Access denied账号密码错误核对 druid.propertiesHTTP 404工件未部署或路径不对重建 artifact 并核对 context_path页面中文乱码编码未统一三个位置全部 UTF-8中文乱码需要单独说清楚。GET 请求在 Tomcat 8 默认按 UTF-8 解码一般不用处理POST 请求需要 req.setCharacterEncoding(UTF-8)响应输出需要 resp.setContentType(text/html;charsetUTF-8)JSP 页面上的 pageEncoding 也统一 UTF-8。三个位置一致后姓名、公司这类中文才显示正常。运行这种工程最常见的卡点是 MySQL 8 的驱动兼容。5.x 的 Connector/J 连接 8.0 会报 SSL 或 caching_sha2_password 错误处理方式直接换 8.0.x 驱动同时把 URL 的参数补全。若 SQL 脚本在旧库里建过同名表先 DROP 再重新执行保证表结构是脚本里定义的最新版本。提示改完 druid.properties 后务必重新构建 Web 工件再重启 Tomcat静态文件热部署不一定生效配置文件常驻类路径。5. 让名片系统源码经得起推敲四个低成本高收益的改造点5.1 密码存储从 MD5 升级为 BCrypt课程设计的密码大多直接 MD5 存储教学场景能自圆其说但写入简历的系统安全就经不起追问。MD5 无盐且极快彩虹表几乎秒破。最经济的替换是 jBCrypt 这个无依赖小库改动集中在 UserDao// 注册时生成哈希 String hashed BCrypt.hashpw(rawPassword, BCrypt.gensalt()); // 登录时校验 BCrypt.checkpw(rawPassword, user.getPassword());BCrypt 自动加盐且每次哈希不同计算耗时被刻意放大暴力破解成本显著提高。动手改时注意把数据库 password 字段长度扩到 60 以上VARCHAR(100) 最稳妥。5.2 统一返回结构与参数校验Servlet 里最容易出现散落的成功/失败分流后续要做成接口时改造成本不小。先落一个最简 Result 类code、message、data 三个字段配 success() 和 failure() 两个静态工厂。参数校验放在 controller 第一道拦截name 为空、phone 超长这类问题直接返回固定提示不在 JSP 里做唯一判断。5.3 列表导出前先拆查询方法答辩时很容易被问能不能导出 Excel而源码里通常没有这个功能。先别急着引 Apache POI把列表查询拆成按条件查全部和按条件分页两个方法导出复用前者。这个重构只在 DAO 层做小改动不碰页面逻辑收益却直接落在外显功能上。5.4 补齐 README 和可重复执行的建库脚本源码包没有 README 的情况并不少见。花半小时补上环境要求、建库语句、默认账号、部署步骤答辩展示和后续维护都省心。SQL 脚本开头加 DROP TABLE IF EXISTS中间用显式事务包裹保证重复执行不报错、数据不残留。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。