Dify + Spring Boot 实战:请假申请分支完整实现指南

1. 项目背景与目标

在企业智能助手系列的第二部分,我们继续完善员工自助服务助手,为其添加请假申请功能。用户只需通过自然语言说出请假需求,助手就能自动解析信息、调用后端接口、提交申请并反馈结果。

本文核心内容

  • 使用 LLM 从自然语言中提取结构化信息(JSON)
  • 处理模型输出中的推理标签(<think>)和换行符
  • 使用代码执行节点清洗数据并判断信息完整性
  • HTTP POST 请求提交请假申请
  • 条件分支处理信息缺失场景

2. 整体流程设计

请假申请分支的完整流程如下:

text

用户输入 → 意图识别 → 请假申请分支
    ↓
提取请假信息(LLM) → 输出带标签的 JSON
    ↓
代码执行节点(清洗 + 解析 + 缺失判断)
    ↓
条件分支(missing_fields)
    ├─ true → 直接回复(反问缺失信息)
    └─ false → HTTP POST → 回答 LLM → 结束

3. 后端接口准备(Java Spring Boot)

3.1 数据库表

sql

CREATE DATABASE employee_db;
USE employee_db;

CREATE TABLE leave_requests (
    id INT AUTO_INCREMENT PRIMARY KEY,
    employee_id VARCHAR(20) NOT NULL,
    start_date DATE NOT NULL,
    end_date DATE NOT NULL,
    reason VARCHAR(255),
    status VARCHAR(20) DEFAULT 'pending'
);

3.2 实体类 LeaveRequest.java

java

package com.example.employee.entity;

import lombok.Data;
import javax.persistence.*;
import java.time.LocalDate;

@Entity
@Table(name = "leave_requests")
@Data
public class LeaveRequest {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "employee_id")
    private String employeeId;

    @Column(name = "start_date")
    private LocalDate startDate;

    @Column(name = "end_date")
    private LocalDate endDate;

    private String reason;
    private String status;
}

3.3 DTO LeaveRequestDto.java

java

package com.example.employee.dto;

import lombok.Data;
import java.time.LocalDate;

@Data
public class LeaveRequestDto {
    private String employeeId;
    private LocalDate startDate;
    private LocalDate endDate;
    private String reason;
}

3.4 Repository LeaveRepository.java

java

package com.example.employee.repository;

import com.example.employee.entity.LeaveRequest;
import org.springframework.data.jpa.repository.JpaRepository;

public interface LeaveRepository extends JpaRepository<LeaveRequest, Long> {
}

3.5 Service LeaveService.java

java

package com.example.employee.service;

import com.example.employee.dto.LeaveRequestDto;
import com.example.employee.entity.LeaveRequest;
import com.example.employee.repository.LeaveRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class LeaveService {

    @Autowired
    private LeaveRepository repository;

    public LeaveRequest submitLeave(LeaveRequestDto dto) {
        LeaveRequest leave = new LeaveRequest();
        leave.setEmployeeId(dto.getEmployeeId());
        leave.setStartDate(dto.getStartDate());
        leave.setEndDate(dto.getEndDate());
        leave.setReason(dto.getReason());
        leave.setStatus("pending");
        return repository.save(leave);
    }
}

3.6 Controller LeaveController.java

java

package com.example.employee.controller;

import com.example.employee.dto.LeaveRequestDto;
import com.example.employee.entity.LeaveRequest;
import com.example.employee.service.LeaveService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/leave")
public class LeaveController {

    @Autowired
    private LeaveService leaveService;

    @PostMapping
    public LeaveRequest submitLeave(@RequestBody LeaveRequestDto dto) {
        return leaveService.submitLeave(dto);
    }
}

3.7 部署与测试

  1. 打包:mvn clean package

  2. 上传 jar 到服务器

  3. 启动:nohup java -jar employee-assistant-backend-1.0-SNAPSHOT.jar > app.log 2>&1 &

  4. 测试接口:

    bash

    curl -X POST http://你的IP:8081/api/leave \
      -H "Content-Type: application/json" \
      -d '{"employeeId":"10001","startDate":"2026-04-01","endDate":"2026-04-03","reason":"年假"}'
    

    返回示例:

    json

    {"id":3,"employeeId":"10001","startDate":"2026-04-01","endDate":"2026-04-03","reason":"年假","status":"pending"}
    

4. Dify Chatflow 请假申请分支配置

4.1 意图识别(复用之前的 LLM)

确保意图识别输出 leave_apply,条件分支已配置。

4.2 提取请假信息(LLM 节点)

节点名称提取请假信息

系统提示

text

从用户输入中提取请假信息,输出 JSON 格式,字段如下:
- employeeId: 员工工号(如果没有,填 "MISSING")
- startDate: 开始日期(格式 YYYY-MM-DD,如果没有,填 "MISSING")
- endDate: 结束日期(格式 YYYY-MM-DD,如果没有,填 "MISSING")
- reason: 请假原因(如果没有,填 "MISSING")

示例输出:
{"employeeId":"10001","startDate":"2026-04-01","endDate":"2026-04-03","reason":"年假"}

用户输入:{{sys.query}}

温度:0.1
输出text

4.3 清洗与解析(代码执行节点 - JavaScript)

由于部分模型(如 DeepSeek R1)会在输出中添加 <think>...</think> 推理标签,需要清洗后提取 JSON。

输入变量

  • input{{提取请假信息.text}}

输出变量

变量名 类型
leave_data Object
missing_fields Boolean

JavaScript 代码

javascript

function main(input) {
    // 1. 确保输入是字符串
    let text = '';
    if (typeof input === 'string') {
        text = input;
    } else if (input && typeof input === 'object') {
        text = input.text || JSON.stringify(input);
    } else {
        text = String(input);
    }

    // 2. 去除 <think>...</think> 部分
    const thinkEnd = text.indexOf('</think>');
    if (thinkEnd !== -1) {
        text = text.substring(thinkEnd + 8);
    }

    // 3. 去除首尾空白
    text = text.trim();

    // 4. 提取 JSON 部分(找到第一个 { 和最后一个 })
    const firstBrace = text.indexOf('{');
    const lastBrace = text.lastIndexOf('}');
    if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
        text = text.substring(firstBrace, lastBrace + 1);
    }

    // 5. 再次清理空白
    text = text.trim();

    // 6. 尝试解析 JSON
    try {
        const obj = JSON.parse(text);
        
        // 检查是否有缺失字段
        const hasMissing = !obj.employeeId || obj.employeeId === 'MISSING' ||
                           !obj.startDate || obj.startDate === 'MISSING' ||
                           !obj.endDate || obj.endDate === 'MISSING' ||
                           !obj.reason || obj.reason === 'MISSING';
        
        if (hasMissing) {
            return { leave_data: null, missing_fields: true };
        }
        
        return { leave_data: obj, missing_fields: false };
        
    } catch(e) {
        // 解析失败,返回缺失标识
        return { leave_data: null, missing_fields: true };
    }
}

4.4 条件分支节点

条件{{代码执行节点.missing_fields}} 等于 true

  • (信息缺失):进入直接回复节点,内容:“请提供完整的请假信息,包括:员工工号、开始日期(YYYY-MM-DD)、结束日期(YYYY-MM-DD)、请假原因。”
  • (信息完整):进入 HTTP 请求节点

4.5 HTTP 请求节点(POST)

配置项
URL http://你的IP:8081/api/leave
Method POST
Body Type JSON
Body Content {{代码执行节点.leave_data}}
Headers Content-Type 自动添加

输出变量body(后端返回的 JSON)

4.6 回答生成(LLM 节点)

系统提示

text

你是一个企业助手。根据以下请假申请结果,用友好、亲切的语气告知用户。

结果:{{HTTP请求节点.body}}

要求:
- 如果返回结果中包含 id 和 status,说明提交成功,告知用户申请已提交,状态为“待审批”。
- 如果返回错误信息(如 status>=400),则提示失败原因。
- 最后可加一句:“审批通过后您将收到通知。”

字段说明:
- id: 申请编号
- status: pending(待审批)

用户消息{{sys.query}}(可选)
温度:0.1

4.7 结束节点

将直接回复节点和回答 LLM 节点都连接到结束节点。


5. 关键知识点总结

5.1 处理模型推理标签

部分模型(如 DeepSeek R1)会在输出中添加 <think>...</think> 标签。解决方案:

  • 在代码执行节点中截取 </think> 之后的内容
  • 提取 JSON 部分(定位第一个 { 和最后一个 }

5.2 信息完整性判断

  • 让 LLM 在缺失字段时输出 "MISSING"
  • 代码执行节点解析后检查字段值
  • 输出布尔值 missing_fields 供条件分支使用

5.3 避免 JSON 双重转义

  • 代码执行节点返回对象leave_data: obj
  • HTTP 节点选择 JSON 模式,直接引用对象
  • 不要将对象手动 JSON.stringify()

5.4 条件分支使用布尔类型

  • 布尔类型在条件分支中直接判断 等于 true/false
  • 避免使用字符串包含判断,更安全可靠

6. 常见问题与解决方案

问题 原因 解决方案
输出仍包含 <think> 标签 清洗逻辑不完整 截取 </think> 之后,提取 {} 部分
leave_data: null JSON 解析失败 增强清洗逻辑,添加错误日志调试
Invalid actual value type: string or array 条件分支用了对象变量 改用布尔变量 missing_fields
请求体出现反斜杠 二次序列化 代码节点返回对象,HTTP 用 JSON 模式
HTTP 返回 400 JSON 字段名不匹配 确保字段名与后端 DTO 一致

7. 测试用例

7.1 完整信息

输入工号10001,请2026年4月1日到4月3日年假
期望:提交成功,返回申请编号和待审批状态

7.2 缺失工号

输入请2026年4月1日到4月3日年假
期望:反问“请提供完整的请假信息…”

7.3 缺失日期

输入工号10001,请年假
期望:反问“请提供完整的请假信息…”

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐