feat: 初始化考培练系统项目
- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
311
backend/app/api/v1/scrm.py
Normal file
311
backend/app/api/v1/scrm.py
Normal file
@@ -0,0 +1,311 @@
|
||||
"""
|
||||
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)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user