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 .apis import apis_router
|
||||
from .app_users import app_users_router
|
||||
from .auditlog import auditlog_router
|
||||
from .base import base_router
|
||||
from .depts import depts_router
|
||||
@ -18,6 +19,7 @@ from .users import users_router
|
||||
v1_router = APIRouter()
|
||||
|
||||
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(roles_router, prefix="/role", 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 .industry 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