app_user: 用户体系

This commit is contained in:
邹方成 2025-10-02 09:44:54 +08:00
parent 8dc8e13019
commit bd5b2e5cbb
9 changed files with 443 additions and 302 deletions

302
aaa.txt
View File

@ -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 诉讼标的

View File

@ -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])

View 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认证"])

View 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
View 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()

View File

@ -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
View 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
View 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
View 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