app_user: 用户体系
This commit is contained in:
parent
8dc8e13019
commit
bd5b2e5cbb
302
aaa.txt
302
aaa.txt
@ -1,302 +0,0 @@
|
|||||||
企业软件著作权查询:
|
|
||||||
接口地址 : https://openapi.chinaz.net/v1/1036/copyrightsoftware
|
|
||||||
返回格式 : JSON
|
|
||||||
请求方式 : POST
|
|
||||||
请求参数:
|
|
||||||
名称
|
|
||||||
类型
|
|
||||||
必填
|
|
||||||
说明
|
|
||||||
sign string 是 填写由"634xz年月日"生成的32位md5加密值("年月日"指调用api的日期)。 举例:如果是2021年5月6日调用api,则对634xz20210506进行md5加密,得到的32位加密值就是sign
|
|
||||||
entName string 是 企业名称
|
|
||||||
pageNo string 是 页码,值从1开始(表示第1页)
|
|
||||||
range string 是 每页条数,取值介于在1到300之间(含1和300)
|
|
||||||
APIKey string 是 申请接口时获取的APIKey值
|
|
||||||
ChinazVer string 是 接口版本号,取值:2.0
|
|
||||||
返回参数说明:
|
|
||||||
名称
|
|
||||||
类型
|
|
||||||
说明
|
|
||||||
rc string 状态码
|
|
||||||
msg string 状态信息
|
|
||||||
data object 数据对象
|
|
||||||
data.total string 总数量
|
|
||||||
data.totalPage string 总页数
|
|
||||||
data.dataList object 软件著作权列表
|
|
||||||
data.dataList[].SNAME string 软件全称
|
|
||||||
data.dataList[].SHORTNAME string 软件简称
|
|
||||||
data.dataList[].ENTNAME string 著作权人
|
|
||||||
data.dataList[].SNUM string 登记号
|
|
||||||
data.dataList[].REGDATE string 首次发表日期
|
|
||||||
data.dataList[].ANNDATE string 登记批准日期
|
|
||||||
data.dataList[].ANNTYPE string 分类号名称
|
|
||||||
data.dataList[].TYPENUM string 分类号编号
|
|
||||||
data.dataList[].VNUM string 版本号
|
|
||||||
data.dataList[].cdate string 完成日期
|
|
||||||
正确的返回示例:
|
|
||||||
|
|
||||||
{
|
|
||||||
"orderNo":"202308281517124830015",
|
|
||||||
"data":{
|
|
||||||
"total":12,
|
|
||||||
"totalPage":25,
|
|
||||||
"dataList":[
|
|
||||||
{
|
|
||||||
"cdate":"2023-08-30",
|
|
||||||
"ANNDATE":"2023-11-29",
|
|
||||||
"SNUM":"2023SR1531019",
|
|
||||||
"SHORTNAME":"ModelMate",
|
|
||||||
"REGDATE":"2023-11-29",
|
|
||||||
"SNAME":"AI模型使能平台",
|
|
||||||
"VNUM":"24.0",
|
|
||||||
"ENTNAME":"****有限公司",
|
|
||||||
"ANNTYPE":"****",
|
|
||||||
"TYPENUM":"438951"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"rc":"0000",
|
|
||||||
"msg":"查询成功"
|
|
||||||
}
|
|
||||||
|
|
||||||
错误的返回示例:
|
|
||||||
{
|
|
||||||
"rc":"1002",
|
|
||||||
"msg":"sign校验失败"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
企业专利信息
|
|
||||||
|
|
||||||
接口地址 : https://openapi.chinaz.net/v1/1036/patent
|
|
||||||
返回格式 : JSON
|
|
||||||
请求方式 : POST
|
|
||||||
请求参数:
|
|
||||||
名称
|
|
||||||
类型
|
|
||||||
必填
|
|
||||||
说明
|
|
||||||
sign string 是 填写由"634xz年月日"生成的32位md5加密值("年月日"指调用api的日期)。 举例:如果是2021年5月6日调用api,则对634xz20210506进行md5加密,得到的32位加密值就是sign
|
|
||||||
searchKey string 是 查询关键词(企业名称、企业统代、企业注册号)
|
|
||||||
searchType string 否 查询关键字类型;默认为0:支持的所有类型;1:企业名称;2:统一社会信用代码;3:注册号;
|
|
||||||
pageNo string 是 页码,值从1开始(表示第1页)
|
|
||||||
range string 是 每页条数,取值介于在1到300之间(含1和300)
|
|
||||||
APIKey string 是 申请接口时获取的APIKey值
|
|
||||||
ChinazVer string 是 接口版本号,取值:2.0
|
|
||||||
返回参数说明:
|
|
||||||
名称
|
|
||||||
类型
|
|
||||||
说明
|
|
||||||
rc string 状态码
|
|
||||||
msg string 状态信息
|
|
||||||
data object 数据列表
|
|
||||||
data.total string 总数量
|
|
||||||
data.totalPage string 总页数
|
|
||||||
data.dataList string 专利信息列表
|
|
||||||
data.dataList[].patentflls string 专利法律状态信息列表
|
|
||||||
data.dataList[].patentflls[].flzt string 法律状态
|
|
||||||
data.dataList[].patentflls[].flztxq string 法律状态详情
|
|
||||||
data.dataList[].patentflls[].flztggrq string 法律状态公告日期
|
|
||||||
data.dataList[].SQH string 专利申请号
|
|
||||||
data.dataList[].PATNAME string 专利标题
|
|
||||||
data.dataList[].SQR string 申请/专利权人
|
|
||||||
data.dataList[].SQRQ string 申请日期
|
|
||||||
data.dataList[].GKGGH string 公开/公告号
|
|
||||||
data.dataList[].FMR string 发明/设计人
|
|
||||||
data.dataList[].GKGGR string 公开/公告日
|
|
||||||
data.dataList[].FCFL string 范畴分类
|
|
||||||
data.dataList[].FLH string 分类号
|
|
||||||
data.dataList[].PTYPE string 专利分类
|
|
||||||
data.dataList[].DLR string 代理人
|
|
||||||
data.dataList[].DZ string 地址
|
|
||||||
data.dataList[].FASQ string 分案申请
|
|
||||||
data.dataList[].GJGB string 国际公布
|
|
||||||
data.dataList[].GJSQ string 国际申请
|
|
||||||
data.dataList[].GSDM string 国省代码
|
|
||||||
data.dataList[].JRGJRQ string 进入国家日期
|
|
||||||
data.dataList[].YXQ string 优先权
|
|
||||||
data.dataList[].YZWX string 引证文献
|
|
||||||
data.dataList[].ZFLH string 主分类号
|
|
||||||
data.dataList[].ZLDLJG string 专利代理机构
|
|
||||||
data.dataList[].ZQX string 主权项
|
|
||||||
data.dataList[].ZY string 摘要
|
|
||||||
data.dataList[].IPC string 专利行业分类
|
|
||||||
data.dataList[].PID string 专利ID
|
|
||||||
data.dataList[].PIC string 代表图片
|
|
||||||
data.dataList[].SQGKGGH string 授权公告号
|
|
||||||
data.dataList[].SQGKGGR string 授权公开日期
|
|
||||||
data.dataList[].ENTNAME string 申请企业
|
|
||||||
data.dataList[].ZFLNAME string 主分类名称
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
司法综合数据查询
|
|
||||||
接口地址 : https://openapi.chinaz.net/v1/1036/judgementdetailv4
|
|
||||||
返回格式 : JSON
|
|
||||||
请求方式 : POST
|
|
||||||
调用说明 : 司法数据综合查询接口数据非实时,可能存在部分数据延迟。该数据仅供参考,不作为判定具体结果使用。
|
|
||||||
请求参数:
|
|
||||||
名称
|
|
||||||
类型
|
|
||||||
必填
|
|
||||||
说明
|
|
||||||
sign string 是 填写由"634xz年月日"生成的32位md5加密值("年月日"指调用api的日期)。 举例:如果是2021年5月6日调用api,则对634xz20210506进行md5加密,得到的32位加密值就是sign
|
|
||||||
q string 否 名称,选填,列表检索时(即查详情,也即id不填时)必填
|
|
||||||
idCardNo string 否 身份证号,选填,一般查个人时填写查询会更加精确
|
|
||||||
datatype string 否 查询数据类型,多个以',' 分割,共有zxgg(被执行人)、sxgg(失信被执行人)、ktgg(开庭公告)、fygg(法院公告)、cpws(裁判文书)、splc(审判流程)六种数据类型,如果列表检索时,该字段默认为六种数据类型都填
|
|
||||||
id string 否 详情id,该id从检索结果中获取,请配合查询数据类型使用。如查询某条被执行人详情时,datatype必须填'zxgg'
|
|
||||||
pageNo string 否 页码,列表检索时输入的字段,默认为1
|
|
||||||
APIKey string 是 申请接口时获取的APIKey值
|
|
||||||
ChinazVer string 是 接口版本号,取值:1.0
|
|
||||||
返回参数说明:
|
|
||||||
名称
|
|
||||||
类型
|
|
||||||
说明
|
|
||||||
rc string 状态码
|
|
||||||
msg string 状态信息
|
|
||||||
orderNo string 订单号
|
|
||||||
data string 返回查询结果
|
|
||||||
count string 检索出每种数据类型的总量统计
|
|
||||||
count[].bzxrCount string 被执行人总量
|
|
||||||
count[].sxrCount string 失信被执行人总量
|
|
||||||
count[].ktggCount string 开庭公告总量
|
|
||||||
count[].fyggCount string 法院公告数量
|
|
||||||
count[].cpwsCount string 裁判文书数量
|
|
||||||
count[].splcCount string 审判流程数量
|
|
||||||
list string 检索出每种数据类型数据列表
|
|
||||||
list[].bzxrList string 被执行人列表
|
|
||||||
bzxrList[].id string 被执行人ID
|
|
||||||
bzxrList[].entityName string 主体名称
|
|
||||||
bzxrList[].entityId string 主体代码
|
|
||||||
bzxrList[].courtName string 法院
|
|
||||||
bzxrList[].caseCode string 案号
|
|
||||||
bzxrList[].regDate string 立案时间
|
|
||||||
bzxrList[].relateType string 数据类别
|
|
||||||
list[].sxrList string 失信被执行人列表
|
|
||||||
sxrList[].id string 失信被执行人ID
|
|
||||||
sxrList[].entityName string 主体名称
|
|
||||||
sxrList[].entityId string 主体代码
|
|
||||||
sxrList[].courtName string 法院
|
|
||||||
sxrList[].caseCode string 案号
|
|
||||||
sxrList[].regDate string 立案时间
|
|
||||||
sxrList[].relateType string 数据类别
|
|
||||||
list[].ktggList string 开庭公告列表
|
|
||||||
ktggList[].id string 开庭公告ID
|
|
||||||
ktggList[].content string 开庭公告文书内容
|
|
||||||
list[].fyggList string 法院公告列表
|
|
||||||
fyggList[].id string 法院公告ID
|
|
||||||
fyggList[].content string 法院公告文书内容
|
|
||||||
list[].cpwsList string 裁判文书列表
|
|
||||||
cpwsList[].id string 裁判文书ID
|
|
||||||
cpwsList[].title string 标题
|
|
||||||
cpwsList[].caseCode string 案号
|
|
||||||
cpwsList[].court string 法院
|
|
||||||
cpwsList[].standPoint string 查询主体立场
|
|
||||||
cpwsList[].judgeDate string 裁判日期
|
|
||||||
cpwsList[].content string 文书缩略内容
|
|
||||||
cpwsList[].resultMatch string 个人查询匹配度,个人查询时有值,企业查询返回是空值
|
|
||||||
list[].splcList string 审判流程列表
|
|
||||||
splcList[].id string 审判流程ID
|
|
||||||
splcList[].litigant string 当事人
|
|
||||||
splcList[].courtName string 法院
|
|
||||||
splcList[].caseCode string 案号
|
|
||||||
entityName string 主体名称
|
|
||||||
entityId string 主体代码
|
|
||||||
courtName string 法院
|
|
||||||
caseState string 案件状态
|
|
||||||
caseCode string 案号
|
|
||||||
execMoney string 执行标的
|
|
||||||
regDate string 立案时间
|
|
||||||
gistId string 依据文号
|
|
||||||
entityName string 主体名称
|
|
||||||
entityId string 主体代码
|
|
||||||
caseCode string 案号
|
|
||||||
age string 年龄
|
|
||||||
sex string 性别
|
|
||||||
bussinessEntity string 法定代表人或者负责人姓名
|
|
||||||
courtName string 法院
|
|
||||||
areaName string 省份
|
|
||||||
partyTypeName string 类型
|
|
||||||
gistId string 执行依据文号
|
|
||||||
regDate string 立案时间
|
|
||||||
gistUnit string 做出执行依据单位
|
|
||||||
duty string 生效法律文书确定的义务
|
|
||||||
performance string 被执行人的履行情况
|
|
||||||
performedPart string 已履行
|
|
||||||
unPerformPart string 未履行
|
|
||||||
disruptTypeName string 失信类型
|
|
||||||
publishDate string 发布日期
|
|
||||||
status string 下架状态:已下架,未下架
|
|
||||||
ktggId string 开庭公告ID
|
|
||||||
areaName string 地区名称
|
|
||||||
court string 审理法院
|
|
||||||
caseNo string 案号
|
|
||||||
content string 公告内容
|
|
||||||
openTime string 开庭时间
|
|
||||||
litigant string 当事人,多个以顿号分开
|
|
||||||
plaintiff string 原告,多个以顿号分开
|
|
||||||
defendant string 被告,多个以顿号分开
|
|
||||||
caseCause string 案由
|
|
||||||
type string 公告类型
|
|
||||||
announcer string 公告人
|
|
||||||
litigant string 当事人
|
|
||||||
content string 公告内容
|
|
||||||
publishDate string 公告时间
|
|
||||||
baseInfo string 案件详情
|
|
||||||
baseInfo.caseCode string 案号
|
|
||||||
baseInfo.judgeDate string 裁判日期
|
|
||||||
baseInfo.title string 标题
|
|
||||||
baseInfo.publishDate string 发布日期
|
|
||||||
baseInfo.caseType string 案件类型
|
|
||||||
baseInfo.docType string 文书类型
|
|
||||||
baseInfo.caseCause string 案由
|
|
||||||
baseInfo.court string 法院
|
|
||||||
baseInfo.province string 省份
|
|
||||||
baseInfo.program string 审判程序
|
|
||||||
baseInfo.clauseContent string 审判依据
|
|
||||||
baseInfo.resultContent string 审判结果
|
|
||||||
baseInfo.content string 文书缩略内容
|
|
||||||
baseInfo.id string 裁判文书ID
|
|
||||||
roleList[] string 角色详情
|
|
||||||
roleList[].roleName string 角色
|
|
||||||
roleList[].standpoint string 立场
|
|
||||||
roleList[].entityName string 名称
|
|
||||||
roleList[].birthday string 生日
|
|
||||||
roleList[].gender string 性别
|
|
||||||
roleList[].nativePlace string 籍贯
|
|
||||||
roleList[].address string 地址
|
|
||||||
roleList[].workUnit string 工作单位
|
|
||||||
roleList[].nation string 民族
|
|
||||||
roleList[].organizationCode string 组织机构代码
|
|
||||||
roleList[].socialCreditCode string 统一社会信用代码
|
|
||||||
roleList[].idcard string 身份证
|
|
||||||
roleList[].jobTitle string 职位
|
|
||||||
judgeResultList[] string 判决结果
|
|
||||||
judgeResultList[].price string 涉案金额
|
|
||||||
judgeResultList[].realPrice string 实际金额
|
|
||||||
judgeResultList[].fromRole string 负担角色
|
|
||||||
judgeResultList[].way string 处置方式
|
|
||||||
judgeResultList[].toRole string 受让角色
|
|
||||||
judgeResultList[].priceType string 金额类型
|
|
||||||
judgeResultList[].priceUnit string 金额单位
|
|
||||||
judgeResultList[].currency string 币种
|
|
||||||
relationCaseList[] string 案件关联
|
|
||||||
relationCaseList[].caseCode string 案号
|
|
||||||
relationCaseList[].court string 法院
|
|
||||||
relationCaseList[].realationCaseCode string 关联案号
|
|
||||||
relationCaseList[].relationCaseId string 关联文书ID
|
|
||||||
areaName string 区域名称
|
|
||||||
litigant string 当事人
|
|
||||||
accuser string 原告
|
|
||||||
defender string 被告
|
|
||||||
others string 其他
|
|
||||||
courtName string 法院
|
|
||||||
caseCode string 案号
|
|
||||||
caseStat string 案件状态
|
|
||||||
openTime string 立案时间
|
|
||||||
trialTime string 开庭时间
|
|
||||||
closeTime string 结案时间
|
|
||||||
program string 审判程序
|
|
||||||
reason string 案由
|
|
||||||
caseType string 案件类型
|
|
||||||
target string 诉讼标的
|
|
||||||
@ -3,6 +3,7 @@ from fastapi import APIRouter
|
|||||||
from app.core.dependency import DependPermission
|
from app.core.dependency import DependPermission
|
||||||
|
|
||||||
from .apis import apis_router
|
from .apis import apis_router
|
||||||
|
from .app_users import app_users_router
|
||||||
from .auditlog import auditlog_router
|
from .auditlog import auditlog_router
|
||||||
from .base import base_router
|
from .base import base_router
|
||||||
from .depts import depts_router
|
from .depts import depts_router
|
||||||
@ -18,6 +19,7 @@ from .users import users_router
|
|||||||
v1_router = APIRouter()
|
v1_router = APIRouter()
|
||||||
|
|
||||||
v1_router.include_router(base_router, prefix="/base")
|
v1_router.include_router(base_router, prefix="/base")
|
||||||
|
v1_router.include_router(app_users_router, prefix="/app-user") # AppUser路由,无需权限依赖
|
||||||
v1_router.include_router(users_router, prefix="/user", dependencies=[DependPermission])
|
v1_router.include_router(users_router, prefix="/user", dependencies=[DependPermission])
|
||||||
v1_router.include_router(roles_router, prefix="/role", dependencies=[DependPermission])
|
v1_router.include_router(roles_router, prefix="/role", dependencies=[DependPermission])
|
||||||
v1_router.include_router(menus_router, prefix="/menu", dependencies=[DependPermission])
|
v1_router.include_router(menus_router, prefix="/menu", dependencies=[DependPermission])
|
||||||
|
|||||||
5
app/api/v1/app_users/__init__.py
Normal file
5
app/api/v1/app_users/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
from .app_users import router
|
||||||
|
|
||||||
|
app_users_router = APIRouter()
|
||||||
|
app_users_router.include_router(router, tags=["AppUser认证"])
|
||||||
134
app/api/v1/app_users/app_users.py
Normal file
134
app/api/v1/app_users/app_users.py
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from app.controllers.app_user import app_user_controller
|
||||||
|
from app.schemas.app_user import (
|
||||||
|
AppUserRegisterSchema,
|
||||||
|
AppUserLoginSchema,
|
||||||
|
AppUserJWTOut,
|
||||||
|
AppUserInfoOut,
|
||||||
|
AppUserUpdateSchema,
|
||||||
|
AppUserChangePasswordSchema
|
||||||
|
)
|
||||||
|
from app.utils.app_user_jwt import (
|
||||||
|
create_app_user_access_token,
|
||||||
|
get_current_app_user,
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES
|
||||||
|
)
|
||||||
|
from app.models.user import AppUser
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/register", response_model=dict, summary="用户注册")
|
||||||
|
async def register(
|
||||||
|
register_data: AppUserRegisterSchema
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
用户注册 - 只需要手机号
|
||||||
|
默认密码为手机号后六位
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user = await app_user_controller.register(register_data)
|
||||||
|
return {
|
||||||
|
"code": 200,
|
||||||
|
"message": "注册成功",
|
||||||
|
"data": {
|
||||||
|
"user_id": user.id,
|
||||||
|
"phone": user.phone,
|
||||||
|
"default_password": register_data.phone[-6:] # 返回默认密码供用户知晓
|
||||||
|
}
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/login", response_model=AppUserJWTOut, summary="用户登录")
|
||||||
|
async def login(
|
||||||
|
login_data: AppUserLoginSchema
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
用户登录
|
||||||
|
"""
|
||||||
|
user = await app_user_controller.authenticate(login_data)
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="手机号或密码错误"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 更新最后登录时间
|
||||||
|
await app_user_controller.update_last_login(user.id)
|
||||||
|
|
||||||
|
# 生成访问令牌
|
||||||
|
access_token = create_app_user_access_token(user.id, user.phone)
|
||||||
|
|
||||||
|
return AppUserJWTOut(
|
||||||
|
access_token=access_token,
|
||||||
|
token_type="bearer",
|
||||||
|
expires_in=ACCESS_TOKEN_EXPIRE_MINUTES * 60
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/logout", summary="用户登出")
|
||||||
|
async def logout(current_user: AppUser = Depends(get_current_app_user)):
|
||||||
|
"""
|
||||||
|
用户登出(客户端需要删除本地token)
|
||||||
|
"""
|
||||||
|
return {"code": 200, "message": "登出成功"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/profile", response_model=AppUserInfoOut, summary="获取用户信息")
|
||||||
|
async def get_profile(current_user: AppUser = Depends(get_current_app_user)):
|
||||||
|
"""
|
||||||
|
获取当前用户信息
|
||||||
|
"""
|
||||||
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/profile", response_model=AppUserInfoOut, summary="更新用户信息")
|
||||||
|
async def update_profile(
|
||||||
|
update_data: AppUserUpdateSchema,
|
||||||
|
current_user: AppUser = Depends(get_current_app_user)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
更新用户信息
|
||||||
|
"""
|
||||||
|
updated_user = await app_user_controller.update_user_info(current_user.id, update_data)
|
||||||
|
if not updated_user:
|
||||||
|
raise HTTPException(status_code=404, detail="用户不存在")
|
||||||
|
|
||||||
|
return updated_user
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/change-password", summary="修改密码")
|
||||||
|
async def change_password(
|
||||||
|
password_data: AppUserChangePasswordSchema,
|
||||||
|
current_user: AppUser = Depends(get_current_app_user)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
修改密码
|
||||||
|
"""
|
||||||
|
success = await app_user_controller.change_password(
|
||||||
|
current_user.id,
|
||||||
|
password_data.old_password,
|
||||||
|
password_data.new_password
|
||||||
|
)
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
raise HTTPException(status_code=400, detail="原密码错误")
|
||||||
|
|
||||||
|
return {"code": 200, "message": "密码修改成功"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/validate-token", summary="验证token")
|
||||||
|
async def validate_token(current_user: AppUser = Depends(get_current_app_user)):
|
||||||
|
"""
|
||||||
|
验证token是否有效
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"code": 200,
|
||||||
|
"message": "token有效",
|
||||||
|
"data": {
|
||||||
|
"user_id": current_user.id,
|
||||||
|
"phone": current_user.phone
|
||||||
|
}
|
||||||
|
}
|
||||||
124
app/controllers/app_user.py
Normal file
124
app/controllers/app_user.py
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
from app.models.user import AppUser
|
||||||
|
from app.schemas.app_user import AppUserRegisterSchema, AppUserLoginSchema, AppUserUpdateSchema
|
||||||
|
from app.utils.password import get_password_hash, verify_password
|
||||||
|
from app.core.crud import CRUDBase
|
||||||
|
from fastapi.exceptions import HTTPException
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class AppUserController(CRUDBase[AppUser, AppUserRegisterSchema, AppUserUpdateSchema]):
|
||||||
|
"""AppUser控制器"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(model=AppUser)
|
||||||
|
|
||||||
|
async def register(self, register_data: AppUserRegisterSchema) -> AppUser:
|
||||||
|
"""
|
||||||
|
用户注册 - 只需要手机号,默认使用手机号后六位作为密码
|
||||||
|
"""
|
||||||
|
# 检查手机号是否已存在
|
||||||
|
existing_user = await self.model.filter(phone=register_data.phone).first()
|
||||||
|
if existing_user:
|
||||||
|
raise HTTPException(status_code=400, detail="手机号已存在")
|
||||||
|
|
||||||
|
# 生成默认密码:手机号后六位
|
||||||
|
default_password = register_data.phone[-6:]
|
||||||
|
hashed_password = get_password_hash(default_password)
|
||||||
|
|
||||||
|
# 创建新用户
|
||||||
|
new_user = self.model(
|
||||||
|
phone=register_data.phone,
|
||||||
|
password=hashed_password,
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
|
||||||
|
await new_user.save()
|
||||||
|
return new_user
|
||||||
|
|
||||||
|
async def authenticate(self, login_data: AppUserLoginSchema) -> Optional[AppUser]:
|
||||||
|
"""
|
||||||
|
用户认证
|
||||||
|
"""
|
||||||
|
user = await self.model.filter(
|
||||||
|
phone=login_data.phone, is_active=True
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not verify_password(login_data.password, user.password):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
async def get_user_by_id(self, user_id: int) -> Optional[AppUser]:
|
||||||
|
"""
|
||||||
|
根据ID获取用户
|
||||||
|
"""
|
||||||
|
return await self.model.filter(id=user_id, is_active=True).first()
|
||||||
|
|
||||||
|
async def get_user_by_phone(self, phone: str) -> Optional[AppUser]:
|
||||||
|
"""
|
||||||
|
根据手机号获取用户
|
||||||
|
"""
|
||||||
|
return await self.model.filter(phone=phone, is_active=True).first()
|
||||||
|
|
||||||
|
async def update_last_login(self, user_id: int) -> bool:
|
||||||
|
"""
|
||||||
|
更新最后登录时间
|
||||||
|
"""
|
||||||
|
user = await self.model.filter(id=user_id).first()
|
||||||
|
if user:
|
||||||
|
user.last_login = datetime.now()
|
||||||
|
await user.save()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def update_user_info(self, user_id: int, update_data: AppUserUpdateSchema) -> Optional[AppUser]:
|
||||||
|
"""
|
||||||
|
更新用户信息
|
||||||
|
"""
|
||||||
|
user = await self.model.filter(id=user_id).first()
|
||||||
|
if not user:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 更新字段
|
||||||
|
update_dict = update_data.model_dump(exclude_unset=True)
|
||||||
|
for field, value in update_dict.items():
|
||||||
|
setattr(user, field, value)
|
||||||
|
|
||||||
|
await user.save()
|
||||||
|
return user
|
||||||
|
|
||||||
|
async def change_password(self, user_id: int, old_password: str, new_password: str) -> bool:
|
||||||
|
"""
|
||||||
|
修改密码
|
||||||
|
"""
|
||||||
|
user = await self.model.filter(id=user_id).first()
|
||||||
|
if not user:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 验证原密码
|
||||||
|
if not verify_password(old_password, user.password):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 更新密码
|
||||||
|
user.password = get_password_hash(new_password)
|
||||||
|
await user.save()
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def deactivate_user(self, user_id: int) -> bool:
|
||||||
|
"""
|
||||||
|
停用用户
|
||||||
|
"""
|
||||||
|
user = await self.model.filter(id=user_id).first()
|
||||||
|
if user:
|
||||||
|
user.is_active = False
|
||||||
|
await user.save()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# 创建控制器实例
|
||||||
|
app_user_controller = AppUserController()
|
||||||
@ -4,3 +4,4 @@ from .esg import *
|
|||||||
from .index import *
|
from .index import *
|
||||||
from .industry import *
|
from .industry import *
|
||||||
from .policy import *
|
from .policy import *
|
||||||
|
from .user import *
|
||||||
25
app/models/user.py
Normal file
25
app/models/user.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from tortoise import fields
|
||||||
|
|
||||||
|
from app.schemas.menus import MenuType
|
||||||
|
|
||||||
|
from .base import BaseModel, TimestampMixin
|
||||||
|
from .enums import MethodType
|
||||||
|
|
||||||
|
|
||||||
|
class AppUser(BaseModel, TimestampMixin):
|
||||||
|
username = fields.CharField(max_length=20, unique=True, null=True, description="用户名称", index=True)
|
||||||
|
alias = fields.CharField(max_length=30, null=True, description="姓名", index=True)
|
||||||
|
email = fields.CharField(max_length=255, unique=True, null=True, description="邮箱", index=True)
|
||||||
|
phone = fields.CharField(max_length=20, unique=True, description="手机号", index=True)
|
||||||
|
password = fields.CharField(max_length=128, description="密码")
|
||||||
|
company_name = fields.CharField(max_length=100, null=True, description="公司名称", index=True)
|
||||||
|
company_address = fields.CharField(max_length=255, null=True, description="公司地址")
|
||||||
|
company_contact = fields.CharField(max_length=50, null=True, description="公司联系人")
|
||||||
|
company_phone = fields.CharField(max_length=20, null=True, description="公司电话")
|
||||||
|
company_email = fields.CharField(max_length=100, null=True, description="公司邮箱")
|
||||||
|
is_active = fields.BooleanField(default=True, description="是否激活", index=True)
|
||||||
|
last_login = fields.DatetimeField(null=True, description="最后登录时间", index=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
table = "app_user"
|
||||||
|
table_description = "用户表"
|
||||||
69
app/schemas/app_user.py
Normal file
69
app/schemas/app_user.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from pydantic import BaseModel, Field, validator
|
||||||
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class AppUserRegisterSchema(BaseModel):
|
||||||
|
"""AppUser注册Schema - 只需要手机号"""
|
||||||
|
phone: str = Field(..., description="手机号")
|
||||||
|
|
||||||
|
@validator('phone')
|
||||||
|
def validate_phone(cls, v):
|
||||||
|
if not re.match(r'^1[3-9]\d{9}$', v):
|
||||||
|
raise ValueError('手机号格式不正确')
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
class AppUserLoginSchema(BaseModel):
|
||||||
|
"""AppUser登录Schema"""
|
||||||
|
phone: str = Field(..., description="手机号")
|
||||||
|
password: str = Field(..., description="密码")
|
||||||
|
|
||||||
|
|
||||||
|
class AppUserJWTPayload(BaseModel):
|
||||||
|
"""AppUser JWT载荷"""
|
||||||
|
user_id: int
|
||||||
|
phone: str
|
||||||
|
exp: datetime
|
||||||
|
|
||||||
|
|
||||||
|
class AppUserJWTOut(BaseModel):
|
||||||
|
"""AppUser JWT输出"""
|
||||||
|
access_token: str
|
||||||
|
token_type: str = "bearer"
|
||||||
|
expires_in: int
|
||||||
|
|
||||||
|
|
||||||
|
class AppUserInfoOut(BaseModel):
|
||||||
|
"""AppUser信息输出"""
|
||||||
|
id: int
|
||||||
|
phone: str
|
||||||
|
nickname: Optional[str] = None
|
||||||
|
avatar: Optional[str] = None
|
||||||
|
company_name: Optional[str] = None
|
||||||
|
company_address: Optional[str] = None
|
||||||
|
company_contact: Optional[str] = None
|
||||||
|
company_phone: Optional[str] = None
|
||||||
|
company_email: Optional[str] = None
|
||||||
|
is_active: bool
|
||||||
|
last_login: Optional[datetime] = None
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
|
||||||
|
|
||||||
|
class AppUserUpdateSchema(BaseModel):
|
||||||
|
"""AppUser更新Schema"""
|
||||||
|
nickname: Optional[str] = Field(None, description="昵称")
|
||||||
|
avatar: Optional[str] = Field(None, description="头像")
|
||||||
|
company_name: Optional[str] = Field(None, description="公司名称")
|
||||||
|
company_address: Optional[str] = Field(None, description="公司地址")
|
||||||
|
company_contact: Optional[str] = Field(None, description="公司联系人")
|
||||||
|
company_phone: Optional[str] = Field(None, description="公司电话")
|
||||||
|
company_email: Optional[str] = Field(None, description="公司邮箱")
|
||||||
|
|
||||||
|
|
||||||
|
class AppUserChangePasswordSchema(BaseModel):
|
||||||
|
"""AppUser修改密码Schema"""
|
||||||
|
old_password: str = Field(..., description="原密码")
|
||||||
|
new_password: str = Field(..., description="新密码")
|
||||||
83
app/utils/app_user_jwt.py
Normal file
83
app/utils/app_user_jwt.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Optional
|
||||||
|
import jwt
|
||||||
|
from fastapi import HTTPException, status, Depends
|
||||||
|
from fastapi.security import HTTPBearer
|
||||||
|
from app.controllers.app_user import app_user_controller
|
||||||
|
from app.schemas.app_user import AppUserJWTPayload
|
||||||
|
from app.settings import settings
|
||||||
|
|
||||||
|
# JWT配置
|
||||||
|
SECRET_KEY = settings.SECRET_KEY
|
||||||
|
ALGORITHM = "HS256"
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 # 7天
|
||||||
|
|
||||||
|
security = HTTPBearer()
|
||||||
|
|
||||||
|
|
||||||
|
def create_app_user_access_token(user_id: int, phone: str) -> str:
|
||||||
|
"""
|
||||||
|
为AppUser创建访问令牌
|
||||||
|
"""
|
||||||
|
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||||
|
to_encode = {
|
||||||
|
"user_id": user_id,
|
||||||
|
"phone": phone,
|
||||||
|
"exp": expire
|
||||||
|
}
|
||||||
|
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||||
|
return encoded_jwt
|
||||||
|
|
||||||
|
|
||||||
|
def verify_app_user_token(token: str) -> Optional[AppUserJWTPayload]:
|
||||||
|
"""
|
||||||
|
验证AppUser令牌
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||||
|
user_id: int = payload.get("user_id")
|
||||||
|
phone: str = payload.get("phone")
|
||||||
|
exp: datetime = datetime.fromtimestamp(payload.get("exp"))
|
||||||
|
|
||||||
|
if user_id is None or phone is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return AppUserJWTPayload(user_id=user_id, phone=phone, exp=exp)
|
||||||
|
except jwt.DecodeError:
|
||||||
|
return None
|
||||||
|
except jwt.ExpiredSignatureError:
|
||||||
|
return None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_app_user_id(token: str = Depends(security)) -> int:
|
||||||
|
"""
|
||||||
|
从令牌中获取当前AppUser ID
|
||||||
|
"""
|
||||||
|
credentials_exception = HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="无效的认证凭据",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = verify_app_user_token(token.credentials)
|
||||||
|
if payload is None:
|
||||||
|
raise credentials_exception
|
||||||
|
|
||||||
|
return payload.user_id
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_app_user(
|
||||||
|
current_user_id: int = Depends(get_current_app_user_id)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
获取当前AppUser
|
||||||
|
"""
|
||||||
|
user = await app_user_controller.get_user_by_id(current_user_id)
|
||||||
|
if user is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="用户不存在或已被停用"
|
||||||
|
)
|
||||||
|
return user
|
||||||
Loading…
x
Reference in New Issue
Block a user