feat(phase2): shared/ 共享层迁移
- shared/: enums/constants/pagination/base_model/base_db/base_repository/base_service - apps/constants 与 apps/models/base_model、base_db 改为垫片 - 新增 pagination 单元测试 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+2
-205
@@ -1,205 +1,2 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 一米阳光团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 一米阳光团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
# 性别
|
||||
GENDER_LIST = {
|
||||
1: "男",
|
||||
2: "女",
|
||||
3: "保密",
|
||||
}
|
||||
# 部门类型
|
||||
DEPT_TYPE_LIST = {
|
||||
1: "公司",
|
||||
2: "子公司",
|
||||
3: "部门",
|
||||
4: "小组",
|
||||
}
|
||||
|
||||
# 站点类型
|
||||
ITEM_TYPE_LIST = {
|
||||
1: "普通站点",
|
||||
2: "其他站点"
|
||||
}
|
||||
|
||||
# 友链类型:1友情链接 2合作伙伴
|
||||
LINK_TYPE_LIST = {
|
||||
1: "友情链接",
|
||||
2: "合作伙伴"
|
||||
}
|
||||
|
||||
# 友链投放平台:1PC站 2WAP站 3微信小程序 4APP应用
|
||||
LINK_PLATFORM_LIST = {
|
||||
1: "PC站",
|
||||
2: "WAP站",
|
||||
3: "微信小程序",
|
||||
4: "APP应用"
|
||||
}
|
||||
|
||||
# 友链形式:1文字链接 2图片链接
|
||||
LINK_FORM_LIST = {
|
||||
1: "文字链接",
|
||||
2: "图片链接",
|
||||
}
|
||||
|
||||
# 广告投放平台:1PC站 2WAP站 3微信小程序 4APP应用
|
||||
AD_SORT_PLATFORM_LIST = {
|
||||
1: "PC站",
|
||||
2: "WAP站",
|
||||
3: "微信小程序",
|
||||
4: "APP应用"
|
||||
}
|
||||
|
||||
# 广告类型:1图片 2文字 3视频 4推荐
|
||||
AD_TYPE_LIST = {
|
||||
1: "图片",
|
||||
2: "文字",
|
||||
3: "视频",
|
||||
4: "推荐"
|
||||
}
|
||||
|
||||
# 通知来源:1官方平台 2开源中国 3CSDN官方 4新浪微博
|
||||
NOTICE_SOURCE_LIST = {
|
||||
1: "官方平台",
|
||||
2: "开源中国",
|
||||
3: "CSDN官方",
|
||||
4: "新浪微博",
|
||||
}
|
||||
|
||||
# 会员注册来源:1-网站注册 2-客户端注册 3-小程序注册 4-手机站注册 5-后台添加
|
||||
MEMBER_SOURCE_LIST = {
|
||||
1: "网站注册",
|
||||
2: "客户端注册",
|
||||
3: "小程序注册",
|
||||
4: "手机站注册",
|
||||
5: "后台添加"
|
||||
}
|
||||
|
||||
# 配置项类型
|
||||
CONFIG_DATA_TYPE_LIST = {
|
||||
"text": "单行文本",
|
||||
"textarea": "多行文本",
|
||||
"ueditor": "富文本编辑器",
|
||||
"date": "日期",
|
||||
"datetime": "时间",
|
||||
"number": "数字",
|
||||
"select": "下拉框",
|
||||
"radio": "单选框",
|
||||
"checkbox": "复选框",
|
||||
"image": "单张图片",
|
||||
"images": "多张图片",
|
||||
"password": "密码",
|
||||
"icon": "字体图标",
|
||||
"file": "单个文件",
|
||||
"files": "多个文件",
|
||||
"hidden": "隐藏",
|
||||
"readonly": "只读文本",
|
||||
}
|
||||
|
||||
# 菜单类型:0-菜单 1-节点
|
||||
MENU_TYPE_LIST = {
|
||||
0: '菜单',
|
||||
1: '节点',
|
||||
}
|
||||
|
||||
# 打开方式:1-内部打开 2-外部打开
|
||||
MENU_TARGET_LIST = {
|
||||
1: '内部打开',
|
||||
2: '外部打开',
|
||||
}
|
||||
|
||||
# 城市级别
|
||||
CITY_LEVEL_LIST = {
|
||||
1: "省份",
|
||||
2: "城市",
|
||||
3: "县区",
|
||||
4: "街道"
|
||||
}
|
||||
|
||||
# 日志类型:1-登录,2-退出
|
||||
LOG_TYPE_LIST = {
|
||||
1: "登录",
|
||||
2: "退出",
|
||||
}
|
||||
|
||||
# 登录来源:0-后台,1-前台
|
||||
LOGIN_SOURCE_LIST = {
|
||||
0: "后台",
|
||||
1: "前台",
|
||||
}
|
||||
|
||||
# 登录状态:0-正常,1-异常
|
||||
LOGIN_STATUS_LIST = {
|
||||
0: "正常",
|
||||
1: "异常",
|
||||
}
|
||||
|
||||
# 操作日志来源:0-后台,1-前台
|
||||
OPERATION_SOURCE_LIST = {
|
||||
0: "后台",
|
||||
1: "前台",
|
||||
}
|
||||
|
||||
# 操作日志状态:0-正常,1-异常
|
||||
OPERATION_STATUS_LIST = {
|
||||
0: "正常",
|
||||
1: "异常",
|
||||
}
|
||||
|
||||
# 任务状态:0-未发布,1-运行中,2-暂停,3-删除
|
||||
JOB_STATUS_LIST = {
|
||||
0: "未发布",
|
||||
1: "运行中",
|
||||
2: "暂停",
|
||||
3: "删除",
|
||||
}
|
||||
|
||||
# 执行策略:1-立即执行,2-执行一次,3-放弃执行
|
||||
JOB_EXECUTE_POLICY_LIST = {
|
||||
1: "立即执行",
|
||||
2: "执行一次",
|
||||
3: "放弃执行",
|
||||
}
|
||||
|
||||
# 任务日志执行状态:0-正常 1-失败
|
||||
JOB_LOG_STATUS_LIST = {
|
||||
0: "正常",
|
||||
1: "失败",
|
||||
}
|
||||
# 兼容垫片:由 shared/enums.py 迁移,保留旧导入路径
|
||||
from shared.enums import * # noqa: F401,F403
|
||||
|
||||
@@ -1,85 +1,2 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 珊瑚云团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 珊瑚云团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
# ======================= 分页配置 ==========================
|
||||
# 该部分配置用于控制列表页面的数据分页显示
|
||||
# 通过调整每页记录数可以优化页面加载速度和用户体验
|
||||
# ==========================================================
|
||||
|
||||
# 默认每页显示记录数
|
||||
# 类型:int
|
||||
# 默认值:10
|
||||
# 说明:列表页面默认每页显示的数据条数,可根据实际需求调整
|
||||
PAGE_SIZE = 10
|
||||
|
||||
# ======================= 用户配置 ==========================
|
||||
# 该部分配置用于管理用户账户相关的默认设置
|
||||
# 包含新用户创建和密码重置时的默认值
|
||||
# ==========================================================
|
||||
|
||||
# 默认密码
|
||||
# 类型:str
|
||||
# 默认值:"123456"
|
||||
# 说明:新创建用户或重置密码时的默认密码,建议首次登录后强制修改
|
||||
DEFAULT_PASSWORD = "123456"
|
||||
|
||||
# ======================= 消息提示配置 ==========================
|
||||
# 该部分配置定义了所有操作响应的统一消息文本
|
||||
# 用于保持系统前后端消息提示的一致性
|
||||
# ==========================================================
|
||||
|
||||
# 操作成功提示信息
|
||||
# 类型:str
|
||||
# 默认值:"操作成功"
|
||||
# 说明:统一的操作成功返回消息,所有成功操作均使用此消息
|
||||
MESSAGE_OK = "操作成功"
|
||||
|
||||
# 操作失败提示信息
|
||||
# 类型:str
|
||||
# 默认值:"操作失败"
|
||||
# 说明:统一的操作失败返回消息,所有失败操作均使用此消息
|
||||
MESSAGE_FAIL = "操作失败"
|
||||
|
||||
# ======================= 序列化安全配置 ==========================
|
||||
# 模型序列化时统一排除的敏感字段,防止密码、盐值等泄露给前端
|
||||
# ==================================================================
|
||||
|
||||
SERIALIZE_EXCLUDE_FIELDS = frozenset({'is_delete', 'password', 'salt'})
|
||||
# 兼容垫片:由 shared/constants.py 迁移,保留旧导入路径
|
||||
from shared.constants import * # noqa: F401,F403
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 一米阳光团队 版权所有
|
||||
# | 开源协议: Apache-2.0
|
||||
# +======================================================================
|
||||
|
||||
"""跨模块共享层:枚举、常量、分页、模型基类、Repository 基类。"""
|
||||
|
||||
from shared.constants import ( # noqa: F401
|
||||
PAGE_SIZE,
|
||||
DEFAULT_PASSWORD,
|
||||
MESSAGE_OK,
|
||||
MESSAGE_FAIL,
|
||||
SERIALIZE_EXCLUDE_FIELDS,
|
||||
)
|
||||
from shared.enums import * # noqa: F401,F403
|
||||
@@ -0,0 +1,63 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 一米阳光团队 版权所有
|
||||
# | 开源协议: Apache-2.0
|
||||
# +======================================================================
|
||||
# 由 apps/models/base_db.py 迁移而来。
|
||||
|
||||
"""
|
||||
数据库操作基类模块
|
||||
=============================================================
|
||||
|
||||
提供数据库模型的通用操作方法,包括:
|
||||
- 单条数据保存(save):add + flush,不提交事务
|
||||
- 多条数据批量保存(save_all):add_all + flush,不提交事务
|
||||
- 数据删除(delete):delete + flush,不提交事务
|
||||
|
||||
事务边界由 db_session_middleware 统一管理:
|
||||
- 请求成功且为写操作 → 自动 commit
|
||||
- 请求异常 → 自动 rollback
|
||||
- 业务代码无需关心事务提交
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from core.database import get_db_session
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class base_db:
|
||||
__abstract__ = True
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
保存当前实例到数据库
|
||||
|
||||
将对象添加到会话并 flush(发送 SQL 到数据库获取自增 ID),
|
||||
但不提交事务。事务由中间件统一管理。
|
||||
|
||||
Returns:
|
||||
self: 当前实例,支持链式调用。通过 self.id 可获取自增主键。
|
||||
"""
|
||||
db = get_db_session()
|
||||
db.add(self)
|
||||
db.flush()
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def save_all(*args):
|
||||
"""批量保存多个实例到数据库"""
|
||||
if not args:
|
||||
return
|
||||
db = get_db_session()
|
||||
db.add_all(args)
|
||||
db.flush()
|
||||
|
||||
def delete(self):
|
||||
"""从数据库删除当前实例"""
|
||||
db = get_db_session()
|
||||
db.delete(self)
|
||||
db.flush()
|
||||
return True
|
||||
@@ -0,0 +1,149 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 一米阳光团队 版权所有
|
||||
# | 开源协议: Apache-2.0
|
||||
# +======================================================================
|
||||
# 由 apps/models/base_model.py 迁移而来。
|
||||
|
||||
"""数据库模型基类:通用字段、序列化与通用查询方法。"""
|
||||
|
||||
import logging
|
||||
from datetime import date, datetime, timezone
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from sqlalchemy import Column, Integer, DATETIME, and_
|
||||
|
||||
from core import R
|
||||
from core.database import Base, get_db_session
|
||||
from shared.constants import SERIALIZE_EXCLUDE_FIELDS
|
||||
from utils.request import parse_id_list
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_UTC_NOW = lambda: datetime.now(tz=timezone.utc)
|
||||
|
||||
|
||||
class base_model(Base):
|
||||
"""
|
||||
数据库模型基类
|
||||
|
||||
所有业务模型应继承此类,自动获得通用字段和公共方法。
|
||||
|
||||
提供的功能:
|
||||
1. 通用字段:id、创建人、创建时间、更新人、更新时间、软删除标识
|
||||
2. 序列化方法:to_dict()
|
||||
3. 查询方法:get_by_id()、get_by_ids()、get_all_active()、exists()
|
||||
4. 删除方法:batch_delete_r()
|
||||
"""
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
# ============================================================
|
||||
# 通用字段定义
|
||||
# ============================================================
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True, comment="主键ID")
|
||||
create_user = Column(Integer, default=0, comment="创建人")
|
||||
create_time = Column(DATETIME, default=_UTC_NOW, comment="创建时间")
|
||||
update_user = Column(Integer, default=0, comment="更新人")
|
||||
update_time = Column(DATETIME, default=_UTC_NOW, onupdate=_UTC_NOW, comment="更新时间")
|
||||
is_delete = Column(Integer, default=0, comment="删除标识:0-正常 1-已删除")
|
||||
|
||||
# ============================================================
|
||||
# 序列化方法
|
||||
# ============================================================
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""
|
||||
将模型实例转换为字典
|
||||
|
||||
自动排除敏感字段(is_delete、password、salt)。
|
||||
datetime 类型格式化为字符串,UTC 时间转为本地时间便于前端展示。
|
||||
"""
|
||||
result = {}
|
||||
for column in self.__table__.columns:
|
||||
if column.name in SERIALIZE_EXCLUDE_FIELDS:
|
||||
continue
|
||||
value = getattr(self, column.name)
|
||||
if isinstance(value, datetime):
|
||||
if value.tzinfo is not None:
|
||||
# UTC 时间转为本地时间
|
||||
value = value.astimezone()
|
||||
value = value.strftime('%Y-%m-%d %H:%M:%S')
|
||||
elif isinstance(value, date):
|
||||
# 纯日期类型(如生日)无时分秒,单独格式化,避免 JSON 序列化失败
|
||||
value = value.strftime('%Y-%m-%d')
|
||||
result[column.name] = value
|
||||
return result
|
||||
|
||||
# ============================================================
|
||||
# 通用查询方法
|
||||
# ============================================================
|
||||
|
||||
@classmethod
|
||||
def get_by_id(cls, id: Optional[int]) -> Optional['base_model']:
|
||||
"""根据ID获取未删除的记录,不存在返回 None"""
|
||||
if id is None or id <= 0:
|
||||
return None
|
||||
db = get_db_session()
|
||||
return db.query(cls).filter(cls.id == id, cls.is_delete == 0).first()
|
||||
|
||||
@classmethod
|
||||
def get_by_ids(cls, ids: List[int]) -> List['base_model']:
|
||||
"""根据ID列表批量获取未删除的记录"""
|
||||
if not ids:
|
||||
return []
|
||||
valid_ids = [i for i in ids if i and i > 0]
|
||||
if not valid_ids:
|
||||
return []
|
||||
db = get_db_session()
|
||||
return db.query(cls).filter(cls.id.in_(valid_ids), cls.is_delete == 0).all()
|
||||
|
||||
@classmethod
|
||||
def get_all_active(cls) -> List['base_model']:
|
||||
"""获取所有未删除的记录"""
|
||||
db = get_db_session()
|
||||
return db.query(cls).filter(cls.is_delete == 0).all()
|
||||
|
||||
@classmethod
|
||||
def exists(cls, id: Optional[int]) -> bool:
|
||||
"""检查未删除记录是否存在"""
|
||||
if id is None or id <= 0:
|
||||
return False
|
||||
db = get_db_session()
|
||||
return db.query(cls).filter(cls.id == id, cls.is_delete == 0).count() > 0
|
||||
|
||||
# ============================================================
|
||||
# 删除方法
|
||||
# ============================================================
|
||||
|
||||
@classmethod
|
||||
def batch_delete_r(cls, ids_str: Optional[str]):
|
||||
"""
|
||||
批量软删除并返回R对象
|
||||
|
||||
封装ID校验、解析、软删除的完整流程。
|
||||
|
||||
Args:
|
||||
ids_str: 逗号分隔的ID字符串,如 "1,2,3"
|
||||
|
||||
Returns:
|
||||
R: 操作结果
|
||||
"""
|
||||
if not ids_str:
|
||||
return R.failed("记录ID不存在")
|
||||
|
||||
id_list = parse_id_list(ids_str)
|
||||
if not id_list:
|
||||
return R.failed("记录ID不存在")
|
||||
|
||||
db = get_db_session()
|
||||
records = db.query(cls).filter(and_(cls.id.in_(id_list), cls.is_delete == 0)).all()
|
||||
if len(records) != len(id_list):
|
||||
return R.failed("记录不存在")
|
||||
|
||||
for record in records:
|
||||
record.is_delete = 1
|
||||
|
||||
return R.ok(msg="本次共删除{0}条数据".format(len(records)))
|
||||
@@ -0,0 +1,134 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 一米阳光团队 版权所有
|
||||
# | 开源协议: Apache-2.0
|
||||
# +======================================================================
|
||||
|
||||
"""通用 Repository 基类:封装各模型的数据访问层公共操作。
|
||||
|
||||
各模块 repository.py 继承本类并提供自定义查询,service 通过 repository 访问数据库。
|
||||
过渡期与 base_model 的 classmethod 双轨并存,全部模块迁移完成后收敛。
|
||||
"""
|
||||
|
||||
from typing import Generic, Optional, TypeVar
|
||||
|
||||
from core.database import get_db_session
|
||||
from shared.constants import PAGE_SIZE
|
||||
from utils.request import parse_id_list
|
||||
|
||||
M = TypeVar("M")
|
||||
|
||||
|
||||
class BaseRepository(Generic[M]):
|
||||
"""模型数据访问基类。"""
|
||||
|
||||
# 软删除列名,JobLogRepository 覆盖为 "del_flag"
|
||||
soft_delete_col: str = "is_delete"
|
||||
|
||||
def __init__(self, model_cls):
|
||||
self.model = model_cls
|
||||
|
||||
@property
|
||||
def db(self):
|
||||
"""当前请求的数据库会话。"""
|
||||
return get_db_session()
|
||||
|
||||
def _soft_col(self):
|
||||
return getattr(self.model, self.soft_delete_col)
|
||||
|
||||
# ============================================================
|
||||
# 基础查询
|
||||
# ============================================================
|
||||
|
||||
def get_by_id(self, obj_id: Optional[int]) -> Optional[M]:
|
||||
"""根据ID获取未删除记录。"""
|
||||
if obj_id is None or obj_id <= 0:
|
||||
return None
|
||||
return self.db.query(self.model).filter(
|
||||
self.model.id == obj_id, self._soft_col() == 0
|
||||
).first()
|
||||
|
||||
def get_by_ids(self, ids) -> list:
|
||||
"""根据ID列表批量获取未删除记录。"""
|
||||
valid = [i for i in ids if i and i > 0]
|
||||
if not valid:
|
||||
return []
|
||||
return self.db.query(self.model).filter(
|
||||
self.model.id.in_(valid), self._soft_col() == 0
|
||||
).all()
|
||||
|
||||
def get_all_active(self) -> list:
|
||||
"""获取所有未删除记录。"""
|
||||
return self.db.query(self.model).filter(self._soft_col() == 0).all()
|
||||
|
||||
def exists(self, obj_id: Optional[int]) -> bool:
|
||||
"""检查未删除记录是否存在。"""
|
||||
return self.get_by_id(obj_id) is not None
|
||||
|
||||
# ============================================================
|
||||
# 分页查询
|
||||
# ============================================================
|
||||
|
||||
def paginate(self, page_no: int = 1, page_size: int = PAGE_SIZE, query=None):
|
||||
"""
|
||||
分页查询,返回 (items, total)。自动追加软删过滤。
|
||||
传入 query 可在外部先构建过滤条件。
|
||||
"""
|
||||
q = query if query is not None else self.db.query(self.model)
|
||||
q = q.filter(self._soft_col() == 0)
|
||||
total = q.count()
|
||||
items = q.limit(page_size).offset((page_no - 1) * page_size).all()
|
||||
return items, total
|
||||
|
||||
# ============================================================
|
||||
# 写操作
|
||||
# ============================================================
|
||||
|
||||
def create(self, **fields) -> M:
|
||||
"""创建记录(不提交事务,由中间件统一管理)。"""
|
||||
obj = self.model(**fields)
|
||||
obj.save()
|
||||
return obj
|
||||
|
||||
def update(self, obj_id, data: dict) -> int:
|
||||
"""按ID更新,返回受影响行数。"""
|
||||
return self.db.query(self.model).filter(
|
||||
self.model.id == obj_id, self._soft_col() == 0
|
||||
).update(data)
|
||||
|
||||
def soft_delete(self, ids) -> bool:
|
||||
"""
|
||||
批量软删除(等价 base_model.batch_delete_r 的语义)。
|
||||
|
||||
Args:
|
||||
ids: 逗号分隔字符串或整数列表
|
||||
|
||||
Returns:
|
||||
bool: 是否全部删除成功(ID 数需与匹配记录数一致)
|
||||
"""
|
||||
id_list = parse_id_list(ids) if isinstance(ids, str) else [i for i in ids if i]
|
||||
if not id_list:
|
||||
return False
|
||||
records = self.db.query(self.model).filter(
|
||||
self.model.id.in_(id_list), self._soft_col() == 0
|
||||
).all()
|
||||
if len(records) != len(id_list):
|
||||
return False
|
||||
for record in records:
|
||||
setattr(record, self.soft_delete_col, 1)
|
||||
return True
|
||||
|
||||
def hard_delete(self, obj) -> bool:
|
||||
"""物理删除实例。"""
|
||||
self.db.delete(obj)
|
||||
self.db.flush()
|
||||
return True
|
||||
|
||||
# ============================================================
|
||||
# 自定义查询出口
|
||||
# ============================================================
|
||||
|
||||
def filter(self, *criterion):
|
||||
"""返回过滤后的 query,供子类组合复杂查询。"""
|
||||
return self.db.query(self.model).filter(*criterion)
|
||||
@@ -0,0 +1,30 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 一米阳光团队 版权所有
|
||||
# | 开源协议: Apache-2.0
|
||||
# +======================================================================
|
||||
|
||||
"""Service 基类(可选):为面向对象的 service 提供 repository 与响应便捷属性。
|
||||
|
||||
本项目现有 service 为模块级函数风格,如后续需要类风格 service 可继承本类。
|
||||
"""
|
||||
|
||||
from core import response as R
|
||||
|
||||
|
||||
class BaseService:
|
||||
"""Service 基类:绑定 repository 与统一响应助手。"""
|
||||
|
||||
repo = None
|
||||
|
||||
def __init__(self, repo=None):
|
||||
self.repo = repo or self.repo
|
||||
|
||||
@property
|
||||
def ok(self):
|
||||
return R.ok
|
||||
|
||||
@property
|
||||
def failed(self):
|
||||
return R.failed
|
||||
@@ -0,0 +1,28 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 一米阳光团队 版权所有
|
||||
# | 开源协议: Apache-2.0
|
||||
# +======================================================================
|
||||
# 由 apps/constants/message.py 迁移而来。
|
||||
|
||||
"""全局常量:分页、默认密码、消息文本、序列化排除字段。"""
|
||||
|
||||
# ======================= 分页配置 ==========================
|
||||
# 默认每页显示记录数
|
||||
PAGE_SIZE = 10
|
||||
|
||||
# ======================= 用户配置 ==========================
|
||||
# 默认密码(新创建用户或重置密码时的默认密码,建议首次登录后强制修改)
|
||||
DEFAULT_PASSWORD = "123456"
|
||||
|
||||
# ======================= 消息提示配置 ==========================
|
||||
# 操作成功提示信息
|
||||
MESSAGE_OK = "操作成功"
|
||||
|
||||
# 操作失败提示信息
|
||||
MESSAGE_FAIL = "操作失败"
|
||||
|
||||
# ======================= 序列化安全配置 ==========================
|
||||
# 模型序列化时统一排除的敏感字段,防止密码、盐值等泄露给前端
|
||||
SERIALIZE_EXCLUDE_FIELDS = frozenset({'is_delete', 'password', 'salt'})
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 一米阳光团队 版权所有
|
||||
# | 开源协议: Apache-2.0
|
||||
# +======================================================================
|
||||
# 由 apps/constants/constants.py 迁移而来。
|
||||
|
||||
"""业务枚举字典:各模块的状态/类型展示映射。"""
|
||||
|
||||
# 性别
|
||||
GENDER_LIST = {
|
||||
1: "男",
|
||||
2: "女",
|
||||
3: "保密",
|
||||
}
|
||||
# 部门类型
|
||||
DEPT_TYPE_LIST = {
|
||||
1: "公司",
|
||||
2: "子公司",
|
||||
3: "部门",
|
||||
4: "小组",
|
||||
}
|
||||
|
||||
# 站点类型
|
||||
ITEM_TYPE_LIST = {
|
||||
1: "普通站点",
|
||||
2: "其他站点"
|
||||
}
|
||||
|
||||
# 友链类型:1友情链接 2合作伙伴
|
||||
LINK_TYPE_LIST = {
|
||||
1: "友情链接",
|
||||
2: "合作伙伴"
|
||||
}
|
||||
|
||||
# 友链投放平台:1PC站 2WAP站 3微信小程序 4APP应用
|
||||
LINK_PLATFORM_LIST = {
|
||||
1: "PC站",
|
||||
2: "WAP站",
|
||||
3: "微信小程序",
|
||||
4: "APP应用"
|
||||
}
|
||||
|
||||
# 友链形式:1文字链接 2图片链接
|
||||
LINK_FORM_LIST = {
|
||||
1: "文字链接",
|
||||
2: "图片链接",
|
||||
}
|
||||
|
||||
# 广告投放平台:1PC站 2WAP站 3微信小程序 4APP应用
|
||||
AD_SORT_PLATFORM_LIST = {
|
||||
1: "PC站",
|
||||
2: "WAP站",
|
||||
3: "微信小程序",
|
||||
4: "APP应用"
|
||||
}
|
||||
|
||||
# 广告类型:1图片 2文字 3视频 4推荐
|
||||
AD_TYPE_LIST = {
|
||||
1: "图片",
|
||||
2: "文字",
|
||||
3: "视频",
|
||||
4: "推荐"
|
||||
}
|
||||
|
||||
# 通知来源:1官方平台 2开源中国 3CSDN官方 4新浪微博
|
||||
NOTICE_SOURCE_LIST = {
|
||||
1: "官方平台",
|
||||
2: "开源中国",
|
||||
3: "CSDN官方",
|
||||
4: "新浪微博",
|
||||
}
|
||||
|
||||
# 会员注册来源:1-网站注册 2-客户端注册 3-小程序注册 4-手机站注册 5-后台添加
|
||||
MEMBER_SOURCE_LIST = {
|
||||
1: "网站注册",
|
||||
2: "客户端注册",
|
||||
3: "小程序注册",
|
||||
4: "手机站注册",
|
||||
5: "后台添加"
|
||||
}
|
||||
|
||||
# 配置项类型
|
||||
CONFIG_DATA_TYPE_LIST = {
|
||||
"text": "单行文本",
|
||||
"textarea": "多行文本",
|
||||
"ueditor": "富文本编辑器",
|
||||
"date": "日期",
|
||||
"datetime": "时间",
|
||||
"number": "数字",
|
||||
"select": "下拉框",
|
||||
"radio": "单选框",
|
||||
"checkbox": "复选框",
|
||||
"image": "单张图片",
|
||||
"images": "多张图片",
|
||||
"password": "密码",
|
||||
"icon": "字体图标",
|
||||
"file": "单个文件",
|
||||
"files": "多个文件",
|
||||
"hidden": "隐藏",
|
||||
"readonly": "只读文本",
|
||||
}
|
||||
|
||||
# 菜单类型:0-菜单 1-节点
|
||||
MENU_TYPE_LIST = {
|
||||
0: '菜单',
|
||||
1: '节点',
|
||||
}
|
||||
|
||||
# 打开方式:1-内部打开 2-外部打开
|
||||
MENU_TARGET_LIST = {
|
||||
1: '内部打开',
|
||||
2: '外部打开',
|
||||
}
|
||||
|
||||
# 城市级别
|
||||
CITY_LEVEL_LIST = {
|
||||
1: "省份",
|
||||
2: "城市",
|
||||
3: "县区",
|
||||
4: "街道"
|
||||
}
|
||||
|
||||
# 日志类型:1-登录,2-退出
|
||||
LOG_TYPE_LIST = {
|
||||
1: "登录",
|
||||
2: "退出",
|
||||
}
|
||||
|
||||
# 登录来源:0-后台,1-前台
|
||||
LOGIN_SOURCE_LIST = {
|
||||
0: "后台",
|
||||
1: "前台",
|
||||
}
|
||||
|
||||
# 登录状态:0-正常,1-异常
|
||||
LOGIN_STATUS_LIST = {
|
||||
0: "正常",
|
||||
1: "异常",
|
||||
}
|
||||
|
||||
# 操作日志来源:0-后台,1-前台
|
||||
OPERATION_SOURCE_LIST = {
|
||||
0: "后台",
|
||||
1: "前台",
|
||||
}
|
||||
|
||||
# 操作日志状态:0-正常,1-异常
|
||||
OPERATION_STATUS_LIST = {
|
||||
0: "正常",
|
||||
1: "异常",
|
||||
}
|
||||
|
||||
# 任务状态:0-未发布,1-运行中,2-暂停,3-删除
|
||||
JOB_STATUS_LIST = {
|
||||
0: "未发布",
|
||||
1: "运行中",
|
||||
2: "暂停",
|
||||
3: "删除",
|
||||
}
|
||||
|
||||
# 执行策略:1-立即执行,2-执行一次,3-放弃执行
|
||||
JOB_EXECUTE_POLICY_LIST = {
|
||||
1: "立即执行",
|
||||
2: "执行一次",
|
||||
3: "放弃执行",
|
||||
}
|
||||
|
||||
# 任务日志执行状态:0-正常 1-失败
|
||||
JOB_LOG_STATUS_LIST = {
|
||||
0: "正常",
|
||||
1: "失败",
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 一米阳光团队 版权所有
|
||||
# | 开源协议: Apache-2.0
|
||||
# +======================================================================
|
||||
|
||||
"""分页统一封装:解析 pageNo/pageSize 并对 SQLAlchemy query 应用分页。"""
|
||||
|
||||
from shared.constants import PAGE_SIZE
|
||||
|
||||
|
||||
def get_page_params(query_params, default_size: int = PAGE_SIZE) -> tuple[int, int]:
|
||||
"""
|
||||
从 request.query_params 解析分页参数。
|
||||
|
||||
Args:
|
||||
query_params: request.query_params(类 dict 对象)
|
||||
default_size: 默认每页条数
|
||||
|
||||
Returns:
|
||||
(page, size):page>=1,size>=1
|
||||
"""
|
||||
try:
|
||||
page = int(query_params.get('pageNo', 1) or 1)
|
||||
except (TypeError, ValueError):
|
||||
page = 1
|
||||
try:
|
||||
size = int(query_params.get('pageSize', default_size) or default_size)
|
||||
except (TypeError, ValueError):
|
||||
size = default_size
|
||||
|
||||
if page < 1:
|
||||
page = 1
|
||||
if size < 1:
|
||||
size = default_size
|
||||
return page, size
|
||||
|
||||
|
||||
def paginate_query(query, page: int, size: int) -> tuple[list, int]:
|
||||
"""
|
||||
对 SQLAlchemy query 应用分页并返回 (items, total)。
|
||||
|
||||
Args:
|
||||
query: 已构建的 SQLAlchemy Query 对象(不含 count/limit/offset)
|
||||
page: 页码(>=1)
|
||||
size: 每页条数
|
||||
|
||||
Returns:
|
||||
(items, total):当前页数据列表与总记录数
|
||||
"""
|
||||
total = query.count()
|
||||
items = query.limit(size).offset((page - 1) * size).all()
|
||||
return items, total
|
||||
@@ -0,0 +1,21 @@
|
||||
"""shared/pagination.py 分页工具单元测试。"""
|
||||
|
||||
from shared.pagination import get_page_params
|
||||
|
||||
|
||||
def test_defaults():
|
||||
page, size = get_page_params({})
|
||||
assert page == 1
|
||||
assert size == 10
|
||||
|
||||
|
||||
def test_valid_params():
|
||||
page, size = get_page_params({"pageNo": "3", "pageSize": "20"})
|
||||
assert page == 3
|
||||
assert size == 20
|
||||
|
||||
|
||||
def test_invalid_params_fall_back():
|
||||
page, size = get_page_params({"pageNo": "abc", "pageSize": "0"})
|
||||
assert page == 1
|
||||
assert size == 10
|
||||
Reference in New Issue
Block a user