尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

摊位租赁管理系统设计与实现:Spring Boot + Vue前后端分离实战

摊位租赁管理系统设计与实现:Spring Boot + Vue前后端分离实战 标题一听就是典型的“毕设练手级”前后端分离项目但它背后其实覆盖了一个非常完整的业务闭环摊位管理、租赁流程、费用计算、合同状态流转、商户信息维护。这东西放在农贸市场、夜市、甚至临时展销会场景里都有真实需求。我做过类似的场地租赁系统踩过不少坑这篇就把整个系统的设计思路、技术选型、数据库设计、核心代码实现和常见问题完整梳理一遍给正在做同类项目的朋友一份能直接参考的实操文档。1. 项目的核心价值与系统边界1.1 为什么需要一套摊位租赁系统农贸市场或者夜市的摊位管理很多地方到现在还在用Excel甚至纸质台账。管理员手工记录哪个摊位租给了谁、租到什么时候、费用收没收信息分散不说还容易出现档期冲突。今天张三租了A区3号摊明天李四也看中同一个摊位一查台账没更新两边都交了定金后面扯皮就非常麻烦。这套系统要解决的就是三个核心问题摊位信息和状态透明化、租赁流程线上化、费用和合同管理规范化。管理员在后台发布摊位、设置租金规则商户在小程序或者网页端浏览摊位、提交租赁申请、在线签约缴费整个链路都是留痕的。1.2 角色与核心业务场景系统的用户角色我建议拆成三类不要做太多做多了权限管理会很啰嗦。系统管理员维护市场信息、管理用户、发布摊位、处理租赁订单、查看经营数据。市场运营人员日常运营比如录入摊位、线下签约辅助、处理退租和续租。商户/摊主浏览摊位、收藏意向摊位、提交租赁申请、查看自己的合同和缴费记录。核心业务场景其实就这么几个摊位发布与状态管理管理员录入摊位编号、区域、面积、租金单价摊位自动处于“空闲”状态。租赁申请与审核商户选择空闲摊位提交租赁申请填写租赁周期管理员审核通过后生成合同。费用计算与缴纳根据租金单价和租赁时长自动计算总费用支持在线支付或者线下确认缴费。到期提醒与续租退租系统定时任务扫描即将到期的合同发送提醒商户可以申请续租或退租。1.3 系统的非功能性需求光把功能做完不够这类项目还要考虑几个容易被忽视的点。第一是并发控制热门夜市摊位很抢手多个商户同时申请同一个摊位系统不能出现超卖。第二是数据统计管理员需要看每月租金收入、摊位出租率这个用SQL聚合就能做但设计表的时候要提前预留字段。第三是操作日志谁在什么时候修改了摊位租金、审核了哪个合同都要有记录真出了纠纷能追溯。2. 技术选型拆解Java、Vue、Spring Boot为什么是这个组合2.1 后端框架Spring Boot的取舍逻辑很多人在做管理系统时会纠结用SSM还是Spring Boot。我的建议是直接Spring Boot别犹豫。SSM时代需要写大量的XML配置数据源、事务、MyBatis映射都要手动配光是环境搭建就能劝退一大半人。Spring Boot把自动配置做到了极致内嵌Tomcat启动一个main方法就能跑起来对于业务管理系统的开发效率提升非常明显。Spring Boot 3.x现在是最新的大版本但是如果你参考的教程和依赖大部分都是2.x建议直接用Spring Boot 2.7.x。原因很简单3.x基于Jakarta EE很多老项目的包名从javax.*改成了jakarta.*遇到问题网上搜到的解决方案可能对不上。做项目最重要的是稳定跑通不是追新。2.2 前端框架Vue的渐进式优势前端选Vue核心原因有三个。第一Vue的学习曲线比React平滑模板语法直观v-for、v-if、v-model这几个指令就能覆盖90%的表单和列表场景。第二Vue的生态完善Element UIVue 2和Element PlusVue 3做后台管理界面非常快表格、弹窗、表单组件开箱即用。第三Vue CLI或者Vite创建项目、开发调试、最终打包部署的流程很成熟。这里有个版本选择问题。Vue 2已经停止维护了新项目建议直接上Vue 3 Vite Element Plus。如果你的毕设模板或者教程是基于Vue 2的也不是不能用但尽量还是用Vue 3面试的时候问起来也好回答。2.3 前后端分离架构与接口约定既然用了Vue Spring Boot天然就是前后端分离架构。后端只负责提供RESTful API返回JSON数据前端负责页面渲染和用户交互。两者通过HTTP协议通信开发阶段用Vite代理解决跨域生产环境用Nginx统一托管前端静态资源并反向代理后端接口。接口格式一定要一开始就统一建议采用这类结构{ code: 200, message: 操作成功, data: { total: 100, records: [] } }code是业务状态码200表示成功401表示未登录500表示服务器异常。message用于前端提示data是真正的业务数据。统一了这套约定前后端联调的时候能省一半的沟通成本。3. 数据库设计与核心模块划分3.1 核心数据表结构设计数据库是这类业务系统的地基表设计直接决定了后续开发是不是顺手。按照租赁系统的业务链路核心表我拆成六张用户表、摊位表、租赁订单表、合同表、租金记录表、操作日志表。用户表sys_user字段名类型说明idbigint主键usernamevarchar(50)登录名passwordvarchar(100)BCrypt加密后的密码real_namevarchar(50)真实姓名/商户名称phonevarchar(20)手机号roletinyint角色1管理员 2运营 3商户statustinyint状态0禁用 1启用create_timedatetime创建时间摊位表stall字段名类型说明idbigint主键stall_novarchar(20)摊位编号如A-001area_codevarchar(10)区域如A区/B区squaredecimal(10,2)面积平方米unit_pricedecimal(10,2)租金单价元/月statustinyint状态0空闲 1预定 2租赁中 3维护remarkvarchar(255)备注租赁订单表rent_order字段名类型说明idbigint主键order_novarchar(32)订单编号stall_idbigint摊位IDuser_idbigint商户用户IDstart_datedate租赁开始日期end_datedate租赁结束日期total_amountdecimal(10,2)总费用statustinyint状态0待审核 1待支付 2租赁中 3已到期 4已取消 5已退租create_timedatetime申请时间这里要特别注意租金单价不能只在摊位表里存一份。因为摊位租金可能会调整合同一旦生成金额应该固化在订单表或者合同表里。否则管理员半年后改了单价之前所有的历史合同金额都变了财务对账会对不上。3.2 核心模块划分从功能模块角度系统可以分为四个大模块系统管理模块用户登录、用户管理、角色权限、操作日志。摊档资源模块摊位区域管理、摊位信息维护、摊位状态看板。租赁业务模块意向申请、审核、合同生成、费用计算、续租/退租。统计报表模块出租率统计、租金收入统计、到期预警列表。模块划分的原则是“低耦合”租赁业务模块不要直接改摊位状态而是通过服务层的方法调用。比如审核通过一份租赁订单订单服务先更新订单状态再调用摊位服务的occupyStall方法把摊位置为租赁中。这样逻辑清晰后面加需求也好扩展。3.3 关键业务状态设计业务状态设计是个容易出错的地方我建议在建表的时候就把枚举值注释写清楚。以订单状态为例0 待审核商户提交申请管理员还没处理。1 待支付管理员审核通过需要商户缴纳租金。2 租赁中确认收款后订单进入租赁周期。3 已到期租赁结束日期小于当前日期系统自动更新。4 已取消商户主动取消或管理员驳回。5 已退租合同期内提前退租并完成结算。摊位状态的流转也要同步设计。空闲摊位被申请后就变成预定状态防止其他商户重复申请管理员审核通过后变成租赁中合同到期后自动回到空闲。4. 后端核心实现Spring Boot业务落地4.1 项目初始化与包结构用Spring Initializr创建项目时依赖选择这块我的建议是Spring Web必须Spring Data JPA 或 MyBatis二选一MySQL Driver必须Lombok强烈推荐减少样板代码Spring Security如果做登录鉴权Quartz 或 Spring Task定时任务做到期扫描如果你的项目已经用了MyBatis-Plus也别纠结要不要换JPA。MyBatis-Plus的单表CRUD非常省事BaseMapper直接把增删改查都封装好了适合快速开发。我这篇文章的代码示例就按MyBatis-Plus来写。包结构建议这么分com.example.stall ├── common // 通用类比如统一返回结果、异常处理 ├── config // 配置类 ├── controller // 接口层 ├── service // 业务层 │ └── impl ├── mapper // 数据访问层 ├── entity // 实体类 ├── dto // 入参出参对象 └── utils // 工具类4.2 实体类与数据访问层实体类直接用MyBatis-Plus的注解举个例子摊位表实体Data TableName(stall) public class Stall { TableId(type IdType.AUTO) private Long id; private String stallNo; private String areaCode; private BigDecimal square; private BigDecimal unitPrice; private Integer status; private String remark; }Data是Lombok提供的自动生成getter/setter代码干净很多。TableName指定表名TableId指定主键生成策略。Mapper层更简单继承BaseMapper就有现成的单表方法Mapper public interface StallMapper extends BaseMapperStall { }代码量少的同时还要保证复杂查询能写SQLMyBatis-Plus支持在Mapper接口里自己定义方法加Select注解或者写XML都可以。比如统计不同区域的摊位出租率Select(SELECT area_code, COUNT(*) AS total, SUM(CASE WHEN status 2 THEN 1 ELSE 0 END) AS rented FROM stall GROUP BY area_code) ListAreaRentStatVO selectAreaRentStat();4.3 核心接口设计与实现后端接口的设计要跟前端页面一一对应这里梳理一份核心API清单方法路径说明POST/api/auth/login登录GET/api/stall/list摊位分页列表支持条件筛选POST/api/stall新增摊位PUT/api/stall修改摊位POST/api/order提交租赁申请GET/api/order/my当前商户的订单列表PUT/api/order/audit管理员审核订单PUT/api/order/pay确认缴费GET/api/stat/rentRate出租率统计提交流转的核心逻辑在订单审核接口里。审核通过时不能只改一个订单状态要在一个事务里完成三件事Transactional public boolean auditOrder(Long orderId, Long operatorId) { // 1. 校验订单当前状态是待审核 RentOrder order orderMapper.selectById(orderId); if (order null || order.getStatus() ! 0) { throw new BusinessException(订单状态异常); } // 2. 校验摊位当前状态是预定状态 Stall stall stallMapper.selectById(order.getStallId()); if (stall null || stall.getStatus() ! 1) { throw new BusinessException(摊位已被占用请刷新后重试); } // 3. 更新订单为待支付摊位状态改为预定如果有必要可以改备注 order.setStatus(1); orderMapper.updateById(order); return true; }注意这个事务里我在更新前先查了摊位状态这是一个乐观校验。如果要更严格可以给订单或者摊位表加乐观锁字段version更新时用UPDATE ... WHERE id ? AND version ?CAS的思路防止并发问题。后面第七节会专门讲。4.4 定时任务实现到期状态扫描到期自动更新这个功能不要放在接口请求里去判断要用定时任务。Spring Boot自带的Scheduled注解就够了不需要引入Quartz除非你要做分布式任务调度。Component public class OrderExpireTask { Autowired private RentOrderMapper rentOrderMapper; Autowired private StallMapper stallMapper; Scheduled(cron 0 0 2 * * ?) public void expireOrders() { // 每天凌晨2点执行找出所有状态为租赁中且结束日期小于今天的订单 LambdaQueryWrapperRentOrder wrapper new LambdaQueryWrapper(); wrapper.eq(RentOrder::getStatus, 2) .lt(RentOrder::getEndDate, LocalDate.now()); ListRentOrder expiredOrders rentOrderMapper.selectList(wrapper); for (RentOrder order : expiredOrders) { order.setStatus(3); rentOrderMapper.updateById(order); // 摊位释放为空闲 Stall stall stallMapper.selectById(order.getStallId()); stall.setStatus(0); stallMapper.updateById(stall); } } }注意cron表达式里0 0 2 * * ?表示每天凌晨2点触发避免白天业务高峰时间跑任务。如果市场有夜间经营的特点跑批时间可以调整到凌晨4点以后。5. 前端核心实现Vue页面与交互5.1 项目初始化与环境配置前端我建议用Vite创建Vue 3项目命令很简单npm create vitelatest stall-admin -- --template vue cd stall-admin npm install npm install element-plus axios vue-router pinia安装依赖以后在main.js里注册Element Plusimport { createApp } from vue import ElementPlus from element-plus import element-plus/dist/index.css import App from ./App.vue import router from ./router const app createApp(App) app.use(ElementPlus) app.use(router) app.mount(#app)很多人会在vue安装和依赖配置上踩坑最常见的错误就是npm版本和node版本不匹配。Vite 5要求Node.js 18如果你的电脑还是Node 14运行npm create vite的时候会直接报错。我的建议是装一个nvm来管理Node版本需要哪个版本切哪个版本省心很多。5.2 路由配置与登录鉴权既然有角色区分前端路由肯定要做权限控制。思路是登录成功后后端返回当前用户的角色标识前端存到Pinia里路由守卫里判断目标路由需要的角色。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() return } if (!token) { next(/login) return } // 这里可以再判断角色如果非管理员访问admin页面就重定向 next() })localStorage存token的方式简单粗暴适合毕设和管理系统。但是要注意token存localStorage有XSS风险如果项目要上生产环境建议改用httpOnly的Cookie方案。另外后端返回的token建议设置过期时间前端在请求拦截器里监听401状态码自动跳转登录页。5.3 核心业务页面实现我要重点讲摊位管理页面这个页面涉及列表查询、状态筛选、弹窗编辑是后台管理的典型形态。模板部分核心代码el-table :datastallList v-loadingloading el-table-column propstallNo label摊位编号 width120 / el-table-column propareaCode label所属区域 width100 / el-table-column propsquare label面积(m²) width100 / el-table-column propunitPrice label月租金(元) width120 / el-table-column label状态 width100 template #default{ row } el-tag :typestatusTagType(row.status) {{ statusText(row.status) }} /el-tag /template /el-table-column el-table-column label操作 width200 template #default{ row } el-button typeprimary link clickeditStall(row)编辑/el-button el-button typedanger link clickdeleteStall(row.id)删除/el-button /template /el-table-column /el-table状态显示用el-tag加不同颜色空闲用绿色租赁中用橙色维护中用灰色这样管理员扫一眼就能知道摊位情况。statusText和statusTagType是两个纯函数专门做状态码到文案/颜色的映射。这种写法比在模板里写一堆v-if干净多了const statusMap { 0: { text: 空闲, type: success }, 1: { text: 预定, type: warning }, 2: { text: 租赁中, type: danger }, 3: { text: 维护, type: info } } function statusText(status) { return statusMap[status]?.text || 未知 } function statusTagType(status) { return statusMap[status]?.type || info }5.4 axios封装与接口对接axios封装这块我建议封装一个请求工具统一处理请求头、响应拦截和错误提示。代码不复杂但是能省很多重复劳动import axios from axios import { ElMessage } from element-plus import router from ../router const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动携带token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截器统一处理业务错误码 request.interceptors.response.use( response { const res response.data if (res.code 200) { return res.data } ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) }, error { if (error.response?.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(网络异常请稍后重试) return Promise.reject(error) } ) export default request这样封装以后业务代码里只需要写const data await request.get(/stall/list, { params: { pageNum: 1, pageSize: 10 } })而且生产环境部署的时候baseURL设置为/api配合Nginx把/api反向代理到后端服务前后端完全解耦。6. 租赁业务的关键细节与算法6.1 租赁费用计算逻辑费用计算是整个系统最容易出bug的地方因为规则灵活。有的市场按固定月租收有的按摊位面积乘以单价收还有的按月租加管理费。我建议把费用计算独立成一个工具类后期改规则只改这里public class RentCalculator { /** * 计算租赁总费用 * param unitPrice 月租金单价 * param square 面积 * param chargeType 计费类型1按月度固定费用2按面积计费 * param startDate 开始日期 * param endDate 结束日期 */ public static BigDecimal calculate(BigDecimal unitPrice, BigDecimal square, Integer chargeType, LocalDate startDate, LocalDate endDate) { // 计算租赁月数不足一个月的按一个月算 long days ChronoUnit.DAYS.between(startDate, endDate) 1; BigDecimal months BigDecimal.valueOf(days).divide(BigDecimal.valueOf(30), 2, RoundingMode.UP); BigDecimal oneMonthFee; if (chargeType 1) { oneMonthFee unitPrice; } else if (chargeType 2) { oneMonthFee unitPrice.multiply(square); } else { throw new BusinessException(不支持的计费类型); } return oneMonthFee.multiply(months).setScale(2, RoundingMode.HALF_UP); } }这里有个细节跨月租赁的场景天数计算要考虑清楚。比如2月1日到2月28日直接除以30再向上取整结果是1个月。但如果是1月31日到2月1日只有2天按这个算法会收一个月租金从业务上讲是合理的因为跨月最少也要按一个月起租。6.2 摊位档期冲突校验这是租赁系统最重要的并发控制点。我在前面提过提交租赁申请时要校验摊位状态。但状态字段只能保证“摊位当前有没有人租”不能保证“摊位在某个时间段内有没有人租”。比如一个摊位6月1日到期商户A在5月15日申请6月5日开始租这时候摊位状态还是租赁中按状态校验就会误判。所以档期校验一定要用时间段判断。租赁订单表里要能按座位和时间查重private void checkStallAvailable(Long stallId, LocalDate startDate, LocalDate endDate) { LambdaQueryWrapperRentOrder wrapper new LambdaQueryWrapper(); wrapper.eq(RentOrder::getStallId, stallId) .in(RentOrder::getStatus, Arrays.asList(1, 2)) .and(w - w .between(RentOrder::getStartDate, startDate, endDate) .or() .between(RentOrder::getEndDate, startDate, endDate) .or() .apply(start_date {0} AND end_date {1}, startDate, endDate) ); Long count rentOrderMapper.selectCount(wrapper); if (count 0) { throw new BusinessException(该摊档在所选时间范围内已被预订请更换时间或选择其他摊档); } }SQL判断逻辑是查所有状态为待支付或租赁中的订单只要它们的起止时间段和当前要申请的时间段有交集就认为冲突。这个交集判断用了三个条件覆盖所有重叠场景新时间段开始落在已有订单区间内、结束落在已有订单区间内、或者已有订单完全包含新时间段。6.3 退租与退款处理逻辑退租场景容易被忽略但它恰恰是运营方最关心的。退租分为两种到期自然终止和提前退租。到期自然终止就是定时任务扫描更新状态释放摊位不涉及金额计算。提前退租则要计算违约金。我的建议是违约金规则不要写死在代码里在系统配置表里维护方便运营调整。Transactional public void earlyTerminate(Long orderId, BigDecimal penaltyRate) { RentOrder order rentOrderMapper.selectById(orderId); if (order null || order.getStatus() ! 2) { throw new BusinessException(订单状态异常无法退租); } // 退租金 剩余天数对应租金 * 违约金比例 long remainingDays ChronoUnit.DAYS.between(LocalDate.now(), order.getEndDate()); BigDecimal remainingAmount order.getTotalAmount() .multiply(BigDecimal.valueOf(remainingDays)) .divide(BigDecimal.valueOf(360), 2, RoundingMode.HALF_UP); BigDecimal penalty remainingAmount.multiply(penaltyRate).setScale(2, RoundingMode.HALF_UP); // 更新订单状态为退租记录退款金额和违约金 order.setStatus(5); rentOrderMapper.updateById(order); // 释放摊位 Stall stall stallMapper.selectById(order.getStallId()); stall.setStatus(0); stallMapper.updateById(stall); // 写入退款记录 // ... }退租后商户的押金退还和违约金扣除需要生成一条退款单记录方便财务对账。这个逻辑在毕设论文里也可以作为“系统的复杂性体现”来写比单纯CRUD有亮点得多。7. 常见问题与排查技巧实录7.1 前后端联调跨域问题跨域是前后端分离项目联调遇到的第一个拦路虎。开发环境下的解决方案有两种一种是在后端写CORS配置类另一种是前端用Vite代理。我强烈推荐前端代理方案原因很简单——生产环境的跨域问题最终也会由Nginx代理解决开发环境的代理配置跟生产环境保持一致可以提前暴露问题。Vite的vite.config.js配置export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这样前端请求/api/stall/listVite开发服务器会自动转发到http://localhost:8080/api/stall/list浏览器里看起来是同源的就不会有跨域报错了。7.2 LocalDateTime序列化格式异常Spring Boot默认用Jackson序列化LocalDateTime返回给前端的是2025-06-01T10:30:00这种带T的格式不满足页面展示需求。解决方案是在配置里统一指定格式spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8这里有个凯过的坑date-format对java.util.Date生效但对LocalDateTime不一定生效。最稳妥的方式是在实体字段上加JsonFormat注解JsonFormat(pattern yyyy-MM-dd HH:mm:ss) private LocalDateTime createTime;或者写一个全局的Jackson配置类注册JavaTimeModule并设置格式。另外前端拿到时间字符串后用dayjs处理展示和计算比用原生Date省力很多。7.3 同一摊位被重复下单的并发问题前面提到过档期冲突校验但在高并发下会有问题。两个请求同时查到没有冲突然后同时插入订单就会产生重复订单。解决思路有几种按实际复杂度排序数据库唯一索引给订单表加(stall_id, start_date, end_date, status)的组合唯一索引但状态是变化的这个方案不够灵活。乐观锁在摊位表加version字段下单时读取摊位version然后UPDATE stall SET version version 1 WHERE id ? AND version ?更新影响行数为0说明被其他事务抢先了。分布式锁引入Redisson按摊位ID加锁但这类项目一般用不到除非真有并发压力。我推荐乐观锁方案代码量小理解起来也容易。MyBatis-Plus支持乐观锁插件只需两步。配置类里加Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; }实体类版本字段Version private Integer version;这样调用updateById的时候MP会自动带上版本条件更新成功后版本号自增。7.4 前端打包后接口404Vue项目npm run build后把dist目录扔到Nginx刷新页面就404。这个问题的根因是前端路由用的history模式Nginx需要配置try_files把路由请求指向index.htmllocation / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }后端接口的代理也要配好location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }注意proxy_pass的路径拼接规则location /api/加上proxy_pass http://127.0.0.1:8080不带末尾斜杠请求/api/stall/list会转发到后端也是/api/stall/list。如果proxy_pass带了末尾斜杠http://127.0.0.1:8080/则/api前缀会被去掉转发到/stall/list这取决于后端接口设计有没有统一前缀。最容易出问题的就是你配了前缀但后端没加对应前缀或者反过来。7.5 Spring Boot版本过高导致的依赖冲突开头提过Spring Boot版本问题这里展开说一下。如果你直接用Spring Boot 3.2.x MyBatis-Plus 3.5.3之前的版本启动时会报错因为MyBatis-Plus还没适配Spring Boot 3的包结构。我的建议是如果刚入门直接Spring Boot 2.7.18最后一个2.x版本稳定教程全所有依赖兼容性都验证过。如果一定用Spring Boot 3.xMyBatis-Plus要用3.5.5版本而且引入的包名要从mybatis-plus-boot-starter换成mybatis-plus-spring-boot3-starter这个细节特别容易踩。看热词里也有“springboot版本太高”这个搜索趋势说明不少人在这个问题上卡过。一般情况下除非项目有明确的新特性要求否则不要追最新版本稳定压倒一切。8. 项目管理与部署上线经验8.1 开发环境配置清单Java开发环境配置也是新手经常出问题的地方。我整理一份完整的开发环境配置清单组件版本建议JDKJDK 8 或 JDK 17Spring Boot 2.7.x支持到17Maven3.8.xNode.js18 LTSMySQL5.7 或 8.0IDEIntelliJ IDEA 2023VueVue 3.4Element Plus2.xJDK版本选择上Spring Boot 2.7.x最高支持Java 21但最稳妥的是JDK 8和JDK 17。如果你以后要升级Spring Boot 3JDK 17是必须的所以直接上JDK 17也没问题。8.2 项目部署流程完整部署流程分四步后端打包mvn clean package -DskipTests生成jar包。后端启动java -jar stall-system.jar --spring.profiles.activeprod用prod配置文件里的生产环境数据库。前端构建npm run build生成dist目录。Nginx配置把dist目录作为站点根目录配置API反向代理。如果你的服务器内存有限可以给JVM设置一下内存参数java -jar stall-system.jar -Xms256m -Xmx512m不用给太大一个管理系统256M到512M足够跑得很稳。MySQL单独部署的话1核2G的云服务器就能撑起一个小市场的管理系统成本很低。8.3 数据备份与安全建议上线以后数据库备份是重中之重。我建议写一个简单的Shell脚本每天凌晨用mysqldump备份保留最近7天的备份即可#!/bin/bash BACKUP_DIR/data/backup/mysql DATE$(date %Y%m%d) mysqldump -uroot -p123456 stall_db $BACKUP_DIR/stall_db_$DATE.sql # 删除7天前的备份 find $BACKUP_DIR -mtime 7 -exec rm -f {} \;配合crontab每天凌晨3点执行0 3 * * * /bin/bash /data/backup/mysql/backup.sh /data/backup/mysql/backup.log 21密码安全上前端页面登录一定要用HTTPS协议数据库密码不要明文写在配置文件里可以用环境变量注入。密码存储用BCrypt加密我在用户表字段设计那里给的password字段长度就是100因为BCrypt加密后的字符串是60位留点余量。8.4 系统上线后的经营数据分析系统跑一段时间后产生的订单数据可以给市场运营提供很多有价值的信息。我建议在统计模块里做三个基础报表第一个是区域出租率报表按area_code分组统计出租率可以看出哪个区最抢手哪个区需要做活动引流。第二个是月度租金收入报表按月统计实际收入跟市场整体经营目标对齐。第三个是摊位空置期分析针对每个摊位统计从上一单到期到下一单开始之间的间隔天数间隔越长的摊位说明吸引力越差需要调整租金或者改造。这三张报表在后台管理页面上可以用ECharts折线图或者柱状图展示数据源就是订单表加摊位表的聚合查询实现成本低但给管理者的决策支持价值很高。我在做这类系统的时候还有一个体会不要一开始就求大求全。农贸市场管理系统核心先做租赁闭环把摊位管理、订单管理、财务管理跑通经营分析这些锦上添花的功能放第二期。你把MP的CRUD、状态流转、事务控制、定时任务这些技术点吃透用它做任何类似的资源租赁系统——比如自习室座位、共享工位、设备租赁——业务流程都是相通的换套表结构就能复用一套代码思路。这也是这类项目的真正价值所在。
返回列表