Spring AI 2.0 OA System: RAG, Tool Calling & Multi-Role Approval Automation
This article details the architecture and implementation of an AI-powered OA system built with Spring AI 2.0, Spring Boot 4, and Vue 3, featuring intelligent leave form filling, approval progress querying, and RAG-based policy Q&A using Alibaba's Qwen and embedding models, with a four-role permission model, two-level approval engine, and Redis vector storage.
Project Overview
The system is an intelligent office automation (OA) platform for small and medium enterprises. On top of traditional OA core processes (leave, expense, approval), it deeply integrates Spring AI 2.0 to embed large model capabilities into real business scenarios, achieving three AI landing capabilities:
AI Capability | Description | Technical Means
--- | --- | ---
AI Smart Leave Form Filling | Employee inputs "day off next Wednesday", system auto-calculates date, determines leave type and days, and prompts annual leave balance and deduction rules | Prompt Engineering + Structured Output entity() + Tool Calling
AI Approval Progress Query | User asks "help me check where last month's expense approval is", returns real node, approver, and comments | Tool Calling (calls real business Service, not model hallucination)
Enterprise Policy RAG Q&A | Admin uploads leave/expense/seal policy files; employee questions answered via vector retrieval with source citation | Tika Parsing + Token Chunking + Redis Vector Store + QuestionAnswerAdvisorSystem features:
Four-role permission system : System Admin (ADMIN), General Manager (BOSS), Department Manager (MANAGER), Employee (EMPLOYEE) — data scope and operation permissions isolated by level.
Two-level approval engine : Leave and expense share a unified approval model (t_approval_record unified log), dynamically deciding whether to escalate to General Manager based on "days / amount" thresholds.
Four differentiated dashboards : Same /api/home/stats endpoint returns different dashboard data per login role, frontend renders ECharts charts.
Frontend-backend separation : Vue 3 + Element Plus SPA, Spring Boot provides stateless REST APIs, JWT carries login state.
Unified global time format : Date display as 2026-11-02, datetime as 2026-11-02 17:25:17 (backend Jackson unified yyyy-MM-dd HH:mm:ss, frontend utils/format.js fallback).
Business Logic Design
2.1 Role & Permission Matrix
A detailed matrix defines permissions for each role across functions: dashboard, department management, employee management, leave application, expense application, approval center, knowledge base management, announcement management, AI assistant, personal center. Permission implementation is three-layered: frontend route meta.roles guard → backend JwtInterceptor login verification → method-level @RequireRole annotation authorization; data scope enforced by each Service's applyScope() method assembling MyBatis-Plus query conditions per role.
2.2 User Login & Authentication Flow
2.3 Leave Business Full Process
Leave is the most complex business chain, linking AI form filling, balance verification, two-level approval, and balance deduction.
Key business rules:
Starting node determined by applicant role ; day threshold evaluated only when department manager approves ( days > 3 escalates to General Manager).
Department manager's own leave skips department manager node , goes directly to General Manager final approval.
Admin and General Manager submissions are auto-approved , directly set to approved.
Only PENDING status and own documents can be revoked (to CANCELLED).
Leave balance deducted only upon final approval ; marriage leave does not consume balance field.
New employee's first balance query triggers EmployeeService.initBalance() to auto-initialize current year record (annual leave defaults to 5 days).
2.4 Expense Business Process
Approval node permission verification ( ApprovalService.checkNodePermission): BOSS node only allows BOSS role to approve. MANAGER node only allows MANAGER role, and deptId must match approver's department.
ADMIN has fallback pass permission (for ops handling exceptional documents).
2.5 Knowledge Base Upload & Vectorization Flow
2.6 AI Assistant Conversation Flow (RAG + Tool Calling)
Anti-hallucination design : System prompt forces "balance and progress must call tools, no fabrication" and "when knowledge base has no relevant clause, explicitly answer not recorded". Tool-internal user IDs are sourced exclusively from ToolContext / UserContext, preventing model from passing other user IDs for unauthorized queries.
2.7 Business Rules Cheat Sheet
Rule Item | Value | Code Location
--- | --- | ---
Leave escalation threshold | Days > 3 | ApprovalService.approveLeave
Expense escalation threshold | Amount > 5000 CNY | ApprovalService.approveExpense
Document status | PENDING / APPROVED / REJECTED / CANCELLED | ApprovalStatusEnum
Approval nodes | MANAGER / BOSS / NONE | ApprovalService constants
Leave types | Annual / Personal / Sick / Compensatory / Marriage | t_leave.leave_type
Expense types | Travel / Office Supplies / Entertainment / Transport / Other | t_expense.expense_type
Default password | 123456 (MD5: e10adc3949ba59abbe56e057f20f883e) | EmployeeService.save / resetPassword
New employee annual leave | 5 days | EmployeeService.initBalance
Gender | Only "Male/Female", default "Male" | t_sys_user.gender
Token validity | 24 hours | oa.jwt.expire-hours
Upload root directory | D:/uploads54, access prefix /uploads | oa.upload.*Technology Stack
3.1 Backend Stack
Category | Technology | Version | Purpose
--- | --- | --- | ---
Base Framework | Spring Boot | 4.0.8 | App skeleton, auto-config, embedded container
Web | spring-boot-starter-webmvc | 4.0.8 | REST APIs, interceptors, static resource mapping
Validation | spring-boot-starter-validation | 4.0.8 | Request parameter validation
AI Framework | Spring AI | 2.0.1 | ChatClient, Advisor, Tool Calling, VectorStore
AI Model Access | spring-ai-starter-model-openai | 2.0.1 | OpenAI-compatible protocol to Alibaba Bailian
Vector Store | spring-ai-starter-vector-store-redis | 2.0.1 | Redis Stack vector retrieval
RAG | spring-ai-vector-store-advisor | 2.0.1 | QuestionAnswerAdvisor knowledge augmentation
Document Parsing | spring-ai-tika-document-reader | 2.0.1 | Unified parsing of txt/doc/pdf/markdown
Persistence | MyBatis-Plus (Spring Boot 4 Starter) | 3.5.17 | CRUD, Lambda conditions, pagination
SQL Parsing | mybatis-plus-jsqlparser | 3.5.17 | Pagination plugin dependency
DB Driver | mysql-connector-j | (with Boot) | MySQL 8 connection
Cache/Vector | spring-boot-starter-data-redis + Jedis | Jedis 7.2.0 | Redis connection & RediSearch vector index
Auth | JJWT | 0.12.6 | JWT generation & parsing
Runtime | JDK | 17 | Language versionCompatibility Note (Engineering Highlight) : Spring Boot 4 defaults to Lettuce, while Spring AI 2.0.1's Redis vector store depends on Jedis's RedisClient (provided since Jedis 7.2). Therefore the project explicitly upgrades Jedis and manually registers RedisVectorStore in RedisVectorStoreConfig (HNSW/COSINE); simultaneously MyBatis-Plus requires 3.5.17 to adapt to Boot 4.0.8's removed APIs.
3.2 Frontend Stack
Category | Technology | Version | Purpose
--- | --- | --- | ---
Framework | Vue | 3.5.42 | Composition API SPA
Build | Vite | 8.3.0 | Dev server (5173) & production build
UI Components | Element Plus | 2.11.4 | Table, form, dialog, table column auto-fit browser width
Icons | @element-plus/icons-vue | 2.3.2 | Menu & button icons
State Management | Pinia | 3.0.3 | Login state, user info, roles
Routing | Vue Router | 4.5.1 | Route guards + role menu filtering
HTTP | Axios | 1.12.2 | Request/response interceptors, token injection, unified error toast
Charts | ECharts | 5.6.0 | Dashboard statistics charts3.3 Middleware & External Services
Component | Configuration | Description
--- | --- | ---
MySQL 8 | 127.0.0.1:3308 / db_ai_oa / root / 123456 | Primary business data storage
Redis Stack 7.x | 127.0.0.1:6379, requires RediSearch module, index <code>oa-knowledge-index</code>, HNSW + COSINE | Vector storage & retrieval
Local File Storage | D:/uploads54/{avatar,knowledge,expense}, organized by <code>yyyyMMdd</code> subdirs, UUID naming | File uploads
Alibaba Bailian (OpenAI Compatible) | <code>.../compatible-mode/v1</code> | Chat: <code>qwen3.8-27b</code> (temperature 0.3), Embedding: <code>text-embedding-v4</code> (2048 dim)System Architecture Design
4.1 Overall Architecture
The system adopts classic frontend-backend separation + layered architecture , top-down 10 layers: User Layer → Frontend Presentation → Frontend Support → Security Access → Control Layer → Business Service Layer → AI Capability Layer → Data Access Layer → Data Storage Layer → External Services. Each layer depends unidirectionally; AI Capability Layer and Business Service Layer are peers and reuse the same Service batch (ensuring AI answers and page data share the same source).
4.2 Request Processing Chain
4.3 Backend Package Structure
com.java1234
├── OaApplication.java Startup class
├── ai
│ ├── config AI-related configs
│ │ ├── ChatClientConfig ChatClient & ChatMemory (window 20)
│ │ ├── OpenAiCompatibleConfig OpenAI compatible adapter + fixed vector dim
│ │ ├── FixedDimensionEmbeddingModel Avoid remote dim detection at startup
│ │ ├── EmbeddingUsageCompatInterceptor Compat missing prompt_tokens in response
│ │ └── RedisVectorStoreConfig Manual RedisVectorStore registration (HNSW/COSINE)
│ ├── service AiChatService / AiLeaveService / KnowledgeService
│ └── tool LeaveTools / ExpenseTools / EmployeeTools (@Tool)
├── common Result, PageResult, RoleEnum, ApprovalStatusEnum, BusinessException, GlobalExceptionHandler, LoginUser, UserContext (ThreadLocal), @RequireRole
├── config JacksonConfig (date format), MybatisPlusConfig (pagination plugin), MetaObjectHandlerConfig (auto-fill), WebMvcConfig (CORS/interceptors/static mapping)
├── controller Auth / Home / Dept / Employee / Leave / Expense / Ai (7 total)
├── dto LoginRequest, LoginVO, PasswordDTO, ApprovalDTO, ChatRequest, ChatReplyVO, LeaveParseRequest, LeaveDraftVO
├── entity 10 entities mapping 1:1 to tables (incl. transient display fields)
├── interceptor JwtInterceptor (login + role)
├── mapper 10 BaseMappers + HomeStatMapper (annotation-based stats SQL)
├── service Auth / Employee / Dept / Leave / Expense / Approval / Home / Notice (8 total)
└── util JwtUtil, Md5Util, FileUtilAll classes and methods have Chinese comments; entity classes do not use Lombok, getters/setters handwritten.
4.4 Frontend Project Structure
client/src
├── main.js / App.vue
├── layout/index.vue Main frame: side menu (role-filtered) + top bar
│ Top-right avatar/name dropdown → Personal Center / Logout
├── router/index.js Route table + meta.roles guard
├── stores/user.js Pinia: token, user info, roles
├── utils/request.js Axios instance with bidirectional interceptors
├── utils/format.js Date 2026-11-02, DateTime 2026-11-02 17:25:17
├── components/AiAssistant Global bottom-right floating AI assistant
└── views
├── login Login page
├── home Index dispatches by role → AdminHome / BossHome / ManagerHome / EmployeeHome
├── dept / employee Dept & employee management
├── leave / expense Leave & expense (incl. AI form filling, voucher upload)
├── approval Approval center
├── knowledge / notice Knowledge base & announcements
├── aichat AI Assistant full-screen page (session list + message stream + sources)
└── profile Personal center: left-right layout, left avatar upload+profile, right password change4.5 Key Technical Implementation Points
Theme | Implementation
--- | ---
Stateless Auth | JWT carries <code>id/username/realName/role/deptId/avatar</code>, HMAC-SHA signature, 24h expiry
Role Authorization | Custom annotation <code>@RequireRole</code>, interceptor reads method/class annotation vs role, returns 403 on mismatch
Login Context | <code>UserContext</code> based on ThreadLocal, cleaned in <code>afterCompletion</code> to avoid thread-pool leakage
Data Scope Isolation | Each Service's <code>applyScope()</code>: employee sees self, manager sees dept, BOSS/ADMIN see all pending
Unified Response | <code>Result<T>{code,message,data}</code>, pagination unified <code>PageResult<T>{total,records}</code>
Unified Exception | <code>BusinessException</code> + <code>GlobalExceptionHandler</code>, 401/403/500 structured output
Pagination | MyBatis-Plus <code>PaginationInnerInterceptor</code>
Time Format | Jackson global <code>yyyy-MM-dd HH:mm:ss</code> + timezone <code>Asia/Shanghai</code>
File Upload | <code>FileUtil</code> by <code>subdir/yyyyMMdd/UUID.ext</code>, returns <code>/uploads/...</code> relative URL, <code>WebMvcConfig</code> maps to disk
CORS | Backend <code>addCorsMappings</code> allows; dev mode Vite Proxy forwards <code>/api</code>, <code>/uploads</code>
Password Security | MD5 digest storage, change password validates old password and new/confirm match
Homepage Stats | <code>HomeStatMapper</code> annotation-based aggregation SQL (leave type distribution, last 6 months expense, dept headcount, status distribution, monthly expense total)AI Capability Architecture
5.1 Three AI Pipelines Comparison
Dimension | Smart Leave Form Filling | Smart Assistant | Knowledge Base Ingestion
--- | --- | --- | ---
Entry | POST /api/ai/leave/parse | POST /api/ai/chat | POST /api/knowledge/upload
Memory | None (single-turn) | MessageWindowChatMemory (20) | —
Knowledge Augmentation | None | QuestionAnswerAdvisor (topK=5, threshold 0.45) | —
Tool Calling | LeaveTools | LeaveTools / ExpenseTools / EmployeeTools | —
Output Form | Structured <code>entity(LeaveDraftVO.class)</code> | Natural language text + source list | Vector write
Fallback Strategy | <code>fillFallbackTip()</code> supplements with real balance | Empty answer fallback phrasing | Failure sets <code>vector_status=FAIL</code>5.2 Tool Calling Tool Catalog
Tool Class | Method | Parameters | Purpose
--- | --- | --- | ---
LeaveTools | queryLeaveBalance | none (userId from ToolContext) | Query current year annual/personal/sick/compensatory quota & used
LeaveTools | queryLeaveProgress | keyword (optional) | Query latest 5 leave requests status & current node
ExpenseTools | queryExpenseProgress | monthOffset (0 current/-1 previous), keyword | Query latest 8 expense requests real approval progress, approvers, comments
EmployeeTools | queryMyInfo | none | Query name, role, department, entry date5.3 Vector Retrieval Parameters
Parameter | Value
--- | ---
Index Name | oa-knowledge-index
Key Prefix | oa:kb:
Vector Algorithm / Distance | HNSW / COSINE
Embedding Model / Dimension | text-embedding-v4 / 2048
Chunk Size | chunkSize=800, min chars 200, min embeddable 50
Retrieval topK / Threshold | 5 / 0.45
Metadata Fields | docId (tag), docName (text), fileType (tag)Functional Module Design
Module | Core Capabilities
--- | ---
Basic Support | Login auth, Personal Center (avatar upload / profile edit / password change, left-right layout), Dept Management, Employee Management, Reset Password, Announcement Management
Process Office | Leave submit & revoke, Leave balance, Expense submit & voucher upload, Approval center pending, Approve/Reject, Approval log traceability
AI Intelligence | AI fill leave form, Deduction & balance tips, Multi-turn dialogue, Session history management, Policy RAG Q&A with source citation, Approval progress tool calling, Knowledge base vectorization
Data Dashboard | Admin global dashboard, Dept Manager approval desk, GM final review desk, Employee personal desk; includes leave type pie, monthly expense bar, dept headcount distribution, latest announcementsDashboard Data Differences by Role
Role | Metric Cards | Charts | Lists
--- | --- | --- | ---
ADMIN | Active staff count, dept count, pending leave, pending expense | Leave type distribution, last 6 months expense, dept headcount, leave status distribution | Latest announcements
BOSS | Pending final-review leave/expense, in-process docs, monthly expense total | Last 6 months expense, leave status distribution | Pending final-review leave/expense, announcements
MANAGER | Dept pending leave/expense, dept headcount, monthly dept expense | Dept leave type, dept last 6 months expense | Dept pending, announcements
EMPLOYEE | My pending leave/expense, my approved leave, annual leave remaining | My leave type, my last 6 months expense, my leave status | My recent docs, announcementsDatabase Design
7.1 Design Overview
Database name: db_ai_oa, charset utf8mb4 / utf8mb4_unicode_ci, MySQL 8 (port 3308).
All business tables use t_ prefix, total 10 tables .
Primary keys unified id BIGINT AUTO_INCREMENT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP.
Tables use logical foreign keys (no physical FK constraints), consistency guaranteed by service layer, facilitating sharding and migration.
Amounts use DECIMAL(12,2), days use DECIMAL(6,1) to avoid floating-point errors.
Script location: server/src/main/resources/db/db_ai_oa.sql.
7.2 Table Catalog
# | Table | Chinese Name | Description
--- | --- | --- | ---
1 | t_dept | Department Table | Dept basic info & manager
2 | t_sys_user | System User Table | Unified account table for four roles
3 | t_leave_balance | Leave Balance Table | Quota per user per year
4 | t_leave | Leave Request | Leave application main table
5 | t_expense | Expense Request | Expense application main table
6 | t_approval_record | Approval Log | Shared approval log for leave & expense
7 | t_knowledge_doc | Knowledge Document | Policy files & vectorization status
8 | t_chat_session | AI Session | Maps to Spring AI conversationId
9 | t_chat_message | AI Message | Session messages & RAG sources
10 | t_notice | Announcement Table | System announcements7.3 Table Structure Details
Each table's columns, types, lengths, nullability, and comments are fully documented in the source. Key highlights: t_dept: id, dept_name, dept_code, manager_id (logical FK to t_sys_user), parent_id (0 for top-level), sort_num, remark, create_time. t_sys_user: id, username (unique), password (MD5), real_name, role (ADMIN/BOSS/MANAGER/EMPLOYEE), dept_id, gender (Male/Female default Male), phone, email, avatar (relative path), entry_date, status (1 enabled/0 disabled), create_time. t_leave_balance: id, user_id, year_num, annual_total (default 10, new employee 5), annual_used, sick_used, personal_used, compensatory_total, compensatory_used. Unique constraint uk_user_year (user_id, year_num). t_leave: id, user_id, dept_id (redundant for dept isolation), leave_type (Annual/Personal/Sick/Compensatory/Marriage), start_date, end_date, days (supports 0.5, >3 escalates), reason, status (PENDING/APPROVED/REJECTED/CANCELLED), current_node (MANAGER/BOSS/NONE), ai_generated (1/0), create_time. t_expense: id, user_id, dept_id, expense_type (Travel/Office/Entertainment/Transport/Other), amount (DECIMAL(12,2), >5000 escalates), expense_date, reason, attachment (comma-separated paths), status, current_node, create_time. t_approval_record: id, biz_type (LEAVE/EXPENSE), biz_id, node_name (MANAGER/BOSS), approver_id, approver_role, action_type (APPROVE/REJECT), comment_text, create_time. t_knowledge_doc: id, doc_name (original filename, used as RAG source), file_path, file_type (txt/doc/pdf/md), file_size, chunk_count, vector_status (PENDING/SUCCESS/FAIL), uploader_id, create_time. t_chat_session: id, session_id (UUID no hyphens, unique, maps to Spring AI conversationId), user_id (for ownership verification), title (default "New Conversation", first question auto-truncated to 20 chars), create_time, update_time (ON UPDATE CURRENT_TIMESTAMP for list ordering). t_chat_message: id, session_id, role_name (user/assistant), content (MEDIUMTEXT ~16MB), sources_json (comma-separated RAG source doc names), create_time. t_notice: id, title, content (TEXT ~64KB), publisher, create_time.
7.4 Indexes & Constraints Summary
Table | Constraint/Index | Fields | Type
--- | --- | --- | ---
All tables | PRIMARY KEY | id | PK, BIGINT auto-increment
t_sys_user | UNIQUE | username | Login account unique
t_leave_balance | UNIQUE uk_user_year | user_id, year_num | Employee yearly balance unique
t_chat_session | UNIQUE | session_id | Session UUID unique7.5 Initial Test Data
Table | Rows | Description
--- | --- | ---
t_dept | 4 | HR, Tech, Finance, Marketing
t_sys_user | 16 | 1 Admin + 1 GM + 4 Dept Managers + 10 Employees, password unified 123456 (stored as MD5)
t_leave_balance | 14 | 2026 yearly balances
t_leave | 25 | Covers 5 leave types, 4 statuses, spanning 2026-04 to 2026-09
t_expense | 20 | Covers 5 expense types, includes >5000 escalation cases & voucher-attached pending
t_approval_record | 18 | Completed docs' two-level approval logs
t_notice | 3 | AI assistant enable notice, holiday schedule, expense reminderAPI Design
Unified prefix /api, unified response {"code":200,"message":"操作成功","data":{}}; except /api/auth/login all require Authorization: Bearer <token>.
Module | Method | Path | Permission | Description
--- | --- | --- | --- | ---
Auth | POST | /api/auth/login | Public | Username/password login, returns token & user info
Auth | GET | /api/auth/info | Login | Current logged-in user details (incl. dept name, role name)
Profile | PUT | /api/profile | Login | Update name, gender, phone, email
Profile | PUT | /api/profile/password | Login | Change password (verify old + confirm match)
Profile | POST | /api/profile/avatar | Login | Upload avatar
Home | GET | /api/home/stats | Login | Returns dashboard data per role
Notice | GET | /api/notice/page | Login | Announcement pagination
Notice | POST/PUT/DELETE | /api/notice, /api/notice/{id} | ADMIN | Announcement CRUD
Dept | GET | /api/dept/page, /api/dept/list | Login | Dept pagination / full list
Dept | POST/PUT/DELETE | /api/dept, /api/dept/{id} | ADMIN | Dept CRUD
Employee | GET | /api/employee/page, /api/employee/{id} | ADMIN, MANAGER | Employee pagination (manager only own dept), detail
Employee | POST/PUT/DELETE | /api/employee, /api/employee/{id} | ADMIN | Employee CRUD
Employee | POST | /api/employee/{id}/resetPassword | ADMIN | Reset password to 123456
Employee | GET | /api/employee/managers | Login | Dept manager candidate list
Leave | GET | /api/leave/page | Login | Pagination (supports status, leaveType, pendingOnly)
Leave | GET | /api/leave/{id} | Login | Detail + approval log
Leave | POST | /api/leave | Login | Submit leave request
Leave | POST | /api/leave/{id}/cancel | Login | Revoke own pending doc
Leave | POST | /api/leave/approve | Node Role | Approve / Reject
Leave | GET | /api/leave/balance | Login | My leave balance
Expense | GET | /api/expense/page, /api/expense/{id} | Login | Pagination, detail
Expense | POST | /api/expense | Login | Submit expense request
Expense | POST | /api/expense/{id}/cancel | Login | Revoke
Expense | POST | /api/expense/approve | Node Role | Approve / Reject
Expense | POST | /api/expense/attachment | Login | Upload voucher images
AI | POST | /api/ai/leave/parse | Login | Natural language parse to leave draft
AI | POST | /api/ai/chat | Login | Multi-turn dialogue (RAG + Tool Calling)
AI | POST | /api/ai/session | Login | Create new session
AI | GET | /api/ai/session/list | Login | Session list
AI | GET | /api/ai/session/{sessionId}/messages | Login | Session messages
AI | DELETE | /api/ai/session/{sessionId} | Login | Delete session & memory
Knowledge | GET | /api/knowledge/page | ADMIN | Document pagination
Knowledge | POST | /api/knowledge/upload | ADMIN | Upload & vectorize
Knowledge | DELETE | /api/knowledge/{id} | ADMIN | Delete document & corresponding vectorsSigned-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
java1234
Former senior programmer at a Fortune Global 500 company, dedicated to sharing Java expertise. Visit Feng's site: Java Knowledge Sharing, www.java1234.com
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
