Files
012-kaopeilian/backend/app/api/v1/scrm.py
111 998211c483 feat: 初始化考培练系统项目
- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
2026-01-24 19:33:28 +08:00

312 lines
9.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
SCRM 系统对接 API 路由
提供给 SCRM 系统调用的数据查询接口
认证方式Bearer Token (SCRM_API_KEY)
"""
import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import get_db, verify_scrm_api_key
from app.services.scrm_service import SCRMService
from app.schemas.scrm import (
EmployeePositionResponse,
EmployeePositionData,
PositionCoursesResponse,
PositionCoursesData,
KnowledgePointSearchRequest,
KnowledgePointSearchResponse,
KnowledgePointSearchData,
KnowledgePointDetailResponse,
KnowledgePointDetailData,
SCRMErrorResponse,
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/scrm", tags=["scrm"])
# ==================== 1. 获取员工岗位 ====================
@router.get(
"/employees/{userid}/position",
response_model=EmployeePositionResponse,
summary="获取员工岗位通过userid",
description="根据企微 userid 查询员工在考陪练系统中的岗位信息",
responses={
200: {"model": EmployeePositionResponse, "description": "成功"},
401: {"model": SCRMErrorResponse, "description": "认证失败"},
404: {"model": SCRMErrorResponse, "description": "员工不存在"},
}
)
async def get_employee_position_by_userid(
userid: str,
_: bool = Depends(verify_scrm_api_key),
db: AsyncSession = Depends(get_db)
):
"""
获取员工岗位通过企微userid
- **userid**: 企微员工 userid
"""
service = SCRMService(db)
result = await service.get_employee_position(userid=userid)
if result is None:
raise HTTPException(
status_code=404,
detail={
"code": 404,
"message": "员工不存在",
"data": None
}
)
# 检查是否有多个匹配结果
if result.get("multiple_matches"):
return {
"code": 0,
"message": f"找到 {result['count']} 个匹配的员工,请确认",
"data": result
}
return EmployeePositionResponse(
code=0,
message="success",
data=EmployeePositionData(**result)
)
@router.get(
"/employees/search/by-name",
summary="获取员工岗位(通过姓名搜索)",
description="根据员工姓名查询员工在考陪练系统中的岗位信息,支持精确匹配和模糊匹配",
responses={
200: {"description": "成功"},
401: {"model": SCRMErrorResponse, "description": "认证失败"},
404: {"model": SCRMErrorResponse, "description": "员工不存在"},
}
)
async def get_employee_position_by_name(
name: str = Query(..., description="员工姓名,支持精确匹配和模糊匹配"),
_: bool = Depends(verify_scrm_api_key),
db: AsyncSession = Depends(get_db)
):
"""
获取员工岗位(通过姓名搜索)
- **name**: 员工姓名(必填),优先精确匹配,无结果时模糊匹配
注意:如果有多个同名员工,会返回员工列表供确认
"""
service = SCRMService(db)
result = await service.get_employee_position(name=name)
if result is None:
raise HTTPException(
status_code=404,
detail={
"code": 404,
"message": f"未找到姓名包含 '{name}' 的员工",
"data": None
}
)
# 检查是否有多个匹配结果
if result.get("multiple_matches"):
return {
"code": 0,
"message": f"找到 {result['count']} 个匹配的员工,请确认后使用 employee_id 精确查询",
"data": result
}
return EmployeePositionResponse(
code=0,
message="success",
data=EmployeePositionData(**result)
)
@router.get(
"/employees/by-id/{employee_id}/position",
response_model=EmployeePositionResponse,
summary="获取员工岗位通过员工ID",
description="根据员工ID精确查询员工岗位信息用于多个同名员工时的精确查询",
responses={
200: {"model": EmployeePositionResponse, "description": "成功"},
401: {"model": SCRMErrorResponse, "description": "认证失败"},
404: {"model": SCRMErrorResponse, "description": "员工不存在"},
}
)
async def get_employee_position_by_id(
employee_id: int,
_: bool = Depends(verify_scrm_api_key),
db: AsyncSession = Depends(get_db)
):
"""
获取员工岗位通过员工ID精确查询
- **employee_id**: 员工ID考陪练系统用户ID
适用场景:通过姓名搜索返回多个匹配结果后,使用此接口精确查询
"""
service = SCRMService(db)
result = await service.get_employee_position_by_id(employee_id)
if result is None:
raise HTTPException(
status_code=404,
detail={
"code": 404,
"message": "员工不存在",
"data": None
}
)
return EmployeePositionResponse(
code=0,
message="success",
data=EmployeePositionData(**result)
)
# ==================== 2. 获取岗位课程列表 ====================
@router.get(
"/positions/{position_id}/courses",
response_model=PositionCoursesResponse,
summary="获取岗位课程列表",
description="获取指定岗位的必修/选修课程列表",
responses={
200: {"model": PositionCoursesResponse, "description": "成功"},
401: {"model": SCRMErrorResponse, "description": "认证失败"},
404: {"model": SCRMErrorResponse, "description": "岗位不存在"},
}
)
async def get_position_courses(
position_id: int,
course_type: Optional[str] = Query(
default="all",
description="课程类型required/optional/all",
regex="^(required|optional|all)$"
),
_: bool = Depends(verify_scrm_api_key),
db: AsyncSession = Depends(get_db)
):
"""
获取岗位课程列表
- **position_id**: 岗位ID
- **course_type**: 课程类型筛选required/optional/all默认 all
"""
service = SCRMService(db)
result = await service.get_position_courses(position_id, course_type)
if result is None:
raise HTTPException(
status_code=404,
detail={
"code": 40002,
"message": "position_id 不存在",
"data": None
}
)
return PositionCoursesResponse(
code=0,
message="success",
data=PositionCoursesData(**result)
)
# ==================== 3. 搜索知识点 ====================
@router.post(
"/knowledge-points/search",
response_model=KnowledgePointSearchResponse,
summary="搜索知识点",
description="根据关键词和岗位搜索匹配的知识点",
responses={
200: {"model": KnowledgePointSearchResponse, "description": "成功"},
401: {"model": SCRMErrorResponse, "description": "认证失败"},
400: {"model": SCRMErrorResponse, "description": "请求参数错误"},
}
)
async def search_knowledge_points(
request: KnowledgePointSearchRequest,
_: bool = Depends(verify_scrm_api_key),
db: AsyncSession = Depends(get_db)
):
"""
搜索知识点
- **keywords**: 搜索关键词列表(必填)
- **position_id**: 岗位ID用于优先排序可选
- **course_ids**: 限定课程范围(可选)
- **knowledge_type**: 知识点类型筛选(可选)
- **limit**: 返回数量默认10最大100
"""
service = SCRMService(db)
result = await service.search_knowledge_points(
keywords=request.keywords,
position_id=request.position_id,
course_ids=request.course_ids,
knowledge_type=request.knowledge_type,
limit=request.limit
)
return KnowledgePointSearchResponse(
code=0,
message="success",
data=KnowledgePointSearchData(**result)
)
# ==================== 4. 获取知识点详情 ====================
@router.get(
"/knowledge-points/{knowledge_point_id}",
response_model=KnowledgePointDetailResponse,
summary="获取知识点详情",
description="获取知识点的完整信息",
responses={
200: {"model": KnowledgePointDetailResponse, "description": "成功"},
401: {"model": SCRMErrorResponse, "description": "认证失败"},
404: {"model": SCRMErrorResponse, "description": "知识点不存在"},
}
)
async def get_knowledge_point_detail(
knowledge_point_id: int,
_: bool = Depends(verify_scrm_api_key),
db: AsyncSession = Depends(get_db)
):
"""
获取知识点详情
- **knowledge_point_id**: 知识点ID
"""
service = SCRMService(db)
result = await service.get_knowledge_point_detail(knowledge_point_id)
if result is None:
raise HTTPException(
status_code=404,
detail={
"code": 40003,
"message": "knowledge_point_id 不存在",
"data": None
}
)
return KnowledgePointDetailResponse(
code=0,
message="success",
data=KnowledgePointDetailData(**result)
)