资讯动态

JavaWeb金融借贷系统源码解析:Servlet+JDBC全流程实战

发布时间:2026/9/16 16:30:39 来源:尧图企业网站定制
简介这套基于JavaWeb实现的金融借贷系统即P2P金融管理系统/小额贷款平台主要面向计算机相关专业毕业设计学生与Java项目实战开发者前台提供融资产品浏览、贷款申请、每日新闻查询后台涵盖贷款申请审核、融资产品与周期管理、新闻发布、企业管理等常用功能整体业务链完整。资源包共15个文件包含项目源码、MySQL数据库脚本、项目文档PDF/Markdown、运行截图、软件工具说明等压缩包大小约14.67MB其中源码与数据库脚本可直接用于本地部署调试。目前已有2535人学习下载适合需要快速搭建可演示金融借贷系统的人群。项目基于Servlet、JDBC、Bootstrap、jQuery、Ajax等主流技术实现界面简洁、操作流畅附带文档和截图便于二次开发与论文撰写能有效支撑毕业设计答辩与项目实训需求。1. 一个JavaWeb金融借贷系统为什么值得把源码拆开看一遍每年毕业设计选题目录里“基于JavaWeb的金融借贷系统”都会反复出现。这类系统有清晰的双端闭环——前台查融资产品、申请贷款、看每日新闻后台做贷款审核、产品维护和信息发布一套代码覆盖了Servlet路由、JDBC操作、Session鉴权、文件上传这些JavaWeb核心知识点。相比用Spring Boot注解一键封装Servlet JDBC反而更直接地回答了答辩时必被追问的两个问题URL如何被映射、数据库连接如何被复用。项目附带完整源码p2p.zip、MySQL脚本iting.sql、项目文档和运行工具前后台共九个功能模块导入数据库、启动Tomcat就能跑适合做毕设二次开发也适合刚学完JavaWeb想拆开完整项目看代码结构的人。2. 数据库设计与连接层从iting.sql反推表结构拿到压缩包先别急着启动第一件事是打开iting.sql把表结构过一遍。金融借贷系统的业务复杂度不高但表与表之间的外键、状态字段和金额字段足以体现一个毕设项目的设计水平。数据库用MySQL还是MariaDB并不关键关键是表拆到什么粒度能让前后台代码都写得顺。2.1 核心表结构与字段约束融资产品这一侧项目里拆出了产品类型、产品、贷款周期三张字典/业务表。产品类型表存放“抵押贷”“信用贷”“车贷”之类的分类贷款周期表存“1个月、3个月、6个月、12个月”这类档位产品表通过外键引用两张字典表。这样拆的好处是后台“融资产品管理”和“产品类型管理”两个菜单可以独立维护增加一种新产品类型时不需要动产品表结构。DROP TABLE IF EXISTS t_product_type; CREATE TABLE t_product_type ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, type_name VARCHAR(50) NOT NULL UNIQUE COMMENT 类型名如信用贷, sort INT DEFAULT 0 COMMENT 排序权重, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; DROP TABLE IF EXISTS t_product; CREATE TABLE t_product ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(80) NOT NULL COMMENT 产品名称, type_id INT NOT NULL COMMENT 外键产品类型, period_id INT NOT NULL COMMENT 外键贷款周期, annual_rate DECIMAL(5,2) NOT NULL COMMENT 年化利率%, min_amount DECIMAL(10,2) DEFAULT 0, max_amount DECIMAL(10,2) DEFAULT 0, description TEXT, cover_image VARCHAR(255) COMMENT 产品配图路径, status TINYINT DEFAULT 1 COMMENT 0下架 1上架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_product_type (type_id), KEY idx_product_period (period_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_loan_apply ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, product_id INT NOT NULL COMMENT 申请的融资产品, apply_no VARCHAR(32) UNIQUE COMMENT 业务编号L时间戳, username VARCHAR(30) NOT NULL COMMENT 客户姓名, phone VARCHAR(20) NOT NULL, amount DECIMAL(12,2) NOT NULL COMMENT 申请金额, period INT NOT NULL COMMENT 借款周期单位月, purpose VARCHAR(200) COMMENT 借款用途, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待审核 1已通过 2已拒绝 3已放款, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, audit_time DATETIME COMMENT 审核时间, audit_reason VARCHAR(200) COMMENT 审核意见, KEY idx_apply_product (product_id), KEY idx_apply_status (status), CONSTRAINT fk_apply_product FOREIGN KEY (product_id) REFERENCES t_product(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;上面两张建表语句里有三个细节答辨时讲出来比单纯念表名有价值。第一金额用DECIMAL(12,2)而不用FLOAT/DOUBLE浮点数计算有精度误差金融系统对金额精度敏感DECIMAL(12,2)最大支持十亿级金额毕设场景足够。第二状态字段用TINYINT而非字符串由代码层的常量或枚举维护状态含义避免字符串拼写不一致。第三业务单号apply_no加了UNIQUE约束一条申请从生成起就保证编号唯一。数据库脚本中还有新闻、企业、后台用户、公告等表结构上没有特殊难点基本就是上述字段风格的延伸。下面把四个业务域的常用表整理出来业务域核心表关键字段关联对象融资产品t_product / t_product_type / t_loan_periodannual_rate、type_id类型表、周期表贷款申请t_loan_applystatus、amount、product_id产品表内容管理t_news / t_news_typetitle、content、publish_time新闻分类后台账号t_adminusername、password、salt单个管理员2.1.1 外键与索引的取舍使用FOREIGN KEY在毕设里能直观展示关联关系但真实生产系统经常移除外键把一致性交给应用层。高频写入时外键带来额外检查开销分库分表后数据库层外键更不好维护。本项目保留外键没问题但你应该清楚这个权衡例如审核贷款申请时需要读取产品利率和金额上限如果把 product_id 外键去掉DAO层就得自己先查产品再查申请记录一致性逻辑也挪到应用里。连接参数的时区设置容易被忽略尤其MySQL 8.x下缺了会直接报异常jdbc.urljdbc:mysql://localhost:3306/iting?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf82.2 连接管理手写JDBC还是引入连接池项目后台框架是Servlet JDBC没有引入MyBatis。这里实际有个选择数据源直接用DriverManager还是用DBCP/C3P0或Druid管连接。如果源码里就是纯DriverManager每个DAO方法会重复类似的样板代码Connection conn null; PreparedStatement ps null; ResultSet rs null; try { Class.forName(com.mysql.cj.jdbc.Driver); conn DriverManager.getConnection(url, username, password); String sql SELECT id, name FROM t_product WHERE status 1 AND type_id ?; ps conn.prepareStatement(sql); ps.setInt(1, typeId); rs ps.executeQuery(); // 组装对象并返回 } catch (Exception e) { e.printStackTrace(); } finally { if (rs ! null) rs.close(); if (ps ! null) ps.close(); if (conn ! null) conn.close(); }这段代码本地单用户访问没问题但每次执行SQL都创建和销毁一个TCP连接MySQL服务端也在持续新增和释放线程并发上来必然扛不住。常见做法是引入DBCP或Druid用少量配置把连接管理交给连接池BasicDataSource ds new BasicDataSource(); ds.setDriverClassName(com.mysql.cj.jdbc.Driver); ds.setUrl(url); ds.setUsername(username); ds.setPassword(password); ds.setInitialSize(5); ds.setMaxActive(20); ds.setMaxIdle(10); // 每个DAO通过 ds.getConnection() 替换 DriverManager.getConnection()改造量很小替换DriverManager.getConnection那几行为ds.getConnection()即可DAO对外返回值不变但是50个并发请求下有连接池与无连接池的响应时间差异能明显感知。initialSize是启动时预建的连接数maxActive是最大并发连接数这两个参数直接决定系统能扛住多少并发写文档时值得提一句。3. 前台查询链路产品列表、Ajax新闻详情与贷款申请前台主页是用户第一眼看到的东西也是整个系统数据最密集的一块。它同时涉及列表页渲染、详情页跳转、异步接口和表单提交四类典型场景。把这条链路拆清楚后面做后台管理就轻松很多。3.1 Servlet路由与请求生命周期这个项目没有用SpringMVC请求通过WebServlet注解映射到Servlet类。一行WebServlet(/product/list)就把“URL到处理类”的映射关系写明白了搜索URL就能直接定位处理逻辑比注解路径更直观这对代码阅读者是友好的。从浏览器发起一次请求按生命周期拆解浏览器发起 GET /product/list?type1page1 - Tomcat 接收请求 - 匹配到 ProductListServlet.doGet() - 设置编码、读取参数 - 调用 ProductDao.queryProducts(type, page) - PreparedStatement 执行 SQL 并返回 List - request.setAttribute() 传给 JSP - JSP 用 JSTL/EL 渲染表格或卡片 - 服务器返回 HTML项目一般按Servlet - Service - DAO - DB分层业务量不大但边界清晰。分层带来的直接好处是后续把DAO替换成MyBatis时Servlet和Service基本不用动。3.2 融资产品列表查询与分页列表页最常用的参数是“按类型筛选”和“按页翻”对应的SQL用LIMIT实现。参数化查询是底线字符串拼接会把系统暴露在SQL注入风险中WebServlet(/product/list) public class ProductListServlet extends HttpServlet { private ProductDao productDao new ProductDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); int typeId parseInt(req.getParameter(type), 0); int page parseInt(req.getParameter(page), 1); int pageSize 6; ListProduct list productDao.queryByPage(typeId, page, pageSize); int total productDao.countByType(typeId); int totalPage (int) Math.ceil(total / (double) pageSize); req.setAttribute(productList, list); req.setAttribute(totalPage, totalPage); req.setAttribute(currentPage, page); req.getRequestDispatcher(/WEB-INF/jsp/product_list.jsp).forward(req, resp); } private int parseInt(String v, int def) { if (v null || v.trim().isEmpty()) return def; try { return Integer.parseInt(v); } catch (NumberFormatException e) { return def; } } }这段代码里三个参数值得说明。pageSize6是每页展示的产品数改这个值直接影响首页滚动长度金融/贷款类展示页放6到12个比较合适取决于产品卡片高度。totalPage由总数除以每页数再向上取整得出必须转成double再除否则整数相除直接截断最后一页数据会丢失。parseInt方法建议保留前端传入pageabc时不会直接抛500而是回落成默认值这是一种很省事的防御性写法。DAO层分页查询用LIMIT ?, ?第一个参数是偏移量第二个是查询条数public ListProduct queryByPage(int typeId, int page, int pageSize) { String sql SELECT id, name, annual_rate, min_amount, max_amount, loan_limit, cover_image FROM t_product WHERE status 1 AND (? 0 OR type_id ?) ORDER BY create_time DESC LIMIT ?, ?; int offset (page - 1) * pageSize; try (Connection conn ds.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, typeId); ps.setInt(2, typeId); ps.setInt(3, offset); ps.setInt(4, pageSize); try (ResultSet rs ps.executeQuery()) { ListProduct list new ArrayList(); while (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setName(rs.getString(name)); p.setAnnualRate(rs.getBigDecimal(annual_rate)); // 其余字段按同样方式填充 list.add(p); } return list; } } catch (SQLException e) { throw new RuntimeException(查询产品列表失败, e); } }这里用(? 0 OR type_id ?)避免写两条SQL分支前端不传类型时 typeId0第一个条件成立第二个条件虽然存在但不影响结果传了具体类型时第一个条件为false第二个生效。JDK 7之后的 try-with-resources 语法能自动关闭连接和语句不需要手写finally块代码短很多。DAO层只返回ListProduct不在Servlet里拼接SQL后续替换存储层或加缓存都更容易。3.3 新闻列表与详情的Ajax交互新闻模块的前台交互有两种做法超链接直接跳转JSP或者进入页面后通过Ajax异步加载内容。这个项目采用Ajax页面不刷新体验接近现在的单页应用。相应的Servlet端返回JSON而不是返回HTML片段WebServlet(/news/detail) public class NewsDetailServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType(application/json;charsetUTF-8); int id Integer.parseInt(req.getParameter(id)); News news newsDao.findById(id); JSONObject result new JSONObject(); if (news ! null) { result.put(code, 200); result.put(data, new JSONObject() .put(title, news.getTitle()) .put(content, news.getContent()) .put(publishTime, news.getPublishTime() )); } else { result.put(code, 404); result.put(msg, 新闻不存在); } resp.getWriter().write(result.toJSONString()); } }$.ajax({ url: /news/detail, type: GET, data: { id: newsId }, dataType: json, beforeSend: function () { $(#newsContent).html(div classloading加载中.../div); }, success: function (res) { if (res.code 200) { $(#newsTitle).text(res.data.title); $(#newsContent).html(res.data.content); } else { alert(res.msg || 加载失败); } }, error: function () { $(#newsContent).html(span classtext-danger网络异常稍后重试/span); } });上面Ajax代码的dataType: json意味着后端必须返回合法JSONServlet某处异常导致输出非JSON内容前端会进入error回调。实际开发建议所有异步接口统一返回壳{code, msg, data}错误时也返回{code: 500, msg: ...}前端只判断code比用HTTP状态码分发分支更一致。后端拼JSON时注意news.getPublishTime()返回java.util.Date直接用Fastjson序列化往往是一串时间戳这里手动转字符串可保证和前端展示格式一致。3.4 申请贷款的参数组织与后端校验申请贷款表单一般包含姓名、手机号、申请金额、借款周期、借款用途以及选定的产品ID。最容易出现的问题是只有前端校验绕过页面直接POST就为空数据入库。后端至少做三件事非空校验、手机号格式校验、金额范围校验。表单字段校验规则后端处理姓名必填长度2-20空白字符直接拦截手机号正则^1\\d{10}$不匹配返回提示申请金额大于0且不超过产品上限BigDecimal compareTo借款周期必须存在于周期表外键关联验证产品ID必须存在且状态为上架不存在返回提示public Result apply(String username, String phone, BigDecimal amount, Integer period, Integer productId) { if (StringUtils.isBlank(username)) return Result.fail(姓名不能为空); if (StringUtils.isBlank(phone) || !phone.matches(^1\\d{10}$)) return Result.fail(手机号格式不正确); if (amount null || amount.compareTo(BigDecimal.ZERO) 0) return Result.fail(申请金额不合法); Product product productDao.findById(productId); if (product null || product.getStatus() ! 1) { return Result.fail(产品不存在或已下架); } if (amount.compareTo(product.getMaxAmount()) 0) { return Result.fail(超过产品最高可借金额); } String applyNo L System.currentTimeMillis(); loanApplyDao.insert(applyNo, username, phone, amount, period, productId); return Result.success(申请提交成功等待后台审核); }注意BigDecimal的比较必须用compareTo而不是equalsequals会同时比较精度new BigDecimal(100.0).equals(100)返回false这是金额校验最常见的坑。手机号校验用一个正则就够不需要引入额外依赖。防重复提交可以在前端把提交按钮置灰后端再对同一手机号短期内的提交次数做限制毕设阶段做到这个程度足够。4. 后台管理登录鉴权、贷款审核与产品维护后台是整个系统真正“干活”的地方也是答辩时老师问得最深的部分。后台的复杂点不在代码难度而在状态管理和权限控制。一个贷款申请从提交到放款要经历多次状态变更产品上下线还要考虑联动。4.1 登录与Session会话保持后台登录常见方案是“用户名 密码”提交到AdminLoginServlet验证通过后写Session页面放WEB-INF下避免直接被URL访问。密码存储不能是明文毕设源码里即使只有MD5文档里也要写明“生产环境务必加盐哈希”。WebServlet(/admin/login) public class AdminLoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); Admin admin adminDao.findByUsername(username); if (admin null) { req.setAttribute(error, 用户不存在); req.getRequestDispatcher(/admin/login.jsp).forward(req, resp); return; } String inputPwd MD5Util.md5(password admin.getSalt()); if (!inputPwd.equals(admin.getPassword())) { req.setAttribute(error, 密码错误); req.getRequestDispatcher(/admin/login.jsp).forward(req, resp); return; } req.getSession().setAttribute(admin, admin); resp.sendRedirect(req.getContextPath() /admin/index); } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { req.getSession().removeAttribute(admin); resp.sendRedirect(req.getContextPath() /admin/login.jsp); } }MD5Util.md5(password admin.getSalt())是加盐哈希的简化版密码明文拼接随机salt再做MD5即使两个用户密码相同存储的哈希值也不同。MD5本身已不适合做密码存储毕设文档里写“生产环境换为BCrypt/Argon2”反而说明你知道边界。这中间还有个关键点forward与sendRedirect的区别。forward是服务端内部转发URL地址不变适合登录失败后把错误信息带回登录页sendRedirect是浏览器重新发起请求URL改变适合登录成功后跳转新页面避免刷新时重复提交表单。后台还需一层拦截逻辑。登录成功后访问各管理页面每个Servlet都判断Session有无admin会很啰嗦用Filter统一拦截/admin/*更干净WebFilter(/admin/*) public class AdminAuthFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpSession session req.getSession(false); if (session ! null session.getAttribute(admin) ! null) { chain.doFilter(request, response); } else { HttpServletResponse resp (HttpServletResponse) response; resp.sendRedirect(req.getContextPath() /admin/login.jsp); } } }Filter做登录鉴权是整块方案最省代码的方式以后新增后台管理Servlet无需重复写Session判断。唯一要留意/admin/*会同时拦截登录接口本身所以要么在Filter里放行登录URL要么按上面这种“Session为空就跳到登录页”的方式处理不会形成重定向死循环。组件职责注意点AdminLoginServlet账号密码校验、写入Session失败用forward带回错误信息AdminLogoutServlet移除Session并重定向到登录页同时清掉自动登录CookieAdminAuthFilter拦截 /admin/* 未登录请求放行登录URL避免重定向循环4.2 贷款审核的状态机流转贷款申请表t_loan_apply的status字段取值是0待审核、1已通过、2已拒绝、3已放款。四态对应的后台操作分别是查询待审列表、通过、拒绝、确认放款。状态机最简单的实现方式是定义一个常量类public final class LoanStatus { public static final int PENDING 0; public static final int APPROVED 1; public static final int REJECTED 2; public static final int LOANED 3; public static String desc(int status) { switch (status) { case PENDING: return 待审核; case APPROVED: return 已通过; case REJECTED: return 已拒绝; case LOANED: return 已放款; default: return 未知; } } }审核操作的SQL要带条件status 0目的只有一个防止两人同时打开后台对同一条申请做重复审核。第一次审核把status改成1第二条SQL更新时WHERE id? AND status0影响行数为0自然失败已通过的申请不会被再刷新一遍。这是乐观锁的极简形态不引入版本号也能解决大部分竞态问题。public boolean approve(Integer applyId, String auditor, String reason) { if (applyId null) return false; String sql UPDATE t_loan_apply SET status 1, auditor ?, audit_time NOW(), audit_reason ? WHERE id ? AND status 0; try (Connection conn ds.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, auditor); ps.setString(2, reason); ps.setInt(3, applyId); return ps.executeUpdate() 0; } catch (SQLException e) { throw new RuntimeException(更新审核状态失败, e); } }拒绝操作是同一套逻辑只是把status改成2。审核列表页面默认只查status 0并按创建时间倒序否则历史审核记录一直混在待审列表里误操作概率会明显上升。4.3 产品与新闻的后台维护FileUpload上传融资产品维护必然涉及图片上传技术栈里的FileUpload是Apache Commons FileUpload组件。使用该组件两个前提表单加enctypemultipart/form-data后端不能用request.getParameter直接拿文本字段必须先解析FileItemWebServlet(/admin/product/save) public class ProductSaveServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); Product product new Product(); if (ServletFileUpload.isMultipartContent(req)) { DiskFileItemFactory factory new DiskFileItemFactory(); factory.setSizeThreshold(1024 * 1024); factory.setRepository(new File(System.getProperty(java.io.tmpdir))); ServletFileUpload upload new ServletFileUpload(factory); upload.setFileSizeMax(2 * 1024 * 1024); upload.setSizeMax(10 * 1024 * 1024); try { ListFileItem items upload.parseRequest(req); for (FileItem item : items) { if (item.isFormField()) { String fieldName item.getFieldName(); String value item.getString(UTF-8); switch (fieldName) { case name: product.setName(value); break; case annualRate: product.setAnnualRate(new BigDecimal(value)); break; case typeId: product.setTypeId(Integer.parseInt(value)); break; } } else { String fileName new File(item.getName()).getName(); String ext fileName.substring(fileName.lastIndexOf(.)); String newName System.currentTimeMillis() ext; File dir new File(req.getServletContext().getRealPath(/upload)); if (!dir.exists()) dir.mkdirs(); item.write(new File(dir, newName)); product.setCoverImage(upload/ newName); } } productDao.insert(product); resp.sendRedirect(req.getContextPath() /admin/product/list); } catch (Exception e) { throw new ServletException(文件上传失败, e); } } } }这里有几个参数值得说明。factory.setSizeThreshold(1MB)是内存缓冲区阈值文件小于1MB放内存超过先落临时目录upload.setFileSizeMax(2MB)是单文件上限upload.setSizeMax(10MB)是整次请求总大小上限。这两个限制必须在服务端做否则传一个大文件时Tomcat直接内存溢出。new File(item.getName()).getName()是为了去掉IE/Edge浏览器提交时带上的完整本地路径防止目录穿越。图片用时间戳重命名避免重名覆盖但也丢失了原始文件名生产环境一般用UUID。新闻管理的增删改查类似只是少了文件上传。后台表格用Bootstrap样式渲染操作按钮绑定事件后通过Ajax或表单同步提交均可毕设里维护操作用同步表单提交反而更直观刷新后状态完全由后端控制。5. 本地部署排错与从毕设到生产的改造清单5.1 本地跑通的三个关键配置运行JavaWeb项目最常折在三个地方MySQL驱动版本不匹配、中文乱码、端口占用。MySQL 5.x和8.x驱动类名不同5.x用com.mysql.jdbc.Driver8.x必须用com.mysql.cj.jdbc.Driver且8.x驱动连接串不带serverTimezone参数会直接抛异常。Tomcat 8.5及以上版本还要在web.xml注册编码过滤器同时连接串设置characterEncodingutf8否则前台中文显示乱码。filter filter-namecharacterEncodingFilter/filter-name filter-classcom.it.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filterTomcat端口被占用时修改conf/server.xml里的8080即可8005和8009一般不会冲突只改8080足够。症状常见原因解决动作ClassNotFoundException: com.mysql.jdbc.Driver驱动版本不一致换成 mysql-connector-java 8.x 或改驱动类名中文乱码请求/响应编码未设置添加编码过滤器连接串加 characterEncodingutf8denied for user rootlocalhost主机或密码配置错误检查MySQL用户授权root一般只允许本机连接port 8080 already in use端口被占用或重复启动Tomcat杀掉占用进程或修改 server.xml 端口文件上传解析异常超过上传大小限制调大 setFileSizeMax或确认表单中 enctype5.2 从毕设到生产的九个改造点把这份源码作为生产系统起点时改造优先级参考下表优先级改造点说明P0密码存储改为BCryptMD5加盐只是过渡生产必须防碰撞P0SQL参数化复核DAO全部用PreparedStatement杜绝拼接P0增加操作日志审核、上架、下线等关键动作记录操作人P1增加事务控制贷款状态变更与流水写入必须在同一事务P1连接池替换用Druid或HikariCP替换DriverManagerP1统一响应结构所有接口返回{code, msg, data}而不是裸文本P2引入MyBatis Plus替换重复的DAO样板代码P2文件上传改OSS本地磁盘存储不利于水平扩展P2增加Redis缓存产品列表和新闻列表缓存降低DB压力排查接口类问题还有一招很实用在DAO层把SQL连同参数拼出来打印到控制台十秒就能判断是参数传错了还是条件写错了比对着浏览器开发者工具猜前端传值快得多。系统回归测试做完后再统一关掉这些日志输出即可。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价