☰
Java C/S架构进销存系统实战:从环境搭建到避坑指南
2026/10/17 2:51:56 网站建设 项目流程

简介:一套完整的Java C/S进销存系统项目资料,覆盖采购管理、销售管理、库存管理、财务对账等核心业务,并附有需求分析、业务流程图、用例图、数据库设计和详细设计文档,适合课程设计、毕业设计或初步接触企业级C/S架构的开发者参考。包体共252个文件、约1.63MB,以Java源码(68个)、编译后的class文件(166个)为主,另含doc需求/设计文档、SQL数据库脚本、properties配置、项目工程文件及界面截图,可直接对照源码理解Swing界面、JDBC数据访问、MVC分层与数据库表关系设计。已有199人学习下载。其价值在于:完整源码配合需求文档和设计文档,能帮读者梳理采购、入库、销售、出库、库存预警的完整业务链路;同时提供数据库脚本和工程配置,便于快速搭建运行环境;源码中数据访问层与界面层职责清晰,适合作为进销存项目的入门参考和二次开发基础。

1. 一套Java C/S进销存资源:先搞清它解决什么问题

进销存系统用Java C/S架构,搭配需求文档和详细设计文档,这套组合在校园项目和企业内部工具里都很常见。很多开发者下载后第一反应是:代码能跑吗?数据库怎么初始化?文档和代码能对上吗?我最近拆了一套典型的Java C/S进销存源码,里面包含需求文档、详细设计文档、SQL脚本和完整Java工程。结论:只要JDK和MySQL版本对齐,半小时能跑通登录和进销存主流程。但坑也不少——中文乱码、事务不回滚、界面假死,几乎每个环节都有人翻车。本文就把落地路径、核心模块和踩坑记录按顺序拆开讲,适合刚接触C/S项目的学生,也适合要给小公司做内部管理系统的开发者。

2. 资源拆解:需求文档、详细设计文档和代码怎么对照着看

2.1 需求文档不是摆设:功能清单先画出边界

拿到资源包后,我习惯先不看代码,先看需求文档。这份进销存的需求文档大约30页,覆盖了背景、角色定义、功能需求、非功能需求。角色分三类:管理员、库管员、财务。功能需求围绕“进、销、存、报”四条线展开,下面这张表是核心模块的提炼:

模块功能点角色
基础资料商品分类维护、商品档案、供应商管理、客户管理管理员
采购管理采购订单、入库审核、退货库管员
销售管理销售订单、出库审核、退货库管员
库存管理库存查询、库存预警、盘点库管员
统计报表进销存汇总、利润统计管理员/财务

需求文档里每个功能都有用户故事,比如“作为库管员,我希望在商品入库时自动更新库存,以便实时掌握库存数量”。拿到代码后,先看主窗体菜单栏,对照这张表打勾。能跑通的功能打“Y”,空壳写“N”。常见问题是文档里写了“库存盘点”,代码里根本没有,或者点击后弹“未实现”。这是课程设计资源最常见的问题,因为作者为了凑文档把不存在的功能写进去了。

我一般会按三遍法来核对需求文档:第一遍通读,了解业务全貌;第二遍把每个功能点对应到菜单路径,比如“采购管理-采购订单”对应主窗体的采购订单菜单;第三遍针对每一个功能点实际操作一遍,记录“预期行为”和“实际行为”的差异。这一步非常关键,因为后面详细设计文档和代码都以需求文档为源头,如果源头有虚,后面全对不上。先画边界,避免被文档带偏。很多同学拿到资源就直接打开代码,结果越看越晕,最后连数据库都建不起来,就是少了这一步。

2.2 详细设计文档:表结构、模块划分和类图怎么落地

详细设计文档一般包含E-R图、数据库表结构、模块类图、关键时序图。对于进销存,核心表大致有这些:

表名说明关键字段
t_user用户表id, username, password, role
t_goods商品表id, code, name, spec, unit, price, category_id
t_supplier供应商表id, name, contact, phone
t_customer客户表id, name, contact, phone
t_purchase_in入库单主表id, order_no, supplier_id, total_amount, status, create_time
t_purchase_item入库单明细id, order_id, goods_id, quantity, price
t_sale_out出库单主表id, order_no, customer_id, total_amount, status, create_time
t_sale_item出库单明细id, order_id, goods_id, quantity, price
t_stock库存表id, goods_id, quantity, update_time

详细设计文档里的表结构是数据库初始化脚本的“答案来源”。拿到SQL脚本后,先用文本编辑器打开,搜索CREATE TABLE,核对字段是否与文档一致。常见差异:文档里的t_stock有两个字段min_quantity和max_quantity,脚本里没有;或者类型从DECIMAL(10,2)变成DOUBLE。这种不一致会导致后面SQL语句运行报错或数据精度丢失。

模块划分方面,这份资源采用Swing客户端 + JDBC直连MySQL的胖客户端模式,严格说也是Client/Server架构中的一种——客户端负责界面和业务逻辑,MySQL服务器负责数据存储。类包结构一般分view、service、dao、model、util。我一般会在IDE里展开包结构,截图保存到笔记里,然后对照详细设计文档的类图核对。文档里画了UserService、GoodsService、StockService,代码里一般都有,但文档里的ReportService可能被合并到StatisticsService里。这种小差异不影响运行,但影响你理解“作者当时是怎么设计的”。

核对表结构时,我习惯按这个步骤走:

  1. 打开详细设计文档的“数据库设计”章节。
  2. 打开SQL目录下的init.sql。
  3. 用文本编辑器的搜索功能,把每个核心表名过一遍。
  4. 记录差异,比如字段缺失、库存表没有预警值字段、外键约束可能没建。
  5. 以SQL脚本为准,因为代码里的SQL语句是照着脚本写的,文档不是。

为什么C/S模式值得学?因为进销存是典型的数据录入密集、响应要求高的系统,C/S模式下Swing界面操作流畅,本地直连数据库延迟低,适合局域网内小规模用户。缺点是需要安装客户端、升级麻烦。这份资源的价值在于完整展现了一个C/S系统的分层结构,适合学习JDBC和Swing组合。

3. 环境搭建与数据库初始化:从零跑通这个C/S系统

3.1 JDK版本选择与项目导入

这份资源我拆的版本是基于JDK8开发,编译级别是1.8。如果本机装了JDK17,直接打开可能会报“无法访问或加载类”,主要是Swing没变,但有些老代码用了com.sun.*内部类,或者某些第三方jar不再兼容。建议先装JDK8,并配置JAVA_HOME。

先在命令行确认当前版本:

java -version

如果输出是1.8.0_xxx,可以直接用。如果输出openjdk version "17"或更高,要么下载JDK8并切换,要么把项目的编译级别改成你当前版本的兼容模式。但新手不建议适配,C/S资源里的老代码往往依赖一些在更高版本中被限制的内部API,强行适配可能要改十几处。

导入项目的步骤:用IDEA的话,File -> New -> Project from Existing Sources,选中资源包里的pom.xml或直接选文件夹。如果是Eclipse,File -> Import -> General -> Existing Projects into Workspace。多数资源是没有Maven的,依赖包都在lib目录下。导入后,右键lib下的jar包,选Add as Library,确保编译时不缺包。

检查依赖时,重点看mysql-connector-java.jar是否存在。有些资源包为了压缩体积把这个jar删了,结果一跑就ClassNotFound。

3.2 MySQL建库脚本与配置文件的四个必改参数

资源包一般有一个sql目录,里面是init.sql。执行前,先查看脚本里的数据库名、字符集。

mysql -u root -p < sql/init.sql

如果脚本包含CREATE DATABASE IF NOT EXISTS erp DEFAULT CHARACTER SET utf8;,则会自动建库。如果脚本只建表不建库,需要先手动创建数据库:

CREATE DATABASE erp DEFAULT CHARACTER SET utf8mb4; USE erp; SOURCE /path/to/init.sql;

注意:SOURCE后面的路径要用绝对路径,Windows下是C:/...,反斜杠要转义或改成斜杠。

接着修改配置文件。常见配置文件是src/db.properties或config/db.properties。打开后,里面应该是类似这样的内容:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/erp?useUnicode=true&characterEncoding=utf8&useSSL=false jdbc.username=root jdbc.password=123456 jdbc.pool.maxActive=5

参数说明:

  • jdbc.driver:MySQL 5.x用com.mysql.jdbc.Driver;MySQL 8.x要用com.mysql.cj.jdbc.Driver,否则报ClassNotFoundException。这个是最常见的启动失败原因。
  • jdbc.url:3306是MySQL端口,erp是库名,必须和脚本里的库名一致。useUnicode=true&characterEncoding=utf8解决中文乱码,useSSL=false是为了避免MySQL 8的SSL警告。
  • jdbc.username/password:改成自己本机的账号密码,不要用123456这种大概率不对的占位符。
  • jdbc.pool.maxActive:如果代码里用的是连接池,这个参数决定最大连接数。进销存操作频繁,建议5以上,但也别太大,小服务器会被连接数打爆。

改完配置后,可以先用一个小工具验证连接,避免在UI里反复试错。在IDE里写一个临时的Java类,或者在命令行用MySQL客户端直接连一下库,看能否正常访问。

3.3 启动服务端与客户端:先验证登录

如果资源是带Socket服务端的,流程是:先启动Server.java,再启动LoginUI.java。如果是直连数据库,直接启动客户端即可。

对于带服务端的版本,服务端入口一般在server/ServerMain.java。在IDE里右键运行,进度会停在那里等待客户端连接。也可以命令行启动:

java -cp .;lib/mysql-connector-java.jar server.ServerMain

说明:-cp指定类路径,Windows下分隔符是;,Linux/Mac是:。如果服务端端口被占用,会报BindException,需要改server.properties里的server.port,默认10200。

客户端启动入口:

java -cp .;lib/mysql-connector-java.jar view.LoginUI

登录界面输入默认账号admin/admin123。如果登录失败,优先看控制台报错——90%的情况是数据库连接问题,解决方案见第5章避坑。

提示:如果脚本里预置的管理员密码是明文123456,而代码里登录时做了MD5加密,那么无论如何都登录不进去。常见做法是先在SQL里执行UPDATE t_user SET password=MD5('admin123') WHERE username='admin';。

4. 核心模块实战:登录、商品管理、出入库和库存查询

4.1 登录模块的密码校验与会话保持

登录模块是C/S进销存的门面。常见的错误写法是对比明文密码,正确做法是数据库存MD5值,登录时把输入密码做同样的摘要再去比对。用PreparedStatement而不是Statement拼接,否则SQL注入分分钟被答辩老师盯上。

public User login(String username, String password) throws Exception { String sql = "SELECT id, username, role FROM t_user WHERE username=? AND password=?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, MD5Util.md5(password)); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { User u = new User(); u.setId(rs.getInt("id")); u.setUsername(rs.getString("username")); u.setRole(rs.getString("role")); return u; } } } catch (SQLException e) { LogUtil.error("login", e); throw new BusinessException("登录查询失败,请检查数据库连接"); } return null; }

逻辑说明:?占位符先set参数再执行,避免SQL拼接;密码在数据库里存的是MD5摘要,登录时同样做MD5,比对是在SQL层完成,所以查不到就返回null。try-with-resources保证连接、PreparedStatement、ResultSet自动关闭,避免连接泄漏。

参数说明:如果MySQL版本是8,DBUtil.getConnection()内部的Class.forName要写成com.mysql.cj.jdbc.Driver。MD5Util.md5在工具类里,如果源码包没有,可以自己实现,算法固定。BusinessException是自定义异常,在Service层统一抛出后,由UI层捕获并弹窗,不要直接让SQLException裸奔到界面。

会话保持:登录成功后,把User对象放到一个全局的AppContext中,界面上按钮的权限根据role值动态启用/禁用。比如管理员可见“用户管理”菜单,库管员隐藏。有些资源包只是把id存下来,没有权限区分,这个后面进阶章节再补。

4.2 商品档案管理:C/S模式下的增删改查

商品档案是进销存的基础数据。在C/S模式下,界面常用表格JTable展示,底层用DefaultTableModel。添加商品时,先做前端校验,再调用Service层。这里给出一个Service层的插入方法:

public void addGoods(Goods g) throws Exception { Connection conn = DBUtil.getConnection(); String sql = "INSERT INTO t_goods (code, name, spec, unit, price, category_id) VALUES (?,?,?,?,?,?)"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, g.getCode()); ps.setString(2, g.getName()); ps.setString(3, g.getSpec()); ps.setString(4, g.getUnit()); ps.setBigDecimal(5, g.getPrice()); ps.setInt(6, g.getCategoryId()); ps.executeUpdate(); } }

逻辑说明:这里没有手动加事务,因为单表插入不涉及多条SQL;但如果同时要更新分类表里的商品数量统计,就必须包事务。注意price用BigDecimal而不是double,否则后续算总金额会有精度玄学误差。

参数说明:code是商品编码,建议唯一。如果数据库里没加唯一约束,代码里在插入前先查一次是否存在,否则会插入重复数据。spec是规格,比如“500ml/瓶”,这个字段在查询时经常要和名称拼接展示,所以不要留空。

删除和修改同理,但删除前要检查t_stock里是否还有库存。如果库存大于0,不能硬删,否则历史单据对不上账。这个坑在避坑章节也会提到。

在UI层,表格刷新一般用tableModel.setRowCount(0)清空旧数据,再逐行加入新数据。不要直接table.setModel(new DefaultTableModel()),否则会丢列头。

4.3 入库出库单流转:事务和库存更新的边界

这是整个系统最核心的地方。入库操作要同时插入入库单主表、入库单明细,并更新库存表。三步必须在一个事务里,任何一步失败都要回滚。很多新手写代码时只插了主表忘了更新库存,或者更新库存时用了UPDATE t_stock SET quantity = ?而不是quantity = quantity + ?,导致覆盖。

public void purchaseIn(PurchaseOrder order, List<PurchaseItem> items) throws Exception { Connection conn = DBUtil.getConnection(); conn.setAutoCommit(false); try (PreparedStatement psOrder = conn.prepareStatement( "INSERT INTO t_purchase_in (order_no, supplier_id, total_amount, status, create_time) VALUES (?,?,?,?,?)", Statement.RETURN_GENERATED_KEYS); PreparedStatement psItem = conn.prepareStatement( "INSERT INTO t_purchase_item (order_id, goods_id, quantity, price) VALUES (?,?,?,?)"); PreparedStatement psStock = conn.prepareStatement( "UPDATE t_stock SET quantity = quantity + ?, update_time = NOW() WHERE goods_id = ?")) { psOrder.setString(1, order.getOrderNo()); psOrder.setInt(2, order.getSupplierId()); psOrder.setBigDecimal(3, order.getTotalAmount()); psOrder.setInt(4, order.getStatus()); psOrder.setTimestamp(5, new Timestamp(System.currentTimeMillis())); psOrder.executeUpdate(); ResultSet keys = psOrder.getGeneratedKeys(); int orderId = 0; if (keys.next()) { orderId = keys.getInt(1); } for (PurchaseItem item : items) { psItem.setInt(1, orderId); psItem.setInt(2, item.getGoodsId()); psItem.setInt(3, item.getQuantity()); psItem.setBigDecimal(4, item.getPrice()); psItem.executeUpdate(); psStock.setInt(1, item.getQuantity()); psStock.setInt(2, item.getGoodsId()); psStock.executeUpdate(); } conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); } }

逻辑说明:setAutoCommit(false)后,所有SQL在同一个事务里执行;RETURN_GENERATED_KEYS拿到主表自动生成的ID,用来插明细;更新库存时用quantity = quantity + ?,这样不会覆盖已有库存。

参数说明:PurchaseItem的quantity是本次入库数量,必须大于0。如果库存表里还没有该商品记录,需要先初始化一条库存记录,否则UPDATE影响行数为0,没人察觉。这里建议在更新后检查psStock.executeUpdate()返回值,如果等于0,则先执行INSERT INTO t_stock再更新。

出库逻辑刚好相反,库存更新写成quantity = quantity - ?,并且要加一个quantity >= ?的条件,防止库存出成负数。如果UPDATE影响行数为0,说明库存不足,应该抛异常回滚整个出库单。

4.4 库存查询与报表:把数据变成可操作信息

库存查询一般用多表关联:t_stock联t_goods,显示商品编码、名称、规格、当前库存。如果加预警,还要查min_quantity。

public List<StockView> queryStock(String keyword, int page, int pageSize) throws Exception { String sql = "SELECT g.code, g.name, g.spec, s.quantity, g.min_quantity " + "FROM t_stock s JOIN t_goods g ON s.goods_id = g.id " + "WHERE g.name LIKE ? OR g.code LIKE ? " + "ORDER BY g.code LIMIT ? OFFSET ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { String like = "%" + keyword + "%"; ps.setString(1, like); ps.setString(2, like); ps.setInt(3, pageSize); ps.setInt(4, (page - 1) * pageSize); try (ResultSet rs = ps.executeQuery()) { List<StockView> list = new ArrayList<>(); while (rs.next()) { StockView v = new StockView(); v.setCode(rs.getString("code")); v.setName(rs.getString("name")); v.setSpec(rs.getString("spec")); v.setQuantity(rs.getInt("quantity")); v.setMinQuantity(rs.getInt("min_quantity")); list.add(v); } return list; } } }

逻辑说明:LIKE配合%做模糊匹配;分页用LIMIT ? OFFSET ?,注意MySQL的LIMIT参数不能直接拼在字符串里,必须用PreparedStatement设置。OFFSET计算用(page - 1) * pageSize,当page从1开始。

参数说明:pageSize建议20-50,如果一次性加载几千条商品,Swing的JTable会卡顿,所以分页查询是进销存界面流畅的关键。报表统计通常用汇总SQL:

SELECT DATE_FORMAT(create_time, '%Y-%m-%d') day, SUM(total_amount) amount FROM t_sale_out GROUP BY day ORDER BY day DESC LIMIT 30;

这个SQL可以在DAO里执行,返回数据后可以用JFreeChart画柱状图。如果资源包不带图表库,至少用表格展示汇总结果,否则“统计报表”功能就是一个摆设。

5. 避坑指南:进销存C/S项目最常见的五个坑

5.1 数据库连接失败:为什么提示ClassNotFoundException或Access denied

现象:启动客户端后,点击登录,界面弹出“数据库连接异常”,控制台输出ClassNotFoundException: com.mysql.jdbc.Driver或Access denied for user 'root'@'localhost'。

原因:前者是忘记把MySQL驱动jar放进lib目录,或者驱动类名写错(MySQL 8要写com.mysql.cj.jdbc.Driver)。后者是密码错误,或账号没有远程访问权限(如果数据库在另一台机器上)。

解决:先检查db.properties里的driver和URL。如果是MySQL5.x,驱动类名是com.mysql.jdbc.Driver;MySQL8.x是com.mysql.cj.jdbc.Driver。然后确认jar存在。Access denied就重置root密码,或新建专用账号并赋予GRANT ALL PRIVILEGES ON erp.* TO 'erpuser'@'%'。我一般会在命令行mysql -u root -p先试连一次,确认不是MySQL服务没启动。

5.2 中文乱码:JVM、SQL和Swing三方编码不一致

现象:商品名称显示成???,或者插入到数据库里变成乱码。

原因:三个环节出问题都会这样。第一,db.properties里URL没加characterEncoding=utf8;第二,SQL脚本建库时字符集不是utf8;第三,IDE编译Swing文件时用了GBK编码,导致字符串字面量本身是乱码。这是C/S项目最容易翻车的地方,我遇到过折腾一整天最后发现是编译器默认编码问题。

解决:统一UTF-8。在mysql命令行执行SHOW VARIABLES LIKE 'character_set%';,确认character_set_server是utf8。db.properties里加useUnicode=true&characterEncoding=utf8。IDE里把Project Encoding、File Encoding都改成UTF-8。如果已经插入了乱码数据,库里先删掉重新插入。Swing的JTable渲染一般不用改,只要数据源是UTF-8,界面就正常。

5.3 界面假死:EDT里跑了网络IO或慢SQL

现象:点击“查询库存”后,整个窗口变成白屏/无响应,过几十秒才恢复,或者永远不恢复。

原因:Swing的事件分发线程(EDT)被耗时操作阻塞。进销存里最常见的阻塞是:直接在ActionListener里执行Thread.sleep、Socket通信、大表全量查询。比如查询10万条商品数据,EDT卡死,系统看起来像崩溃。

解决:所有可能超过100ms的操作放到SwingWorker或新线程中执行,完成后用SwingUtilities.invokeLater更新UI。例如:

new SwingWorker<List<StockView>, Void>() { @Override protected List<StockView> doInBackground() throws Exception { return queryStock(keyword, page, pageSize); } @Override protected void done() { try { table.setModel(new StockTableModel(get())); } catch (Exception e) { e.printStackTrace(); } } }.execute();

说明:doInBackground在后台线程执行,done在EDT执行。如果直接在doInBackground里往table里set模型,会跨线程操作UI,反而不安全。

5.4 库存对不上:事务没提交或更新语句写错

现象:入库单保存成功,但库存表没变化;或出库后库存不减反增。

原因:典型三种。第一,插入主表和明细在同一个事务,但更新库存的SQL写成了SET quantity = ?,如果传入的是本次数量而不是“原数量+本次数量”,就会把库存覆盖成本次数量。第二,事务没有commit(),或者中间方法吞掉了异常导致回滚没触发。第三,UPDATE t_stock影响0行时没检查,比如商品在库表没有记录。

解决:把库存更新统一写成quantity = quantity + ?;在commit前打印日志看影响行数;更新影响0行时,补一条INSERT INTO t_stock。另外,所有Service方法里不要用try { ... } catch (Exception e) { e.printStackTrace(); },至少要抛到Server层统一处理,否则事务永远不自动回滚。

具体排查时,打开MySQL通用日志是最后的后悔药。临时开启:

SET GLOBAL general_log = 'ON'; SET GLOBAL general_log_file = '/tmp/mysql_general.log';

跑一遍入库操作,然后查看日志里实际执行的SQL顺序,看是否少了UPDATE t_stock。定位到后,再回头看事务边界,基本就能找到问题。

5.5 文档和代码版本不一致:按图索骥反而翻车

现象:详细设计文档里画了“库存盘点”界面截图,代码里根本没有;需求文档里的菜单顺序和实际界面不同;文档里写t_stock有min_quantity字段,实际SQL里没有。

原因:很多资源包是作者在不同时期拼凑的,文档更新到V2.0,代码还停留在V1.0,或者反过来。为了看起来完整,把需求文档、设计文档、答辩PPT都塞进去了,但没做一致性校验。

解决:先跑代码,再读文档。把文档当作“背景知识”,不是“验收标准”。跑通代码后,用表格记录差异,比如“需求文档靠山说支持盘点,代码尚无,建议后续实现”。如果要交课程设计或项目答辩,这个差异表反而是加分项,说明你真的动手验证过。我见过太多人照着文档写出和代码不匹配的“设计亮点”,答辩时被现场演示打脸。

6. 进阶技巧:把单机C/S改造成可交付的小型系统

如果只是跑通,这套资源可以交差了。但真正要部署到小公司,至少还要做三件事:密码加盐、权限拦截、一键启动。

密码加盐:把4.1节的MD5Util.md5(password)改成MD5Util.md5(password + salt),salt存用户表。虽然MD5不是安全算法,但至少比明文强。加盐后的登录SQL要改成先按用户名查出用户和salt,再在Java层计算比对,不能直接在SQL里做拼接。

权限拦截:写一个简单的AuthUtil.check(role, "goods:add"),在Service层入口调用。不要依赖界面按钮是否隐藏,因为C/S客户端被反编译后可以绕过界面直接调Service。如果不想引入Spring这种重框架,可以写一个基础Service类,在子类方法里手动调用AuthUtil.check,或者用动态代理统一拦截。

一键启动:写两个bat脚本,Windows下部署很实用。服务端脚本(如果有服务端):

@echo off cd /d %~dp0 set CLASSPATH=.;lib\mysql-connector-java.jar start javaw -cp %CLASSPATH% server.ServerMain timeout /t 2 start javaw -cp %CLASSPATH% view.LoginUI

说明:start会开新窗口,timeout让服务端先跑起来。如果没有服务端,只启动LoginUI即可。这里的lib目录要和bat脚本放在同一个根目录下,否则类路径找不到驱动。如果是Linux部署,同样思路写start.sh,用nohup java -cp方式后台运行。

我拿到每一套C/S进销存资源,都会强制自己走一遍“建库、改配置、登录、做一张入库单、查库存”的完整流程,再回头去改文档差异记录。这个习惯帮我避掉了大部分“文档对不上代码”的坑,也让交付给别人的时候心里有底。希望帮到你。

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

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

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

立即咨询