design.md 19 KB

阶段2a架构设计 — 果农核心功能

基于 Team A 需求规格(阶段2a spec.md) 新增模块:果农名片、招工发布、找工人、找客商、后台审核


一、系统架构(继承阶段一 + 扩展)

1.1 整体架构

┌─────────────────────────────────────────────────────────────┐
│                      客户端层                                │
├─────────────────────────┬───────────────────────────────────┤
│   微信小程序 (wxapp)    │   后台管理系统 (wxbackstage)       │
│   - 原生微信开发         │   - Vue 2 + Element UI            │
│   - 面向四类用户         │   - 面向政府管理部门               │
└───────────┬─────────────┴───────────────┬───────────────────┘
            │                             │
            ▼                             ▼
┌─────────────────────────────────────────────────────────────┐
│                      API 网关层                              │
│   - JWT Token 认证                                           │
│   - 权限拦截器(RBAC)                                        │
│   - 接口路径:/api/wx/* (小程序)  /api/admin/* (后台)         │
└───────────────────────────┬─────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                      业务逻辑层                              │
├──────────┬──────────┬──────────┬──────────┬────────────────┤
│ 用户模块 │ 系统模块 │ 审核模块 │ 首页模块 │  通用模块      │
│AuthService│SysService│AuditService│HomeService│CommonService│
├──────────┼──────────┼──────────┼──────────┼────────────────┤
│ 果农模块 │ 招工模块 │ 搜索模块 │ 文件模块 │  工具模块      │
│GrowerSvc │RecruitSvc│SearchSvc │FileService│ DistanceUtil  │
│GrowerAudit│RecruitRev│CallLogSvc│           │ KeywordCheck  │
└──────┬───┴──────┬───┴──────┬───┴──────┬───┴────────┬───────┘
       │          │          │          │            │
       ▼          ▼          ▼          ▼            ▼
┌─────────────────────────────────────────────────────────────┐
│                      数据访问层                              │
│   - MyBatis XML 映射                                         │
│   - Mapper 接口                                              │
└───────────────────────────┬─────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                      数据存储层                              │
├─────────────────────────┬───────────────────────────────────┤
│   MySQL (sayu 库)       │   本地文件存储                     │
│   - 20 张业务表          │   - ./uploads/image/              │
│   - AES 加密字段         │   - ./uploads/video/              │
│   - SHA256 哈希索引      │                                   │
└─────────────────────────┴───────────────────────────────────┘

1.2 阶段2a新增模块

模块 包名 职责
果农模块 com.sayu.service 果农档案CRUD、审核状态管理
招工模块 com.sayu.service 招工发布、编辑、下架、敏感词检测
搜索模块 com.sayu.service 工人列表、客商列表、距离计算
文件模块 com.sayu.service 文件上传、存储、访问、删除
审核模块(扩展) com.sayu.service 名片审核、待复核列表、SLA提醒

1.3 分层架构(继承阶段一)

Controller 层(API 入口)
    ↓
Service 层(业务逻辑)
    ↓
Mapper 层(数据访问)
    ↓
Entity/DTO/VO 层(数据模型)

二、数据库设计(继承阶段一 + 扩展)

2.1 ER 图(阶段2a涉及)

sys_user (用户基础表)
    │
    └── 1:N ──→ user_identity (身份关联表)
                    │
                    ├── 1:1 ──→ grower_profile (果农档案) ←── 阶段2a核心
                    │               │
                    │               └── 1:N ──→ recruit_info (招工信息) ←── 阶段2a核心
                    │
                    ├── 1:1 ──→ worker_profile (工人档案)
                    │               │
                    │               └── 1:N ──→ worker_apply (工人报名)
                    │
                    └── 1:1 ──→ buyer_profile (客商档案)

call_log (拨号日志) ←── 阶段2a新增使用
    │
    ├── caller_identity_id → user_identity
    └── callee_identity_id → user_identity

audit_log (审核日志) ←── 阶段2a扩展使用
    │
    └── target_type = 'GROWER_PROFILE' / 'RECRUIT_REVIEW'

2.2 阶段2a涉及的核心表

grower_profile(果农档案表)— 阶段2a核心

字段 类型 约束 说明
id BIGINT PK, AUTO_INCREMENT 主键
user_identity_id BIGINT INDEX 关联user_identity.id
name VARCHAR(50) 姓名
varieties VARCHAR(200) 苹果品种(JSON数组)
yield_amount DECIMAL(10,2) 产量(斤)
expected_price DECIMAL(8,2) 预期价格(元/斤)
address VARCHAR(200) 果园地址
latitude DECIMAL(10,7) INDEX 纬度
longitude DECIMAL(10,7) INDEX 经度
video_url VARCHAR(255) 果园视频URL(本地存储)
photos TEXT 照片URL(JSON数组)
audit_status TINYINT INDEX 0待审 1通过 2驳回
audit_remark VARCHAR(200) 驳回原因

recruit_info(招工信息表)— 阶段2a核心

字段 类型 约束 说明
id BIGINT PK, AUTO_INCREMENT 主键
user_identity_id BIGINT INDEX 发布者身份ID
work_types VARCHAR(100) 工种(JSON数组)
price DECIMAL(8,2) 价格
price_unit VARCHAR(10) DAY/PIECE
worker_count INT 需要人数
days INT 天数
location VARCHAR(200) 工作地点
latitude DECIMAL(10,7) INDEX 纬度
longitude DECIMAL(10,7) INDEX 经度
remark VARCHAR(500) 备注
status TINYINT INDEX 0下架 1发布中
keyword_flag TINYINT INDEX 0正常 1待复核
created_at DATETIME 发布时间

call_log(拨号日志表)— 阶段2a新增使用

字段 类型 约束 说明
id BIGINT PK, AUTO_INCREMENT 主键
caller_identity_id BIGINT INDEX 拨号方身份ID
callee_identity_id BIGINT INDEX 被拨方身份ID
call_time DATETIME 拨号时间

2.3 索引设计(阶段2a新增)

-- grower_profile 表索引
CREATE INDEX idx_user_identity_id ON grower_profile(user_identity_id);
CREATE INDEX idx_audit_status ON grower_profile(audit_status);
CREATE INDEX idx_lat_lng ON grower_profile(latitude, longitude);

-- recruit_info 表索引
CREATE INDEX idx_user_identity_id ON recruit_info(user_identity_id);
CREATE INDEX idx_status ON recruit_info(status);
CREATE INDEX idx_keyword_flag ON recruit_info(keyword_flag);
CREATE INDEX idx_lat_lng ON recruit_info(latitude, longitude);

-- call_log 表索引
CREATE INDEX idx_caller ON call_log(caller_identity_id);
CREATE INDEX idx_callee ON call_log(callee_identity_id);

三、接口设计(阶段2a新增)

3.1 果农名片接口

GET /api/wx/grower/profile — 获取果农档案

描述: 获取当前果农的名片信息

请求头: Authorization: Bearer {token}

响应参数:

{
  "code": 200,
  "message": "success",
  "data": {
    "id": 1,
    "name": "张三",
    "varieties": ["红富士", "嘎啦"],
    "yieldAmount": 50000,
    "expectedPrice": 4.0,
    "address": "洒渔镇黄兴村",
    "latitude": 27.3331,
    "longitude": 103.7168,
    "videoUrl": "http://localhost:8080/files/video/abc.mp4",
    "photos": ["http://localhost:8080/files/image/1.jpg"],
    "auditStatus": 0,
    "auditRemark": null
  }
}

PUT /api/wx/grower/profile — 更新果农档案

描述: 更新果农名片信息,审核状态重置为待审

请求参数:

{
  "name": "张三",
  "varieties": ["红富士", "嘎啦"],
  "yieldAmount": 50000,
  "expectedPrice": 4.0,
  "address": "洒渔镇黄兴村",
  "latitude": 27.3331,
  "longitude": 103.7168,
  "videoUrl": "http://localhost:8080/files/video/abc.mp4",
  "photos": ["http://localhost:8080/files/image/1.jpg"]
}

业务逻辑:

  1. 验证必填字段
  2. 更新 grower_profile
  3. 重置 audit_status=0,清空 audit_remark

3.2 招工接口

POST /api/wx/grower/recruit — 发布招工

描述: 发布招工信息,免审核直接发布

请求参数:

{
  "workTypes": ["采摘工", "分拣工"],
  "price": 150,
  "priceUnit": "DAY",
  "workerCount": 3,
  "days": 5,
  "location": "洒渔镇黄兴村",
  "latitude": 27.3331,
  "longitude": 103.7168,
  "remark": "需要有经验的采摘工"
}

业务逻辑:

  1. 验证必填字段(workTypes、price、priceUnit、workerCount、location)
  2. 敏感词检测(keyword_flag)
  3. 创建 recruit_info(status=1,免审核)

GET /api/wx/grower/recruit — 我的招工列表

请求参数: ?page=1&pageSize=20

响应参数:

{
  "code": 200,
  "message": "success",
  "data": {
    "total": 5,
    "page": 1,
    "pageSize": 20,
    "list": [
      {
        "id": 1,
        "workTypes": ["采摘工"],
        "price": 150,
        "priceUnit": "DAY",
        "workerCount": 3,
        "days": 5,
        "location": "洒渔镇黄兴村",
        "status": 1,
        "keywordFlag": 0,
        "applyCount": 2,
        "createdAt": "2026-05-30 10:00"
      }
    ]
  }
}

PUT /api/wx/grower/recruit/{id} — 编辑招工

请求参数:

{
  "price": 180,
  "workerCount": 5,
  "remark": "价格上调,急招"
}

业务逻辑:

  1. 验证招工存在且属于当前用户
  2. 更新可编辑字段
  3. 重新触发敏感词检测

DELETE /api/wx/grower/recruit/{id} — 下架招工

业务逻辑:

  1. 验证招工存在且属于当前用户
  2. 更新 status=0(逻辑删除)

3.3 找工人/客商接口

GET /api/wx/grower/workers — 工人列表

请求参数: ?workType=采摘工&page=1&pageSize=20

响应参数:

{
  "code": 200,
  "message": "success",
  "data": {
    "total": 10,
    "page": 1,
    "pageSize": 20,
    "list": [
      {
        "identityId": 5,
        "name": "李四",
        "skills": ["采摘工", "分拣工"],
        "price": 150,
        "priceUnit": "DAY",
        "status": 1,
        "distance": 2.5
      }
    ]
  }
}

业务逻辑:

  1. 查询 status=1(空闲)的工人
  2. 可选按工种筛选(skills JSON字段 LIKE 查询)
  3. 计算距离(Haversine公式)
  4. 按距离升序排序

GET /api/wx/grower/buyers — 客商列表

请求参数: ?page=1&pageSize=20

响应参数:

{
  "code": 200,
  "message": "success",
  "data": {
    "total": 5,
    "page": 1,
    "pageSize": 20,
    "list": [
      {
        "identityId": 10,
        "name": "王五果业",
        "varieties": ["红富士", "嘎啦"],
        "priceRange": "3.5-4.5",
        "totalAmount": 100000,
        "standards": "果径≥80mm",
        "address": "昭通市水果批发市场"
      }
    ]
  }
}

3.4 拨号接口

POST /api/wx/call/phone — 拨号接口

描述: 获取对方手机号并记录拨号日志

请求参数:

{
  "calleeIdentityId": 5
}

响应参数:

{
  "code": 200,
  "message": "success",
  "data": {
    "phone": "138****8888",
    "realPhone": "13800138000"
  }
}

业务逻辑:

  1. 验证被拨方存在
  2. 记录拨号日志(call_log)
  3. 返回脱敏手机号给前端显示,真实手机号用于拨号

3.5 后台审核接口

GET /api/admin/audit/grower-profile — 名片审核列表

请求参数: ?page=1&pageSize=10&status=0

响应参数:

{
  "code": 200,
  "message": "success",
  "data": {
    "total": 3,
    "page": 1,
    "pageSize": 10,
    "list": [
      {
        "id": 1,
        "growerName": "张三",
        "varieties": ["红富士"],
        "yieldAmount": 50000,
        "videoUrl": "...",
        "photos": ["..."],
        "auditStatus": 0,
        "createdAt": "2026-05-30 10:00"
      }
    ]
  }
}

PUT /api/admin/audit/grower-profile/{id} — 名片审核操作

请求参数:

{
  "action": "APPROVE",
  "reason": ""
}

或驳回:

{
  "action": "REJECT",
  "reason": "视频内容不清晰,请重新上传"
}

业务逻辑:

  1. 验证审核记录存在
  2. 驳回时 reason 不能为空
  3. 更新 grower_profile.audit_status
  4. 记录审核日志(audit_log)

GET /api/admin/audit/recruit-review — 待复核招工列表

请求参数: ?page=1&pageSize=10

响应参数:

{
  "code": 200,
  "message": "success",
  "data": {
    "total": 2,
    "list": [
      {
        "id": 1,
        "workTypes": ["采摘工"],
        "remark": "日薪300元,...",
        "keywordFlag": 1,
        "publisherName": "张三",
        "createdAt": "2026-05-30 10:00"
      }
    ]
  }
}

PUT /api/admin/audit/recruit-review/{id} — 待复核操作

请求参数:

{
  "action": "NORMAL",
  "reason": ""
}

或强制下架:

{
  "action": "FORCE_OFFLINE",
  "reason": "内容违规"
}

业务逻辑:

  1. 验证招工存在
  2. NORMAL → keyword_flag=0
  3. FORCE_OFFLINE → status=0,记录原因

四、安全设计(继承阶段一 + 扩展)

4.1 文件上传安全

前端上传 → 格式白名单校验 → 大小限制 → 存储到本地目录
                                         │
                                         ▼
                                   返回文件URL
  • 格式白名单: 图片(jpg,jpeg,png,gif)、视频(mp4,mov,avi)
  • 大小限制: 图片≤5MB,视频≤50MB
  • EXIF清除: 前端责任,后端不二次处理(性能考虑)
  • 路径安全: 文件名使用UUID,防止路径穿越

4.2 内容安全

招工发布 → 敏感词检测 → keyword_flag 标记 → 不阻塞发布
                                         │
                                         ▼
                                   待复核列表
  • 敏感词库: 初始硬编码,仅含明确违规词
  • 检测方式: 正则匹配
  • 处理方式: keyword_flag=1 进入待复核,不阻塞发布

4.3 拨号安全

拨号请求 → 记录拨号日志 → 返回脱敏手机号 → 前端拨号
  • 日志记录: 拨号方ID、被拨方ID、时间
  • 数据脱敏: 列表不返回手机号,拨号接口单独获取

五、关键技术方案

5.1 距离计算(Haversine公式)

public class DistanceUtil {
    private static final double EARTH_RADIUS = 6371; // 地球半径(km)

    public static double calculate(double lat1, double lng1, double lat2, double lng2) {
        double dLat = Math.toRadians(lat2 - lat1);
        double dLng = Math.toRadians(lng2 - lng1);
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                   Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
                   Math.sin(dLng / 2) * Math.sin(dLng / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return EARTH_RADIUS * c;
    }
}

说明:

  • 输入:两个经纬度点(DECIMAL(10,7))
  • 输出:距离(km,保留1位小数)
  • 精度:小数点后7位,误差≤1米

5.2 敏感词检测

public class KeywordCheckService {
    // 初始敏感词库(硬编码)
    private static final List<String> KEYWORDS = Arrays.asList(
        // 仅含明确违规词,不含正常业务词汇
    );

    public boolean containsSensitiveWord(String text) {
        if (text == null) return false;
        for (String keyword : KEYWORDS) {
            if (text.contains(keyword)) return true;
        }
        return false;
    }
}

5.3 视频压缩(异步)

@Service
public class VideoCompressService {
    @Async
    public void compressVideo(String inputPath, String outputPath) {
        // FFmpeg 命令:720p,码率2Mbps
        // ffmpeg -i input.mp4 -vf scale=-1:720 -b:v 2M output.mp4
    }
}

约束:

  • 异步执行,不影响用户操作
  • 失败自动重试3次
  • 压缩后替换原文件

六、阶段2a新增文件清单

文件 说明
GrowerProfileController.java 果农档案API
GrowerProfileService.java 果农档案业务逻辑
GrowerProfileMapper.xml 果农档案MyBatis映射
RecruitController.java 招工CRUD API
RecruitService.java 招工业务逻辑
RecruitMapper.xml 招工MyBatis映射
WorkerSearchController.java 工人列表API
WorkerSearchService.java 工人搜索业务逻辑
BuyerSearchController.java 客商列表API
BuyerSearchService.java 客商搜索业务逻辑
CallPhoneController.java 拨号API
CallLogService.java 拨号日志业务逻辑
CallLogMapper.xml 拨号日志MyBatis映射
GrowerAuditController.java 名片审核API
GrowerAuditService.java 名片审核业务逻辑
RecruitReviewController.java 待复核API
RecruitReviewService.java 待复核业务逻辑
KeywordCheckService.java 敏感词检测服务
DistanceUtil.java 距离计算工具类
VideoCompressService.java 视频压缩服务