工程机械人机一体考勤绩效系统:从设备台账到工资条的全链路设计

设备管不住、工时算不清、工资对不拢——这三个问题叠加,利润流失往往不是单点故障

引言:一张工资条背后的系统性难题

月初发薪日前,某机械租赁公司HR老张对着Excel加班到凌晨两点。40台设备、80名机手,考勤记录散落在纸质签到表、微信报岗和班长手写笔记里。设备保养记录在另一个Excel里,人机对应关系要靠脑记。最终出来的工资条,总有人质疑工时不对、绩效少算、奖惩不明。

这个场景在工程机械、施工总包、环卫市政等行业并非个例。

传统“考勤打卡”工具解决不了这个问题。设备台账乱、保养跟不上、工时算不清、工资条对不拢——这四件事叠加起来,流失的不是一个人一天的工时,而是整个项目的利润底数。

本文分享一个在工程机械现场生长出来的考勤与绩效管理平台——晋辉智机通(项目代号xlhMoreApp)的设计思路。它不是打卡软件加个壳,而是围绕“设备管理”与“工资绩效”两大核心,把考勤作为数据底座,形成人、机、现场一条线的闭环系统。


第一部分:架构设计的起点——四大能力板块的优先级排序

在动手写第一行代码之前,团队花了两周跑工地、蹲租赁公司、跟班组长聊天。发现真正被高频使用的功能,排序完全不同于“打卡软件”的常规设计:

优先级

板块

核心解决的问题

★★★

设备管理

机械台账、机手绑定、保养计划——人机现场一条线

★★★

工资绩效

考勤汇总、工资条、积分规则——算得清、奖得明

★★

考勤

GPS打卡、拍照留痕、异常申诉——真实可追溯的底座

员工培养

技能提升与积分挂钩——在干活过程中培养

这个排序决定了产品设计的底层逻辑:考勤是手段,设备和绩效才是客户真正愿意付费的价值锚点


第二部分:多租户架构——一套代码,两种交付

2.1 需求来源:两种客户,一种代码库

随着客户增多,需求分叉出现了:

  • 中小型租赁公司:希望快速上线,不想自建机房,按年付费即可

  • 大型国企/集团:数据不能出内网,要求私有化部署,数据完全自主可控

常规做法是维护两套代码——一套SaaS版,一套私有化版。但两套代码意味着双倍维护成本、功能不同步、bug修复要两边改。

解决方案:在数据库层做隔离,同一个Spring Boot工程通过配置切换运行模式:

# application.yml 核心配置 tenant: mode: shared # shared=托管共享库行级隔离 | standalone=私有化独立库
  • shared模式:共享MySQL数据库,MyBatis拦截器自动向SQL追加tenant_id条件,实现行级隔离

  • standalone模式:独立数据库单企业,关闭SQL改写,数据完全在客户环境内

2.2 数据隔离的核心设计

MyBatis拦截器 + JSqlParser 自动改写SQL,业务代码零侵入:

@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class TenantInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 获取当前租户ID(从ThreadLocal或请求头X-Tenant-Id) String tenantId = TenantContext.getCurrentTenantId(); if (StringUtils.isNotBlank(tenantId) && !"platform".equals(tenantId)) { // 使用JSqlParser改写SQL,自动追加 AND tenant_id = ? // 仅对业务表生效,系统表跳过 } return invocation.proceed(); } }

关键设计决策:不依赖多数据源,而是单数据源+行级隔离。原因是:

  • 托管模式下动态创建数据库不现实(需要DDL权限)

  • 行级隔离让新增租户零成本,无需重复部署整套系统

  • 运维后台开通企业 → 生成tenant_id → 立即可用,全程秒级


第三部分:登录链路与租户识别

多企业版与单企业版最大的体验差异在登录链路。用户不是直接输入账号密码,而是:

  1. 输入企业码(如testzhangsan-lease

  2. 调用GET /Tenants/resolve?code=xxx获取企业ID、API地址、品牌信息

  3. 再用企业码+账号密码登录

  4. 后续请求通过JWT Token中的tenantId或请求头X-Tenant-Id传递租户上下文

// 登录接口示例 @PostMapping("/login") public Result login(@RequestBody LoginDTO dto) { // 1. 根据企业码解析租户 Tenant tenant = tenantService.resolveByCode(dto.getTenantCode()); if (tenant == null) { return Result.error("企业码不存在,请联系管理员"); } // 2. 校验账号密码(带租户隔离) User user = userService.login(dto.getUsername(), dto.getPassword(), tenant.getId()); // 3. 生成JWT,包含tenantId String token = JWT.create() .withClaim("userId", user.getId()) .withClaim("tenantId", tenant.getId()) .withClaim("roleId", user.getRoleId()) .sign(Algorithm.HMAC256(tokenSecret)); return Result.ok(token); }

运维管理员使用独立标识platform登录,roleId=100,仅用于企业开通与续期管理,不访问业务数据。


第四部分:企业品牌与服务续期的细节设计

4.1 品牌展示的分区域策略

多租户系统面临一个矛盾:既要让企业看到自己的品牌,又要保持平台统一的认知。我们的策略是分区域展示

区域

展示规则

App登录页/注册页

固定默认Logo +「晋辉智机通」,不随企业配置变化

管理后台登录页/左侧侧栏

固定默认Logo +「晋辉智机通」

管理后台登录后顶栏

可配置系统名、Logo、品牌备注;14种推荐背景色+自定义色值

App登录后首页

展示企业名称;若配置了企业Logo则一并展示

这个策略平衡了平台统一认知企业个性化两个需求。登录前用户看到的是平台品牌(知道自己在用哪个系统),登录后看到的是企业品牌(归属感)。

4.2 服务续期提醒

托管部署模式下,企业服务到期前30天:

  • 移动端自动弹窗提醒

  • 管理后台顶部显示黄色警示条

  • 到期后自动拦截登录,引导联系晋辉续费

私有化部署客户不受此约束,由客户自行运维。


第五部分:前后端工程化实践

5.1 API地址的统一管理

多环境部署容易出现的坑:前端配置了多个API地址,打包时漏改一个就导致生产环境请求失败。

管理后台utils/apiBase.js统一从环境变量读取:

// .env.production VUE_APP_BASE_URL = 'https://jinhuitech.top/seaHouseApiMore/' // apiBase.js export function getApiBaseUrl() { return process.env.VUE_APP_BASE_URL || '/seaHouseApiMore/'; }

移动端util/request/index.js集中配置:

// 统一API根地址,所有请求通过此入口 const API_BASE_URL = 'https://jinhuitech.top/seaHouseApiMore/'; // 图片URL规范化 export function resolveImageUrl(url) { if (!url) return ''; if (url.startsWith('http://') || url.startsWith('https://')) return url; return API_BASE_URL + 'file/download?url=' + encodeURIComponent(url); }

5.2 Nginx反向代理配置

# API反代 location /seaHouseApiMore/ { rewrite ^/seaHouseApiMore/(.*) /$1 break; proxy_pass http://127.0.0.1:11070; proxy_set_header X-Forwarded-Prefix /seaHouseApiMore; proxy_set_header Host $host; } # 管理后台静态资源 location /seahouseAdminMore/ { alias /etc/www/website/seahouseAdminMore/; try_files $uri $uri/ /seahouseAdminMore/index.html; }

5.3 打包与部署

# 后端打包 cd move-apiMorePub mvn clean package -DskipTests # 输出 target/xlhMoreApi.jar # 生产启动(SaaS) java -jar target/xlhMoreApi.jar --spring.profiles.active=prod # 私有化启动 java -jar target/xlhMoreApi.jar --spring.profiles.active=prodprivate

环境变量通过操作系统注入,不写入配置文件:

export TOKEN_SECRET='至少32位随机字符串' export DB_URL='jdbc:mysql://host:3306/sea_house_more?...' export DB_USERNAME='...' export DB_PASSWORD='...' export OSS_ACCESS_KEY_ID='AK' export OSS_ACCESS_KEY_SECRET='SK'

第六部分:关键业务功能实现

6.1 GPS打卡与拍照留痕

移动端打卡时采集GPS经纬度,与后台配置的打卡地点比对,计算距离是否在允许范围内:

// 打卡校验逻辑 public boolean checkLocation(BigDecimal lat, BigDecimal lng, LocationConfig config) { double distance = GeoUtils.distance( lat.doubleValue(), lng.doubleValue(), config.getLatitude(), config.getLongitude() ); return distance <= config.getAllowedRadius(); // 默认300米 }

拍照留痕:上传的图片存储到OSS,返回URL,后续打卡记录与图片关联,确保考勤可审计。

6.2 考勤汇总 → 工资条

考勤数据按自然月汇总,计算OT(加班工时)、中直(中班/直落工时):

-- 考勤汇总核心SQL(简化) SELECT user_id, SUM(CASEWHENtype = 'normal'THENhoursELSE0END) as normal_hours, SUM(CASEWHENtype = 'ot'THENhoursELSE0END) as ot_hours, SUM(CASEWHENtype = 'midnight'THENhoursELSE0END) as midnight_hours FROM attendance_records WHERE tenant_id = #{tenantId} ANDmonth = #{month} GROUPBY user_id;

汇总结果 → 工资条 → 机手App端可查。

6.3 积分规则与抽奖

积分是绩效激励的重要载体。规则可配置:

-- 积分规则表 CREATE TABLE point_rule ( id BIGINT PRIMARY KEY, tenant_id VARCHAR(32), rule_name VARCHAR(100), points INT, -- 每次奖励积分 max_times INT, -- 每人每月最多次数 status TINYINT -- 启用/停用 );

抽奖功能:每N积分抽一次,奖品概率可配,支持增删改,后台自动校验概率总和是否为100%。

// 抽奖逻辑 public Prize drawLottery(Long userId) { // 1. 扣减用户积分 userService.deductPoints(userId, costPoints); // 2. 按概率随机抽取奖品 List<Prize> prizes = prizeService.getEnabledPrizes(); double rand = Math.random(); double cumulative = 0; for (Prize prize : prizes) { cumulative += prize.getProbability(); if (rand <= cumulative) { return prize; } } returnnull; // 实际应兜底返回未中奖 }

第七部分:可扩展的场景延伸

虽然产品起源于工程机械现场,但能力和架构决定了它可以延伸到更多场景:

行业

核心诉求

使用方式

环卫/市政

片区巡检、车辆设备协同

GPS打卡+任务派单+保养巡检

物业/保洁

多小区巡更、保洁任务留痕

特殊任务+打卡留痕+班长审核

仓储/物流

班次考勤、计件任务

移动端打卡+工时汇总

安装维修

工单派发、上门打卡、拍照验收

工单载体+定位打卡+积分激励


第八部分:架构决策复盘

回顾整个设计和开发过程,有几个关键决策值得复盘:

8.1 为什么不用多数据源做租户隔离?

多数据源方案需要为每个租户创建独立数据库,托管模式下不可行(动态创建数据库需要DDL权限,且数据迁移成本高)。行级隔离+SQL改写方案在shared模式下更轻量,新增租户只需insert一条记录。

8.2 为什么JWT不包含租户ID?

初始版本JWT中包含了tenantId,但遇到租户切换的场景(如运维管理员platform需要跨租户操作)。最终方案是JWT只存用户身份,租户上下文通过请求头X-Tenant-Id传递,更灵活。

8.3 私有化部署的企业码怎么处理?

私有化部署虽然只有一家企业,但仍然保留企业码登录链路。这样同一套App可以同时服务于SaaS和私有化客户——私有化客户只需将App的API地址指向自己的服务器,企业码由客户自行分配。

# 私有化部署配置 tenant.mode=standalone tenant.default-tenant-code=your-company app.public-api-base-url=https://your-domain.com/seaHouseApiMore/

结语

从一个工地的Excel工资表,到覆盖设备台账、考勤留痕、绩效核算、积分激励的全链路系统,这个平台的设计始终围绕三个词展开:算得清、奖得明、管得住

技术层面的多租户隔离、SQL改写、JWT鉴权、OSS存储都是手段,真正的价值是让一线机手能实时查工资单、让班组长能快速处理异常、让总部能一张报表看清人机现场。

如果你正在管理分散的工程机械机手、环卫巡检员、物业外勤或仓储班组,这个系统已经准备好了从企业码登录 → 三端实时同步 → 数据隔离 → 部署交付的完整链路。

托管省心,私有化安心。有需要联系晋辉科技,或找你司系统管理员获取企业码。

项目体验:

    租户后台:https://jinhuitech.top/seahouseAdminMore/#/login租户 test:admin1234 / newocean123456租户 test2:13222222222 / 123456移动端:test租户:wuyujie / 123456 test2租户:chenjie / 123456安卓apk下载测试地址: https://www.pgyer.com/jinhuizhijitong-android