Files
medical-mall/docs/AUTH_CHECKIN_API_GUIDE.md
2026-06-10 20:20:47 +08:00

16 KiB
Raw Blame History

居家服务 - Auth 登录与距离预校验接口联调文档

文档信息

项目 说明
文档版本 v1.0
创建日期 2026-06-09
后端地址 http://localhost:4001
Supabase 地址 http://119.1496.131.237:9126
RPC 函数 rpc_homecare_checkin_precheck()

一、邮箱登录接口

1.1 接口概述

用户通过邮箱和密码登录系统,后端验证成功后返回 JWT Token 和用户信息。

1.2 请求信息

POST /auth/email-login
Content-Type: application/json

1.3 请求参数

参数名 类型 必填 说明
email string 用户邮箱地址
password string 用户密码

请求示例:

{
  "email": "dispatcher@example.com",
  "password": "password123"
}

1.4 响应信息

成功响应200

{
  "code": "OK",
  "msg": "success",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "user": {
      "id": "ad0cd0e6-fe95-4946-b189-536dc18cf9e5",
      "email": "dispatcher@example.com",
      "role": "HOMECARE_DISPATCHER",
      "full_name": "管理员",
      "org_id": "00000000-0000-0000-0000-000000000000",
      "created_at": "2026-06-01T00:00:00.000Z"
    }
  },
  "traceId": "abc123-def456-ghi789"
}

字段说明:

字段 类型 说明
token string JWT Token后续请求需携带此 Token
user.id string 用户唯一标识UUID
user.email string 用户邮箱
user.role string 用户角色:HOMECARE_DISPATCHER(派单员)/ HOMECARE_WORKER(居家服务员)
user.full_name string 用户姓名
user.org_id string 所属组织 ID

失败响应

邮箱或密码错误401

{
  "code": "UNAUTHORIZED",
  "msg": "Invalid email or password",
  "data": null,
  "traceId": "abc123-def456-ghi789"
}

用户不存在404

{
  "code": "NOT_FOUND",
  "msg": "User not found",
  "data": null,
  "traceId": "abc123-def456-ghi789"
}

1.5 前端调用示例

import axios from 'axios';

const API_BASE = 'http://localhost:4001';

/**
 * 邮箱登录
 * @param {string} email - 邮箱地址
 * @param {string} password - 密码
 * @returns {Promise<Object>} 登录结果
 */
async function login(email, password) {
  try {
    const response = await axios.post(`${API_BASE}/auth/email-login`, {
      email,
      password
    });
    
    const { token, user } = response.data.data;
    
    // 保存 Token 到本地存储
    localStorage.setItem('auth_token', token);
    localStorage.setItem('user_info', JSON.stringify(user));
    
    // 设置 axios 默认 Header
    axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
    
    return { success: true, user };
  } catch (error) {
    if (error.response) {
      console.error('登录失败:', error.response.data.msg);
      return { success: false, message: error.response.data.msg };
    }
    return { success: false, message: '网络错误' };
  }
}

// 使用示例
const result = await login('dispatcher@example.com', 'password123');
if (result.success) {
  console.log('登录成功,用户信息:', result.user);
}

1.6 测试账号

角色 邮箱 密码 说明
派单员 dispatcher@example.com password123 具有派单权限
居家服务员 worker@example.com password123 具有接单/签到权限

二、距离预校验接口

2.1 接口概述

居家服务员到达服务地点前,调用此接口校验当前位置与服务地点的距离是否在允许范围内。

2.2 接口信息

POST /homecare/checkin/precheck
Content-Type: application/json
Authorization: Bearer {token}

2.3 请求参数

参数名 类型 必填 说明
workOrderId string 工单 IDUUID
latitude number 当前纬度GCJ02 坐标系)
longitude number 当前经度GCJ02 坐标系)
coordinateType string 坐标系类型,默认 gcj02
accuracy number 定位精度(米)
reportedAt string 报告时间ISO 8601 格式)

请求示例:

{
  "workOrderId": "a6450755-add4-4bdf-839f-165459ddff5d",
  "latitude": 39.9042,
  "longitude": 116.4074,
  "coordinateType": "gcj02",
  "accuracy": 10,
  "reportedAt": "2026-06-09T10:30:00.000Z"
}

2.4 响应信息

校验通过200

{
  "code": "OK",
  "msg": "success",
  "data": {
    "distanceMeters": 15.5,
    "allowedRadiusMeters": 50,
    "canCheckin": true,
    "reasonCode": "OK",
    "workerLocationAccepted": true,
    "serviceLocationReady": true
  },
  "traceId": "abc123-def456-ghi789"
}

字段说明:

字段 类型 说明
distanceMeters number 当前位置与服务地点的距离(米)
allowedRadiusMeters number 允许的签到半径(默认 50 米)
canCheckin boolean 是否允许签到
reasonCode string 原因代码:OK(通过)/ OUT_OF_RADIUS(超出范围)/ SERVICE_LOCATION_MISSING(服务地点缺失)
workerLocationAccepted boolean 人员位置是否已记录
serviceLocationReady boolean 服务地点是否已配置

校验失败

超出签到半径400

{
  "code": "BAD_REQUEST",
  "msg": "Checkin rejected",
  "data": {
    "distanceMeters": 150.0,
    "allowedRadiusMeters": 50,
    "canCheckin": false,
    "reasonCode": "OUT_OF_RADIUS",
    "workerLocationAccepted": true,
    "serviceLocationReady": true
  },
  "traceId": "abc123-def456-ghi789"
}

服务地点未配置400

{
  "code": "BAD_REQUEST",
  "msg": "Checkin rejected",
  "data": {
    "distanceMeters": null,
    "allowedRadiusMeters": 0,
    "canCheckin": false,
    "reasonCode": "SERVICE_LOCATION_MISSING",
    "workerLocationAccepted": false,
    "serviceLocationReady": false
  },
  "traceId": "abc123-def456-ghi789"
}

工单未分配400

{
  "code": "BAD_REQUEST",
  "msg": "Checkin rejected",
  "data": {
    "distanceMeters": null,
    "allowedRadiusMeters": 0,
    "canCheckin": false,
    "reasonCode": "WORK_ORDER_NOT_ASSIGNABLE",
    "workerLocationAccepted": false,
    "serviceLocationReady": false
  },
  "traceId": "abc123-def456-ghi789"
}

2.5 前端调用示例

/**
 * 签到距离预校验
 * @param {string} workOrderId - 工单 ID
 * @param {number} latitude - 纬度
 * @param {number} longitude - 经度
 * @param {Object} options - 可选参数
 * @returns {Promise<Object>} 校验结果
 */
async function checkinPrecheck(workOrderId, latitude, longitude, options = {}) {
  const token = localStorage.getItem('auth_token');
  
  if (!token) {
    return { success: false, message: '请先登录' };
  }
  
  try {
    const response = await axios.post(
      `${API_BASE}/homecare/checkin/precheck`,
      {
        workOrderId,
        latitude,
        longitude,
        coordinateType: options.coordinateType || 'gcj02',
        accuracy: options.accuracy,
        reportedAt: options.reportedAt || new Date().toISOString()
      },
      {
        headers: {
          'Authorization': `Bearer ${token}`
        }
      }
    );
    
    const { data } = response.data;
    
    if (data.canCheckin) {
      console.log(`✅ 签到预校验通过`);
      console.log(`   距离: ${data.distanceMeters} 米`);
      console.log(`   允许半径: ${data.allowedRadiusMeters} 米`);
      return { success: true, ...data };
    } else {
      console.log(`❌ 签到预校验失败: ${data.reasonCode}`);
      console.log(`   距离: ${data.distanceMeters} 米`);
      console.log(`   允许半径: ${data.allowedRadiusMeters} 米`);
      return { success: false, reasonCode: data.reasonCode, ...data };
    }
  } catch (error) {
    if (error.response) {
      console.error('预校验请求失败:', error.response.data.msg);
      return { success: false, message: error.response.data.msg };
    }
    return { success: false, message: '网络错误' };
  }
}

// 使用示例 1: 范围内签到
const result1 = await checkinPrecheck(
  'a6450755-add4-4bdf-839f-165459ddff5d',
  39.9042,
  116.4074
);
if (result1.success) {
  // 显示"可以签到"按钮
}

// 使用示例 2: 超出范围签到
const result2 = await checkinPrecheck(
  'a6450755-add4-4bdf-839f-165459ddff5d',
  39.9142, // 距离约 1.1 公里
  116.4174
);
if (!result2.success && result2.reasonCode === 'OUT_OF_RADIUS') {
  // 显示"您超出签到范围"提示
}

2.6 距离计算原理

后端使用 Haversine 公式 计算两点之间的球面距离:

function calculateDistance(lat1, lon1, lat2, lon2) {
  const R = 6371000; // 地球半径(米)
  const dLat = toRad(lat2 - lat1);
  const dLon = toRad(lon2 - lon1);
  const a = 
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
    Math.sin(dLon / 2) * Math.sin(dLon / 2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return R * c;
}

function toRad(degrees) {
  return degrees * Math.PI / 180;
}

2.7 签到半径配置

签到半径通过 sys_sla_config 表配置,优先级如下:

  1. 工单级别 - scope_type = 'WORK_ORDER'
  2. 组织级别 - scope_type = 'ORG'
  3. 团队级别 - scope_type = 'TEAM'
  4. 全局默认 - scope_type = 'GLOBAL'(默认 50 米)

查询配置 SQL

SELECT config_value::numeric as radius_meters
FROM public.sys_sla_config
WHERE config_key = 'HOMECARE_CHECKIN_RADIUS_METERS'
  AND scope_type = 'GLOBAL'
  AND is_active = true
LIMIT 1;

三、完整签到流程

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   用户登录   │ ──► │   管理员派单  │ ──► │  居家服务员接单 │ ──► │  签到预校验  │
│  (邮箱登录)  │     │ (派单接口)   │     │  (接单接口)   │     │ (距离预校验) │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
       │                                                                                    │
       │                                                                                    ▼
       │                                                                             ┌─────────────┐
       │                                                                             │ 签到提交    │
       │                                                                             │ (证据文件)  │
       │                                                                             └─────────────┘
       ▼                                                                                    │
┌─────────────┐                                                                                    │
│  获取用户信息 │                                                                                    │
└─────────────┘                                                                                    ▼
                                                                                         ┌─────────────┐
                                                                                         │ 数据库更新  │
                                                                                         │ - ec_care_ │
                                                                                         │   tasks     │
                                                                                         │ - hc_work_ │
                                                                                         │   order_   │
                                                                                         │   events   │
                                                                                         └─────────────┘

四、错误码对照表

错误码 HTTP 状态码 说明 处理建议
OK 200 操作成功 -
UNAUTHORIZED 401 邮箱或密码错误 提示用户检查账号密码
NOT_FOUND 404 用户不存在 提示用户注册
OUT_OF_RADIUS 400 超出签到半径 提示用户靠近服务地点
SERVICE_LOCATION_MISSING 400 服务地点未配置 联系管理员配置服务地点
WORK_ORDER_NOT_ASSIGNABLE 400 工单未分配 等待管理员派单
WORKER_NOT_MATCHED 400 人员不匹配 当前用户非指定服务人员
SIGNATURE_REQUIRED 400 缺少签名 签到提交时需要电子签名
EVIDENCE_FILE_NOT_EXIST 400 证据文件不存在 签到提交时需要上传照片

五、联调测试清单

5.1 登录接口测试

  • 使用正确账号密码登录成功
  • 使用错误密码登录失败
  • 使用不存在的邮箱登录失败
  • 验证返回的 Token 格式正确
  • 验证 Token 可正常用于后续请求

5.2 距离预校验测试

  • 范围内签到0 米)- 应该通过
  • 范围内签到50 米)- 应该通过
  • 超出范围签到100 米)- 应该拒绝
  • 超出范围签到1 公里)- 应该拒绝
  • 服务地点未配置 - 应该拒绝
  • 工单未分配 - 应该拒绝
  • 人员不匹配 - 应该拒绝

5.3 数据库验证

预校验接口调用后,验证以下数据写入:

-- 1. 检查 hc_worker_locations 表(预校验会插入位置记录)
SELECT * FROM public.hc_worker_locations
WHERE work_order_id = 'a6450755-add4-4bdf-839f-165459ddff5d'
ORDER BY created_at DESC
LIMIT 5;

-- 2. 检查 hc_dispatch_assignments 表(派单记录)
SELECT * FROM public.hc_dispatch_assignments
WHERE work_order_id = 'a6450755-add4-4bdf-839f-165459ddff5d'
ORDER BY created_at DESC
LIMIT 5;

六、常见问题

Q1: 为什么预校验通过但签到提交失败?

A: 签到提交需要额外的证据文件(照片)和电子签名。预校验只检查距离,签到提交会验证:

  • 至少 1 个有效的证据文件
  • 有效的电子签名(至少 8 个字符)
  • 证据文件与工单匹配

Q2: 距离计算为什么有误差?

A: 距离计算使用 Haversine 公式,基于 GCJ02 坐标系。实际误差来源:

  • 手机 GPS 定位精度(通常 5-20 米)
  • 坐标系转换误差
  • 地球曲率近似计算

Q3: 如何修改签到半径?

A: 修改 sys_sla_config 表中的配置:

UPDATE public.sys_sla_config
SET config_value = '100'  -- 改为 100 米
WHERE config_key = 'HOMECARE_CHECKIN_RADIUS_METERS'
  AND scope_type = 'GLOBAL'
  AND is_active = true;

七、相关文档


文档维护: 如有接口变更,请及时更新此文档。