现场干没干活全凭一张嘴?我用 Java 21 + Vue 3 撸了一套多租户履约调度平台,开源了!
项目地址:[https://gitee.com/wyabsdai/display_java.git ]
开源协议:MIT(自由商用、修改与二次分发)
一句话介绍:面向设备维保 / 安装交付 / 门店巡检这类「派工到现场」场景的履约调度平台(SaaS 多租户,Web + 移动端三端全栈),同时也是一套可直接复制开新项目的多租户管理后台脚手架。
项目截图
| 招牌截图:调度中心的候选人评分矩阵(分数 + 不可派原因) |
|---|
![]() |
| 数据看板 | 工单详情(时间线 + 5 页签) |
|---|---|
![]() | |
| 验收审核(SOP 项 vs 服务端水印件) | 结算对账(一键导 CSV) | 派单规则(权重 40/30/20/10) |
|---|---|---|
![]() | ||
| 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上 |
| SOP 模板与步骤编辑器 | 租户自助开通审批 | 租户管理(超管看全量 / 租户看本域) |
|---|
| 
|
一、起因:一个「出了活却拿不到证据」的真实场景
做过设备维保、门店陈列、装机交付、活动执行的朋友应该都懂这种局面:
- 工单在微信群里喊人:谁有空、谁离得近、谁会这个技能,全靠调度员脑子里那张表;
- 现场人员说"我去过了",拿不出到场证据——一张上个月拍的旧照片换个时间又能交一次;
- 交付物几十张照片 + 一段视频发在群里,回来要人工对着 SOP 一项项核对,漏一项没人知道;
- 客户说没服务过,翻聊天记录翻半天,没有一单能拿得出手的闭环凭证;
- 月底结算靠 Excel 拉透视表,返工扣多少、谁该发多少,财务和调度互相扯皮;
- 最要命的是:这套系统给 A 公司用,B 公司也想用,结果两家的工单躺在同一张表里,谁敢接单?
市面上这一类 FSM(Field Service Management)系统不少,但要么是大厂套件、按账号按年收费、二开要走流程;要么功能全但技术栈停在十年前,Excel 导入能撑住就不错。中小团队真正需要的是一套「拉下来就能改、改完就能上线、还能同时服务几个客户」的底子。
所以我做了这个 SailAdmin + Display 履约调度平台:底座是多租户管理后台脚手架,业务层是一个中等复杂度的完整履约闭环,Web 管调度,移动端管现场,两端契约对齐。
免费开源,MIT,商用不收费。
二、核心业务亮点:一单到底的履约闭环
整条链路围绕**「派得准、到得了、证得实、验收得快、结得清」**设计:
| 模块 | 实际解决的业务痛点 |
|---|---|
| 工单状态机 | 草稿 → 待接单 / 抢单中 → 已接单 → 执行中 → 待验收 → 已完成,含挂起、驳回、取消共 10 态;每个阶段落时间戳,超时可见。 |
| 四维智能派单 | 技能匹配 40 分 / 距离 30 分(5km 外归零)/ 负载 20 分(满载归零)/ 信誉 10 分。候选人得分和"为什么不能派他"一起落库,事后能复盘、能申诉。 |
| 三种发布模式 | 自动派(引擎选人)/ 指派(点名)/ 抢单池(先到先得)。抢单冲突走条件更新,抢输的一方收到 409「手慢了,该工单已被他人接单」,大厅自动刷新。 |
| 到场打卡风控 | 超出 500m 半径 或 识别到虚拟定位 → 直接 403 拒绝。而且被拒的记录照样落库,成为风控证据链的一环。 |
| 服务端防伪水印 | 交付照片由服务端把时间 / GPS / 工单号烧进像素(Hutool + Java2D),原件与水印件双 URL 存。相册里翻出来的旧图,水印对不上就是造假。 |
| SOP 逐项验收 | 标准项与水印件并排核对,可精确到单项驳回(rejected_keys),整改重传按 client_key 覆盖而不新增,全部清除后才回到待验收。 |
| 免审 + 强制抽检 | 信誉 ≥ 95 且本单零驳回 → 自动通过;但命中免审仍按 10% 抽检强制人审,auto_passed 标记可查。好人不是不用管,是少管。 |
| 结算对账 | 验收通过即生成结算凭证:奖励 − 扣减 = 实发,返工每次扣 5%;按账期/人员聚合,一键导出带 BOM 的 UTF-8 CSV,Excel 直开不乱码。 |
| 分级培训与准入 | 课程 → 学习心跳 → 服务端判分(题库根本不下发答案)→ 必修达标自动发证 → 证件 / 等级 / 信誉构成派单准入闸门,不够格直接硬拒。 |
| 站内信 + 铃铛 | 7 类事件(工单/派单/验收/风控/结算/培训/系统)统一投递,Web 顶栏未读角标实时可见;越权查别人消息被服务端拒绝。 |
| 数据看板 | 9 张 KPI 卡 + 4 张 ECharts 趋势图:在途量、一次做对率、返工率、平均履约时长、人员产出排名。 |
| 租户自助开通 | 官网免登录提交申请 → 平台审核 → 自动建租户 + 管理员账号 + 类型字典 + 欢迎消息;凭「受理编号 + 联系电话」双要素查进度,初始密码只在通过那一次响应里出现。 |
| 移动端离线补传 | 断网也能干活:提交失败自动进离线队列(图片转 dataURL 持久化),回网 sync 时服务端按已有 client_key 幂等跳过,不产生重复交付物。 |
三、技术栈:不追新,但每一项都有理由
- 后端:Java 21 + Spring Boot 4.0.2,打包
war(内嵌 Tomcat 也能直接java -jar)。 - 持久层:原生 MyBatis 4.0 + XML,刻意不用 MyBatis-Plus。理由很实际:SQL 全部可见可控,多租户拦截要在 SQL 执行前动刀,DBA 审计要能一眼看懂,不引入一套注解式查询语言。
- 分页 / SQL 解析:PageHelper 2.1.1 + JSqlParser 4.7(多租户改写靠它)。
- 数据库:PostgreSQL 17(不是 MySQL),独立业务库
display,端口15432。 - 缓存:Redis(Lettuce)——登录态辅助、失败计数与账号锁定、业务缓存。
- 鉴权:Spring Security + JWT(jjwt 0.13.0),
STATELESS;密码BCrypt(SM3(明文)),自定义Sm3CompatPasswordEncoder双匹配,所以 PC 明文登录和移动端摘要登录共存,老账号零迁移。 - 管理前端:Vue 3.5 + Vite 7 + Element Plus 2.11 + Pinia + ECharts 6,若依风格后端下发动态菜单,按钮级权限
display:order:dispatch式。 - 移动端:uni-app(Vue2,HBuilderX 形态),适配 H5 / 小程序 / Android-iOS,覆盖现场抢单、定位打卡、拍照交付、手写签字。
- 工具库:Hutool 5.8.41、fastjson2、BouncyCastle(SM3)。不引 POI / EasyExcel,导入导出统一走自研
CsvExporter。
规模(不含依赖目录):后端 230 个 Java 文件 / 约 22.2k 行、34 个 Controller(业务域 19 个)、Mapper XML 30 份;Web 89 个文件 / 约 11.6k 行;移动端业务页面 9 个;SQL 脚本 9 份;业务表 16 张。
四、核心架构设计揭秘
1. 多租户:4 个类,业务代码里一行 tenant_id 都不用写
SaaS 隔离三种方案的对比我都试算过:独立数据库(每租户一个库,连接数直接爆炸)、独立 Schema(要维护连接切换与 N 份迁移)、共享表 + tenant_id(一个库,逻辑隔离)。中小规模 SaaS 的最优解是第三种——不是妥协,是匹配体量。
关键在于把隔离做到 SQL 执行前,而不是指望每个开发"记得写条件":
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class TenantInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
String tenantId = TenantContextHolder.getTenantId();
// 无租户上下文(登录接口/定时任务/异步线程)或超管豁免时不改写
if (tenantId == null || tenantId.isBlank() || TenantContextHolder.isIgnore()) {
return invocation.proceed();
}
MetaObject metaObject = unwrapPluginChain((StatementHandler) invocation.getTarget());
String originalSql = (String) metaObject.getValue("delegate.boundSql.sql");
// 用 JSqlParser 给白名单表补上租户条件(INSERT 自动补列)
String newSql = TenantSqlParser.rewrite(originalSql, tenantId, tenantProperties);
metaObject.setValue("delegate.boundSql.sql", newSql);
return invocation.proceed();
}
}
接入新业务表只需两步,Mapper / Service 一行租户条件都不写:
CREATE TABLE biz_order (order_id varchar(32) PRIMARY KEY, ..., tenant_id varchar(32));
tenant:
enabled: true
column: tenant_id
# 表名追加进白名单即完成隔离(当前 17 张表)
tables: sys_user,demo_product,dp_order_types,dp_orders,dp_workers,dp_checkins,dp_upload_items,dp_audits,dp_settlements,dp_messages,dp_courses,dp_learning_records,dp_worker_certs,...
四个必须处理好的细节,踩到一个就出线上事故:
- 剥插件链:MyBatis 的插件是层层代理,PageHelper 也在里面。不逐层剥到真实的
RoutingStatementHandler,boundSql.sql根本拿不到; - 字面量而不是占位符:这里是改写已编译好的 SQL,再往
parameterMappings里塞一个参数就会错位。所以注入的是tenant_id = 'xxx'字面量,且只对白名单表生效; - WHERE 先整体加括号:原 SQL 有
where a or b时直接and tenant_id=会因优先级炸成跨租户泄露,必须包成where (a or b) and tenant_id=; - 解析失败降级不改写:JSqlParser 兜不住的花哨 SQL 会原样放行 + 打 error 日志,而不是让整个接口 500。宁可漏隔离(再由代码显式兜),不可让业务全停。
另外两类表故意不入白名单:sys_tenant 是租户主表,dp_tenant_applications 天生跨租户(平台侧审批),它们的可见范围由代码显式控制。
2. 定时任务怎么跨租户扫,又不把数据写错家
@Scheduled 线程没有登录上下文,这正好是"天然全量扫描"。但逐单处理时必须把上下文切到该单所属租户,否则派单轨迹、信誉分会落到错误的租户名下:
@Scheduled(cron = "0 */2 * * * ?") // 每 2 分钟扫一次响应超时(阈值取自启用的派单规则,默认 10 分钟)
public void scan() {
try {
int handled = dispatchEngine.timeoutScan(); // 扫描不被改写 → 跨租户全量
if (handled > 0) log.info("响应超时扫描完成,本轮处理 {} 个工单", handled);
} finally {
TenantContextHolder.clear(); // Tomcat/线程池会复用线程,不清就是串号事故
}
}
同一套上下文切换手法也用在「租户自助开通审批」上——审核通过要同时写平台表和租户表,只能在一次请求里分段 setIgnore(true) / setTenantId(新租户),并在 finally 恢复。
3. 一次履约的完整数据流(含风控分支)
Web 建单 / 导入
│
发布(auto / assign / grab)
│
┌────────┴────────┐
▼ ▼
DispatchEngine 抢单池
四维评分 40/30/20/10 条件更新 where status='grabbing'
│ │
│ 影响行数=0 → 409「手慢了」(不靠分布式锁)
└────────┬────────┘
▼
已接单 accepted
│
到场打卡 ──┬─ 超出 500m ─────→ 403 拒绝(记录照样落库,风控留痕不回滚)
├─ 虚拟定位 ───────→ 403 拒绝(同上)
└─ 通过 → processing
│
按 SOP 提交交付物(multipart,client_key 幂等)
│
服务端烧水印:时间 / GPS / 工单号
▼
待验收 reviewing
│
┌───────────────┼───────────────┐
▼ ▼ ▼
信誉≥95 且 命中免审但 信誉不足 /
本单零驳回 中 10% 抽检 有驳回
│ │ │
▼ └──────┬────────┘
免审直通 done ▼
│ 人工审核 ── 整单通过 → done
│ └──── 单项驳回 → rejected
│ (整改重传按 client_key 覆盖,
│ rejected_keys 清空后回 reviewing)
▼
生成结算凭证(奖励 − 扣减 = 实发,返工每次扣 5%)
▼
站内信通知 + 信誉变动 + 看板聚合
4. 灰度发布用单号哈希,不用随机数
SOP 模板支持 draft → gray → online → archived。灰度命中判定是:
boolean hitGray = Math.floorMod(orderCode.hashCode(), 100) < grayPercent;
用单号哈希而不是 Random:同一个工单不管重放、重试、还是异步补偿,命中的都是同一个模板版本。用随机数会导致一张工单前半段用 V1 步骤、后半段用 V2 步骤,验收时对不上标准。
5. 导入导出坚持 CSV,坚决不引 POI
中小后台 99% 的"导出 Excel"需求,本质是要一个能被 Excel 双击打开的文件。那就别为它扛一个 POI(内存和依赖都上去了):
// common/utils/CsvExporter(唯一下沉点)
CsvExporter.write(response, "结算对账单", HEADERS, rows); // UTF-8 BOM + filename/filename*=UTF-8'' 双写 + 行数兜底
- 写 UTF-8 BOM,Excel 直开不乱码;中文文件名
filename与filename*=UTF-8''双写兼容老浏览器; - 导出上限 20000 行、导入上限 5000 行,超限直接拒绝,别把 JVM 打穿;
- 导入用
@Transactional保证整批原子:引用字段(部门 / 角色)在文件里写名称,名称对不上就报第 N 行:部门「XXX」不存在,整批回滚,绝不做"成功一半"。
6. 一个按钮要真正生效,三处必须一致(并且机器对账)
| 处 | 位置 | 作用 |
|---|---|---|
| 登记 | sys_menu.perms | 决定角色能否勾上、菜单能否下发 |
| 真拦 | Controller @PreAuthorize("@ss.hasPermi(...)") | 唯一的安全边界 |
| 入口 | Vue v-hasPermi="['...']" | 只是隐藏按钮,不能当安全 |
写了个 work/perms_gap.js 把三方做差集:后端在校验但菜单没登记的、菜单登记了但后端没校验的、前端用了但后端没有的——要补的三类差集已全部归零;剩下的 36 项是 list/query/view/preview 这类页面级权限点,由动态路由 + 后端把关,本来就不需要按钮指令(属预期内)。这套对账在任何若依风格项目里都能直接复用。
7. 防爆破:连续 5 次锁 10 分钟
SysLoginLockServiceImpl 用 Redis login:fail: 计数 + login:lock: 标记,锁定期内在认证之前直接拒绝(不给你继续试的机会),只对 BadCredentialsException 计数(账号不存在、已锁定等不累计,避免刷计数与误锁),超管可在「登录日志」页一键解锁。
这里有个能救半天排查时间的坑:RedisTemplate<String,Object> 配了 GenericJackson2JsonRedisSerializer,值是 JSON 化的,INCR 直接报 value is not an integer——计数器必须注入 StringRedisTemplate。
五、快速上手:本地 10 分钟跑起来
├── src/ 后端(Spring Boot 4 + 原生 MyBatis + PostgreSQL)
├── vue3-sail/ 管理端前端(Vue3 + Element Plus)
├── app/ 移动端(uni-app,HBuilderX 形态)
├── sql/ 初始化与业务脚本(DDL + 字典 + 菜单权限 + 演示数据)
├── tools/DbOps.java 免装 psql 的数据库运维小工具(probe/createdb/tables/run/exec)
├── work/ 端到端冒烟脚本与验证记录(可直接当回归用)
├── work1/ 历次架构决策、改造与踩坑记录(Markdown 索引)
└── AGENTS.md 给 AI 编码助手的项目说明书(新增功能的标准落点)
1. 环境
JDK 21+ / Maven 3.9+ / PostgreSQL 17(127.0.0.1:15432,postgres/123456)/ Redis(127.0.0.1:6379,密码 123456)/ Node 18+。
2. 先配置数据源
application-dev.yaml 不入库(含账号口令),从模板复制一份,把 URL 的库名改成 display、改账号密码即可——后端和仓内的 tools/DbOps.java 都读这一个文件:
cp src/main/resources/application-dev.yaml.example src/main/resources/application-dev.yaml
# 改 datasource 的 url(库名 display)、username、password
3. 初始化数据库(脚本有依赖,按序执行)
# 用仓内工具,不用装 psql($host:port 与库名均取自上面那份 application-dev.yaml)
$env:JAVA_HOME="C:\Program Files\Java\jdk-21" # 本机用 JDK25 编译亦可,产物是 --release 21
$pgjar="$env:USERPROFILE\.m2\repository\org\postgresql\postgresql\42.7.9\postgresql-42.7.9.jar"
& "$env:JAVA_HOME\bin\java.exe" -cp $pgjar tools/DbOps.java createdb # 建 display 库
& "$env:JAVA_HOME\bin\java.exe" -cp $pgjar tools/DbOps.java run sql/wy.sql # 系统表 + 基础种子
& "$env:JAVA_HOME\bin\java.exe" -cp $pgjar tools/DbOps.java run sql/base_init.sql # 租户表 + 租户/公告菜单
& "$env:JAVA_HOME\bin\java.exe" -cp $pgjar tools/DbOps.java run sql/perms_supplement.sql # 补齐参数管理四个按钮(幂等)
& "$env:JAVA_HOME\bin\java.exe" -cp $pgjar tools/DbOps.java run sql/display_module.sql # P0:11 张 dp_ 表 + 3 演示账号
& "$env:JAVA_HOME\bin\java.exe" -cp $pgjar tools/DbOps.java run sql/display_p1.sql # P1:5 张新表 + 菜单/字典(可重复执行)
需要部门管理再补
sql/display_sys_dept.sql(须在display_module.sql之后);演示前可用sql/display_p1_clean_smoke.sql把脏数据拉回可展示态。
4. 启动后端
$env:JAVA_HOME="C:\Program Files\Java\jdk-21"; $env:Path="$env:JAVA_HOME\bin;$env:Path"
.\mvnw.cmd spring-boot:run # 端口 8849,context-path /
5. 启动管理后台
cd vue3-sail
npm install
npm run dev # http://localhost:8990 ,/api 代理 → 8849
移动端:HBuilderX 导入 app/ → 运行到浏览器(H5 8090,代理已配 /api → 8849),建议把窗口调到 390×844。
6. 预置演示账号(覆盖四种视角)
| 账号 | 口令 | 角色 | 能做什么 |
|---|---|---|---|
admin | admin123 | 超级管理员 *:*:* | 系统管理 + 可见全量租户 + 账号解锁 |
demo | Disp@2026 | dispatcher(27 项 display:*) | Web 派单 / 改派 / 审核 / 看板 / 结算 |
worker | Disp@2026 | executor,信誉 96 ≥ 免审线 | 移动端,走免审直通 |
worker2 | Disp@2026 | executor,信誉 82 | 移动端,走人工审核(可看驳回与整改) |
用
worker和worker2各提交一单,就能亲眼看到免审直通和人审驳回两条分支——这是演示这个项目最快的方式。
六、它不是"能跑",是被脚本验过 205 项
我没法接受"README 很美、跑起来报错"的开源项目,所以每个功能都是真实 HTTP 调用断言出来的,脚本全部留在 work/,可直接当回归用(不需要 Postman):
| 脚本 | 覆盖 |
|---|---|
smoke1.ps1 | Web/App 双端登录、菜单与路由下发、字典、模板/人员/规则读取、建单 + 发布、移动端只读接口 |
smoke2.ps1 | 抢单 409、打卡 403(超距 / 虚拟定位)、看板聚合 |
smoke3.js | 全链路:建单 → 发布 → 抢/接 → 风控打卡 → 交付(multipart + 中文 + 水印)→ 免审 / 人审 → 单项驳回 → 整改重传 → 通过 → 结算 |
smoke_p1.js | P1 六段 80 项:站内信、结算 CSV、培训判分与准入、租户自助开通、模板灰度、越权兜底 |
smoke_all_modules.js | system / monitor / demo 全模块流程,16 节 205 项全绿(含导出响应头与 BOM、列数行数、整批回滚、锁定 → 解锁) |
perms_gap.js | 菜单 / @PreAuthorize / v-hasPermi 三方权限差集对账 |
TenantSqlParserTest.java | 租户 SQL 改写的 14 个 JUnit 单测:无 WHERE、OR 优先级包裹、JOIN 按别名各自过滤、子查询、UPDATE / DELETE、INSERT 自动补列(不重复补 / 多行批量补)、非白名单表不动、空租户与开关关闭不改写、解析失败回落原 SQL、带引号与 schema 前缀的表名归一 |
关键断言举例(空库起跑):
code=409 手慢了,该工单已被他人接单
code=403 检测到虚拟定位,打卡被拒绝 ← 3 条打卡记录(含 2 条被拒)全部留库作为风控证据
张三提交 → status=done, auto_passed=1 ← 免审直通
李四提交 → reviewing → 单项驳回 → rejected, rejectCount=1, rectifyDeadline=+120min → 重传 → done
派单候选:张三 87.44(技40 + 距29.74 + 载8 + 信9.7);李四满载 → 给出不可派原因
信誉随履约结果变动:96→97 / 82→78
导出:Content-Type=text/csv;charset=UTF-8,BOM=efbbbf,13 列表头 + 数据行
七、踩过的坑(可以帮你省几小时)
| 现象 | 根因与做法 |
|---|---|
Redis INCR 报 value is not an integer | JSON 序列化器把数字写成 JSON。计数器必须用 StringRedisTemplate |
| 刷新页面后点「首页」必 404 | vue-router 的 addRoute 遇同名先整条移除旧路由。动态菜单的 path 不能和 constantRoutes 的 Dashboard/Data/AuthRole/Login 撞名 |
res.data.map is not a function | 统一响应体对空集合不输出 data 键,一律 `res.data |
| PG 报「字段 “xxx” 不存在」/ 排序失效 | PG 带引号别名大小写敏感,裸写进 ORDER BY 会被折叠成小写;要在 ORDER BY 里重复表达式而非别名 |
时间列显示成 2026-11-01T05:36:04.534Z,移动端少 8 小时 | 后端回的是带时区的 ISO 串。Web 过 parseTime();移动端先转 Date 再按本地时区格式化,不要截字符串 |
| 登录接口 500「Unable to connect to Redis」 | 排查顺序反了就会查半天。先看 6379 在不在,再看 8849,别从前端报错反推后端 |
带 & < 的查询条件匹配不上 | 全局 XSS 绑定器对所有 String 参数做了 HtmlUtil.escape。长期方案是加豁免清单 |
| Maven 有 stderr 警告但构建其实成功 | PowerShell 把 stderr 当错误显示,以 $LASTEXITCODE 为准 |
八、为什么开源?
现场服务履约是个特别"朴素"的领域:不性感、不追新,但几乎所有做设备、做门店、做装维、做活动执行的团队都要面对。市面上的选择要么贵得离谱,要么老旧得没法二次开发。
我把整套东西按 MIT 完整开源,是希望它能被三种人直接用:
- 要做管理后台的团队:把
module/display删掉,module/demo就是你新模块的模板——8 个落点、命名规范、PG 方言坑、多租户登记方式全写在AGENTS.md里。先让 AI 读它,再让它写业务,可以一次性产出可编译可运行的代码,这是我踩了十几轮之后总结出的最省事路径; - 要做 SaaS 多租户的团队:4 个类、511 行的 MyBatis 拦截器方案,配置化白名单,业务代码零侵入,含单测与跨租户审批的上下文切换范式;
- 需要一份"从建表到冒烟全绿"完整参照的人:
work1/里有 8 篇技术记录 + 一张索引表——多租户方案对比、12 个框架级缺陷修复、system 模块功能保留核查、P0/P1 业务落地与验证、三方权限差集对账。每一个决策为什么这么定、踩了什么坑,都留了字。
诚实边界(用之前请知悉)
- 定位是开发 / 演示形态:上线前必须换
token.secret/ RSA 密钥 / 演示口令,配置外置,改库账号端口,移除spring-boot-devtools; - 登录图形验证码未接线(若依遗留字段还在,前后端都没校验),公网暴露登录口请自行补齐,与现有失败锁定形成双保险;
- 角色管理里的「数据权限范围」是保留的界面与字段,后端
@DataScope仍是桩(本项目靠多租户 + Service 层归属校验控范围),需要部门级数据权限的请自行实现切面; tenant.enabled=false即全量可见,单租户部署确认后再关;app/是 HBuilderX 形态目录(manifest.json在根、无src/),CLI 打包需先做目录改造。
项目地址与交流
- Gitee 源码仓库:[ https://gitee.com/wyabsdai/display_java.git ]
- 开源协议:MIT
如果你觉得这个项目对你的业务或技术进阶有帮助,欢迎去 Gitee 点个 ⭐ Star 支持一下!有功能建议、二开需求或 Bug,欢迎提 Issue / PR。
想深入某一块可以直接看仓内文档:
AGENTS.md(新增功能的标准写法)·work1/多租户实现方案与记录.md(隔离方案怎么选)·work1/Display履约调度平台实现记录.md(业务怎么落地)·docs/images/README.md(48 张功能截图清单)。
该项目是一个面向设备维保、门店巡检等现场服务场景的SaaS化履约调度平台,基于Java 21与Vue 3构建,支持Web管理端与uni-app移动端三端协同。核心特性包括零侵入多租户隔离(MyBatis拦截器实现)、跨租户定时任务上下文切换、单号哈希灰度发布、CSV轻量导入导出、按钮级权限机器对账及防爆破登录保护。技术栈聚焦可控性与审计友好,采用PostgreSQL、Redis、Spring Security+JWT、PageHelper+JSqlParser等组件。



388

被折叠的 条评论
为什么被折叠?



