reglow 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. reglow/README.md +162 -0
  2. reglow/common/__init__.py +1 -0
  3. reglow/common/exception_handler.py +205 -0
  4. reglow/common/exceptions.py +136 -0
  5. reglow/common/i18n.py +77 -0
  6. reglow/common/middleware.py +323 -0
  7. reglow/common/response.py +33 -0
  8. reglow/common/utils/__init__.py +4 -0
  9. reglow/common/utils/date_utils.py +27 -0
  10. reglow/common/utils/parse_user_agent.py +27 -0
  11. reglow/common/utils/string_utils.py +29 -0
  12. reglow/core/__init__.py +1 -0
  13. reglow/core/cache.py +19 -0
  14. reglow/core/config.py +112 -0
  15. reglow/core/database.py +98 -0
  16. reglow/core/dependencies.py +150 -0
  17. reglow/core/dependencies_client.py +46 -0
  18. reglow/core/identity.py +27 -0
  19. reglow/core/logging_config.py +154 -0
  20. reglow/core/observability.py +100 -0
  21. reglow/core/plugin.py +93 -0
  22. reglow/core/protocols.py +43 -0
  23. reglow/core/rate_limit.py +28 -0
  24. reglow/core/redis_client.py +130 -0
  25. reglow/core/redis_keys.py +51 -0
  26. reglow/core/security.py +50 -0
  27. reglow/core/sms.py +313 -0
  28. reglow/modules/__init__.py +1 -0
  29. reglow/modules/agreement/__init__.py +1 -0
  30. reglow/modules/agreement/controller.py +723 -0
  31. reglow/modules/agreement/model.py +43 -0
  32. reglow/modules/agreement/router.py +4 -0
  33. reglow/modules/agreement/schema.py +125 -0
  34. reglow/modules/ai/__init__.py +1 -0
  35. reglow/modules/ai/controller.py +1084 -0
  36. reglow/modules/ai/router.py +2 -0
  37. reglow/modules/article/__init__.py +0 -0
  38. reglow/modules/article/controller.py +243 -0
  39. reglow/modules/article/model.py +44 -0
  40. reglow/modules/article/repository.py +193 -0
  41. reglow/modules/article/router.py +2 -0
  42. reglow/modules/article/schema.py +127 -0
  43. reglow/modules/article/service.py +224 -0
  44. reglow/modules/auth/__init__.py +1 -0
  45. reglow/modules/auth/controller.py +132 -0
  46. reglow/modules/auth/router.py +4 -0
  47. reglow/modules/auth/schema.py +46 -0
  48. reglow/modules/auth/service.py +805 -0
  49. reglow/modules/config/__init__.py +1 -0
  50. reglow/modules/config/controller.py +316 -0
  51. reglow/modules/config/model.py +25 -0
  52. reglow/modules/config/router.py +3 -0
  53. reglow/modules/config/schema.py +34 -0
  54. reglow/modules/customer_service/__init__.py +0 -0
  55. reglow/modules/customer_service/controller.py +278 -0
  56. reglow/modules/customer_service/model.py +78 -0
  57. reglow/modules/customer_service/repository.py +223 -0
  58. reglow/modules/customer_service/router.py +4 -0
  59. reglow/modules/customer_service/schema.py +173 -0
  60. reglow/modules/customer_service/service.py +662 -0
  61. reglow/modules/dept/__init__.py +1 -0
  62. reglow/modules/dept/controller.py +104 -0
  63. reglow/modules/dept/model.py +15 -0
  64. reglow/modules/dept/repository.py +206 -0
  65. reglow/modules/dept/router.py +2 -0
  66. reglow/modules/dept/schema.py +62 -0
  67. reglow/modules/dept/service.py +81 -0
  68. reglow/modules/dict/__init__.py +1 -0
  69. reglow/modules/dict/controller.py +97 -0
  70. reglow/modules/dict/model.py +26 -0
  71. reglow/modules/dict/repository.py +95 -0
  72. reglow/modules/dict/router.py +2 -0
  73. reglow/modules/dict/schema.py +67 -0
  74. reglow/modules/dict/service.py +74 -0
  75. reglow/modules/employee/__init__.py +1 -0
  76. reglow/modules/employee/controller.py +111 -0
  77. reglow/modules/employee/employee_dept_model.py +20 -0
  78. reglow/modules/employee/employee_role_model.py +16 -0
  79. reglow/modules/employee/model.py +44 -0
  80. reglow/modules/employee/repository.py +154 -0
  81. reglow/modules/employee/router.py +4 -0
  82. reglow/modules/employee/schema.py +206 -0
  83. reglow/modules/employee/service.py +206 -0
  84. reglow/modules/log/__init__.py +1 -0
  85. reglow/modules/log/controller.py +60 -0
  86. reglow/modules/log/model.py +36 -0
  87. reglow/modules/log/router.py +2 -0
  88. reglow/modules/log/schema.py +35 -0
  89. reglow/modules/material/__init__.py +1 -0
  90. reglow/modules/material/browser_extractor.py +474 -0
  91. reglow/modules/material/controller.py +910 -0
  92. reglow/modules/material/model.py +42 -0
  93. reglow/modules/material/router.py +3 -0
  94. reglow/modules/material/schema.py +119 -0
  95. reglow/modules/menu/__init__.py +1 -0
  96. reglow/modules/menu/controller.py +56 -0
  97. reglow/modules/menu/model.py +20 -0
  98. reglow/modules/menu/repository.py +51 -0
  99. reglow/modules/menu/router.py +2 -0
  100. reglow/modules/menu/schema.py +56 -0
  101. reglow/modules/menu/service.py +60 -0
  102. reglow/modules/message/__init__.py +0 -0
  103. reglow/modules/message/controller.py +270 -0
  104. reglow/modules/message/model.py +41 -0
  105. reglow/modules/message/repository.py +192 -0
  106. reglow/modules/message/router.py +3 -0
  107. reglow/modules/message/schema.py +98 -0
  108. reglow/modules/message/service.py +190 -0
  109. reglow/modules/notice/__init__.py +0 -0
  110. reglow/modules/notice/controller.py +147 -0
  111. reglow/modules/notice/model.py +27 -0
  112. reglow/modules/notice/repository.py +176 -0
  113. reglow/modules/notice/router.py +2 -0
  114. reglow/modules/notice/schema.py +45 -0
  115. reglow/modules/notice/service.py +151 -0
  116. reglow/modules/photo/__init__.py +0 -0
  117. reglow/modules/photo/controller.py +711 -0
  118. reglow/modules/photo/model.py +48 -0
  119. reglow/modules/photo/router.py +2 -0
  120. reglow/modules/photo/schema.py +136 -0
  121. reglow/modules/post/__init__.py +1 -0
  122. reglow/modules/post/controller.py +44 -0
  123. reglow/modules/post/model.py +12 -0
  124. reglow/modules/post/repository.py +56 -0
  125. reglow/modules/post/router.py +2 -0
  126. reglow/modules/post/schema.py +27 -0
  127. reglow/modules/post/service.py +32 -0
  128. reglow/modules/role/__init__.py +1 -0
  129. reglow/modules/role/controller.py +95 -0
  130. reglow/modules/role/model.py +34 -0
  131. reglow/modules/role/repository.py +93 -0
  132. reglow/modules/role/router.py +3 -0
  133. reglow/modules/role/schema.py +43 -0
  134. reglow/modules/role/service.py +53 -0
  135. reglow/modules/user/__init__.py +1 -0
  136. reglow/modules/user/controller.py +90 -0
  137. reglow/modules/user/controller_client.py +120 -0
  138. reglow/modules/user/model.py +49 -0
  139. reglow/modules/user/repository.py +90 -0
  140. reglow/modules/user/router.py +5 -0
  141. reglow/modules/user/schema.py +158 -0
  142. reglow/modules/user/service.py +233 -0
  143. reglow/modules/video/__init__.py +0 -0
  144. reglow/modules/video/controller.py +266 -0
  145. reglow/modules/video/model.py +43 -0
  146. reglow/modules/video/repository.py +190 -0
  147. reglow/modules/video/router.py +2 -0
  148. reglow/modules/video/schema.py +121 -0
  149. reglow/modules/video/service.py +228 -0
  150. reglow-0.3.0.dist-info/METADATA +404 -0
  151. reglow-0.3.0.dist-info/RECORD +154 -0
  152. reglow-0.3.0.dist-info/WHEEL +5 -0
  153. reglow-0.3.0.dist-info/licenses/LICENSE +201 -0
  154. reglow-0.3.0.dist-info/top_level.txt +1 -0
reglow/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # Reglow Python 后端基础库
2
+
3
+ > **包名**:`reglow` | **版本**:0.3.0 | **Python**:>=3.12
4
+
5
+ ## 概述
6
+
7
+ `reglow` 是基于 FastAPI + SQLAlchemy 2.0(async)的全栈后台基础库,提供 19 个即用业务模块,通过 `pip install -e .` 安装后可直接导入使用。
8
+
9
+ ## 目录结构
10
+
11
+ ```
12
+ reglow/
13
+ ├── core/ # 框架核心层
14
+ │ ├── config.py # Settings 配置(pydantic-settings,自动读 .env)
15
+ │ ├── database.py # 异步引擎/会话/Base/AutoBigInt/Mixin
16
+ │ ├── security.py # 密码哈希、JWT 签发/验证
17
+ │ ├── dependencies.py # get_current_employee / PermissionChecker
18
+ │ ├── redis_client.py # Redis 客户端
19
+ │ ├── cache.py # 缓存封装
20
+ │ ├── rate_limit.py # 限流
21
+ │ ├── sms.py # 短信发送
22
+ │ ├── observability.py # 健康检查路由
23
+ │ └── logging_config.py # 日志配置
24
+
25
+ ├── common/ # 跨模块共享层
26
+ │ ├── response.py # ApiResponse[T] / PageData[T] 统一响应
27
+ │ ├── exceptions.py # AppException / ErrorCode 枚举
28
+ │ ├── exception_handler.py # 全局异常处理注册
29
+ │ ├── middleware.py # 中间件注册(CORS/日志/i18n)
30
+ │ └── i18n.py # 国际化(Accept-Language 头)
31
+
32
+ └── modules/ # 业务模块层(19 个模块)
33
+ ├── auth/ # 认证(登录/注册/验证码/Token刷新)
34
+ ├── employee/ # 员工管理
35
+ ├── user/ # 用户管理(注册用户,区别于员工)
36
+ ├── role/ # 角色管理(含数据范围)
37
+ ├── menu/ # 菜单管理(树形)
38
+ ├── dept/ # 部门管理(树形)
39
+ ├── post/ # 岗位管理
40
+ ├── dict/ # 字典管理(类型+数据)
41
+ ├── config/ # 参数配置
42
+ ├── log/ # 日志(登录+操作)
43
+ ├── material/ # 素材中心(图片/视频/文件)
44
+ ├── agreement/ # 协议管理
45
+ ├── notice/ # 通知公告
46
+ ├── message/ # 系统消息+模板+收件箱
47
+ ├── article/ # 文章管理(含分类+文集)
48
+ ├── photo/ # 照片管理(含分类+相册)
49
+ ├── video/ # 视频管理(含分类+视频集)
50
+ ├── ai/ # AI(对话/图片生成/视频生成)
51
+ └── customer_service/ # 智能客服(知识库/会话/SDK)
52
+ ```
53
+
54
+ ## 模块六层架构
55
+
56
+ 每个模块遵循统一的分层结构,依赖单向向下:
57
+
58
+ ```
59
+ model.py ORM 实体(继承 Base + Mixin)
60
+
61
+ schema.py Pydantic 契约(CreateRequest / UpdateRequest / Response)
62
+
63
+ repository.py 数据访问层(AsyncSession,find_/create_/update_)
64
+
65
+ service.py 业务逻辑层(编排 repository,抛 AppException)
66
+
67
+ controller.py 接口控制层(APIRouter + 权限校验 + ApiResponse)
68
+
69
+ router.py 路由导出(from .controller import router)
70
+ ```
71
+
72
+ ## 安装
73
+
74
+ ```bash
75
+ # 可编辑模式(开发)
76
+ cd reglow && pip install -e .
77
+ ```
78
+
79
+ ## 使用
80
+
81
+ ```python
82
+ # main.py — 消费方应用入口
83
+ from fastapi import FastAPI, APIRouter
84
+ from reglow.common.middleware import register_middlewares
85
+ from reglow.common.exception_handler import register_exception_handlers
86
+
87
+ # 导入需要的模块路由
88
+ from reglow.modules.auth.router import router as auth_router
89
+ from reglow.modules.employee.router import router as employee_router
90
+ # ... 按需导入
91
+
92
+ app = FastAPI(title="My App")
93
+ register_middlewares(app)
94
+ register_exception_handlers(app)
95
+
96
+ admin_api = APIRouter(prefix="/admin/api/v1")
97
+ admin_api.include_router(auth_router)
98
+ admin_api.include_router(employee_router)
99
+ app.include_router(admin_api)
100
+ ```
101
+
102
+ ## 核心基础设施
103
+
104
+ ### 统一响应
105
+
106
+ ```python
107
+ from reglow.common.response import ApiResponse, PageData
108
+
109
+ # 成功
110
+ return ApiResponse.success(data)
111
+ return ApiResponse.success(PageData(items=..., total=..., page=1, size=20))
112
+
113
+ # 失败
114
+ return ApiResponse.fail("操作失败")
115
+ ```
116
+
117
+ ### 异常处理
118
+
119
+ ```python
120
+ from reglow.common.exceptions import AppException, ErrorCode
121
+
122
+ raise AppException(ErrorCode.NOT_FOUND, "留言不存在", 404)
123
+ raise AppException(ErrorCode.BAD_REQUEST, "参数错误", 400)
124
+ ```
125
+
126
+ ### 权限校验
127
+
128
+ ```python
129
+ from reglow.core.dependencies import PermissionChecker, get_current_employee
130
+
131
+ @router.get("/feedbacks", dependencies=[Depends(PermissionChecker("business:feedback:list"))])
132
+ async def list_feedbacks(db: AsyncSession = Depends(get_db)):
133
+ ...
134
+ ```
135
+
136
+ ### 数据库模型
137
+
138
+ ```python
139
+ from reglow.core.database import AutoBigInt, Base, TimestampMixin, SoftDeleteMixin
140
+ from sqlalchemy import Column, String
141
+
142
+ class Feedback(TimestampMixin, SoftDeleteMixin, Base):
143
+ __tablename__ = "biz_feedback"
144
+ id = Column(AutoBigInt, primary_key=True, autoincrement=True)
145
+ name = Column(String(100), nullable=False, comment="名称")
146
+ ```
147
+
148
+ ## 配置(.env)
149
+
150
+ ```env
151
+ APP_NAME=MyApp
152
+ DEBUG=True
153
+ DB_DRIVER=sqlite # 或 mysql
154
+ DB_NAME=myapp
155
+ JWT_SECRET=your-secret
156
+ REDIS_URL=redis://localhost:6379/0
157
+ ```
158
+
159
+ ## 参考文档
160
+
161
+ - [Reglow 基础库改造与集成手册](../reglow-docs/content/2.tutorials/3.framework-intro/Reglow基础库改造与集成小白实操手册.md)
162
+ - [reglow-app/backend 示例项目](../reglow-app/backend/)
@@ -0,0 +1 @@
1
+ """Common - 跨模块共享"""
@@ -0,0 +1,205 @@
1
+ """全局异常处理器 - 一个兜底,万事大吉"""
2
+ import logging
3
+
4
+ from fastapi import Request
5
+ from fastapi.responses import JSONResponse
6
+ from fastapi.exceptions import RequestValidationError
7
+ from pydantic import ValidationError
8
+
9
+ from reglow.common.exceptions import AppException
10
+ from reglow.common.exceptions import ErrorCode
11
+ from reglow.common.i18n import get_locale_from_request, translate
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ _ERROR_MSG = {
16
+ "zh-CN": {
17
+ "missing": "字段不能为空",
18
+ "value_error.missing": "字段不能为空",
19
+ "string_type": "请输入字符串",
20
+ "int_type": "请输入整数",
21
+ "float_type": "请输入数字",
22
+ "bool_type": "请输入布尔值",
23
+ "list_type": "请输入列表",
24
+ "string_too_short": "长度不能少于 {min_length} 个字符",
25
+ "string_too_long": "长度不能超过 {max_length} 个字符",
26
+ "int_too_small": "值不能小于 {ge}",
27
+ "int_too_big": "值不能大于 {le}",
28
+ "float_too_small": "值不能小于 {ge}",
29
+ "float_too_big": "值不能大于 {le}",
30
+ "greater_than_equal": "值不能小于 {ge}",
31
+ "less_than_equal": "值不能大于 {le}",
32
+ "pattern_regex": "格式不正确",
33
+ "value_error": "{error}",
34
+ "json_invalid": "JSON 格式不正确",
35
+ "url_parsing": "URL 格式不正确",
36
+ "missing_sole": "字段不能为空",
37
+ },
38
+ "en-US": {
39
+ "missing": " is required",
40
+ "value_error.missing": " is required",
41
+ "string_type": " must be a string",
42
+ "int_type": " must be an integer",
43
+ "float_type": " must be a number",
44
+ "bool_type": " must be a boolean",
45
+ "list_type": " must be a list",
46
+ "string_too_short": " length must be at least {min_length} characters",
47
+ "string_too_long": " length must be at most {max_length} characters",
48
+ "int_too_small": " must be greater than or equal to {ge}",
49
+ "int_too_big": " must be less than or equal to {le}",
50
+ "float_too_small": " must be greater than or equal to {ge}",
51
+ "float_too_big": " must be less than or equal to {le}",
52
+ "greater_than_equal": " must be greater than or equal to {ge}",
53
+ "less_than_equal": " must be less than or equal to {le}",
54
+ "pattern_regex": " format is invalid",
55
+ "value_error": ": {error}",
56
+ "json_invalid": "Invalid JSON format",
57
+ "url_parsing": " URL format is invalid",
58
+ "missing_sole": " is required",
59
+ },
60
+ }
61
+
62
+ _FIELD_NAME = {
63
+ "zh-CN": {
64
+ "name": "名称",
65
+ "code": "编码",
66
+ "type": "类型",
67
+ "icon": "图标",
68
+ "path": "路径",
69
+ "component": "组件",
70
+ "permission": "权限标识",
71
+ "sort": "排序",
72
+ "status": "状态",
73
+ "description": "描述",
74
+ "remark": "备注",
75
+ "username": "用户名",
76
+ "password": "密码",
77
+ "new_password": "新密码",
78
+ "real_name": "真实姓名",
79
+ "nickname": "昵称",
80
+ "phone": "手机号",
81
+ "email": "邮箱",
82
+ "sms_code": "短信验证码",
83
+ "captcha_key": "验证码key",
84
+ "captcha_code": "验证码",
85
+ "slide_x": "滑动位置",
86
+ "employee_no": "工号",
87
+ "gender": "性别",
88
+ "dept_id": "部门",
89
+ "superior_id": "直属上级",
90
+ "role_ids": "角色",
91
+ "post_ids": "岗位",
92
+ "employment_type": "用工类型",
93
+ "job_level": "职级",
94
+ "job_title": "职称",
95
+ "entry_date": "入职日期",
96
+ "work_status": "在职状态",
97
+ "avatar": "头像",
98
+ "data_scope": "数据范围",
99
+ "menu_ids": "菜单列表",
100
+ "dept_ids": "部门列表",
101
+ "parent_id": "父级",
102
+ "visible": "是否显示",
103
+ "cache": "是否缓存",
104
+ "label": "标签",
105
+ "value": "值",
106
+ "source": "来源",
107
+ "group_ids": "分组",
108
+ "key": "键名",
109
+ "content": "内容",
110
+ "agreement_type": "协议类型",
111
+ },
112
+ "en-US": {
113
+ "name": "Name",
114
+ "code": "Code",
115
+ "type": "Type",
116
+ "icon": "Icon",
117
+ "path": "Path",
118
+ "component": "Component",
119
+ "permission": "Permission",
120
+ "sort": "Sort",
121
+ "status": "Status",
122
+ "description": "Description",
123
+ "remark": "Remark",
124
+ "username": "Username",
125
+ "password": "Password",
126
+ "new_password": "New password",
127
+ "real_name": "Real name",
128
+ "nickname": "Nickname",
129
+ "phone": "Phone",
130
+ "email": "Email",
131
+ "sms_code": "SMS code",
132
+ "captcha_key": "Captcha key",
133
+ "captcha_code": "Captcha code",
134
+ "slide_x": "Slide position",
135
+ },
136
+ }
137
+
138
+
139
+ def _translate_validation_error(error: dict, locale: str = "zh-CN") -> str:
140
+ """将单个 Pydantic 错误翻译为当前语言"""
141
+ err_type = error.get("type", "")
142
+ loc = error.get("loc", ())
143
+ field = str(loc[-1]) if loc else ""
144
+ field_name = _FIELD_NAME.get(locale, _FIELD_NAME["zh-CN"]).get(field, field)
145
+ ctx = error.get("ctx", {})
146
+
147
+ if err_type == "value_error":
148
+ custom_msg = error.get("msg", "")
149
+ if custom_msg and not custom_msg.startswith(("Value error", "Assertion failed")):
150
+ return f"{field_name}: {custom_msg}"
151
+ return f"{field_name}: {ctx.get('error', translate('common.validation_failed', locale))}"
152
+
153
+ template = _ERROR_MSG.get(locale, _ERROR_MSG["zh-CN"]).get(err_type)
154
+ if template:
155
+ try:
156
+ msg = template.format(**ctx)
157
+ except (KeyError, IndexError):
158
+ msg = template
159
+ return f"{field_name}{msg}"
160
+
161
+ return f"{field_name}: {error.get('msg', translate('common.validation_failed', locale))}"
162
+
163
+
164
+ async def app_exception_handler(request: Request, exc: AppException):
165
+ """统一处理业务异常"""
166
+ locale = get_locale_from_request(request)
167
+ return JSONResponse(
168
+ status_code=exc.http_status,
169
+ content={"code": exc.code, "msg": translate(exc.msg, locale), "data": None},
170
+ )
171
+
172
+
173
+ async def validation_exception_handler(request: Request, exc: ValidationError):
174
+ """统一处理 Pydantic 验证异常 - 多语言友好提示"""
175
+ errors = exc.errors()
176
+ locale = get_locale_from_request(request)
177
+ logger.warning(
178
+ "[Validation] %s %s errors=%s",
179
+ request.method,
180
+ request.url.path,
181
+ errors,
182
+ )
183
+ msg = translate("common.validation_failed", locale) if not errors else _translate_validation_error(errors[0], locale)
184
+ return JSONResponse(
185
+ status_code=422,
186
+ content={"code": ErrorCode.PARAM_ERROR, "msg": msg, "data": None},
187
+ )
188
+
189
+
190
+ async def global_exception_handler(request: Request, exc: Exception):
191
+ """兜底 - 处理所有未预期的异常"""
192
+ logger.error(f"未预期错误: {exc}", exc_info=True)
193
+ locale = get_locale_from_request(request)
194
+ return JSONResponse(
195
+ status_code=500,
196
+ content={"code": ErrorCode.UNKNOWN, "msg": translate("common.server_error", locale), "data": None},
197
+ )
198
+
199
+
200
+ def register_exception_handlers(app):
201
+ """注册所有异常处理器"""
202
+ app.add_exception_handler(AppException, app_exception_handler)
203
+ app.add_exception_handler(RequestValidationError, validation_exception_handler)
204
+ app.add_exception_handler(ValidationError, validation_exception_handler)
205
+ app.add_exception_handler(Exception, global_exception_handler)
@@ -0,0 +1,136 @@
1
+ """统一异常码体系 - 按模块分段"""
2
+ from enum import IntEnum
3
+
4
+
5
+ class ErrorCode(IntEnum):
6
+ """统一错误码 - 前端拿到 code 就知道具体什么错误"""
7
+
8
+ # 通用 1000-1999
9
+ SUCCESS = 0
10
+ UNKNOWN = 1000
11
+ PARAM_ERROR = 1001
12
+ NOT_FOUND = 1002
13
+
14
+ # 认证 2000-2999
15
+ UNAUTHORIZED = 2000
16
+ TOKEN_EXPIRED = 2001
17
+ FORBIDDEN = 2002
18
+
19
+ # 员工 3000-3999
20
+ EMPLOYEE_NOT_FOUND = 3000
21
+ EMPLOYEE_USERNAME_EXISTS = 3001
22
+ EMPLOYEE_PHONE_EXISTS = 3002
23
+ EMPLOYEE_PASSWORD_ERROR = 3003
24
+ EMPLOYEE_DISABLED = 3004
25
+
26
+ # 角色 4000-4999
27
+ ROLE_NOT_FOUND = 4000
28
+ ROLE_CODE_EXISTS = 4001
29
+ ROLE_IN_USE = 4002
30
+
31
+ # 菜单 4100-4199
32
+ MENU_NOT_FOUND = 4100
33
+ MENU_HAS_CHILDREN = 4101
34
+
35
+ # 部门 4200-4299
36
+ DEPT_NOT_FOUND = 4200
37
+ DEPT_HAS_CHILDREN = 4201
38
+ DEPT_HAS_EMPLOYEES = 4202
39
+
40
+ # 岗位 4300-4399
41
+ POST_NOT_FOUND = 4300
42
+ POST_CODE_EXISTS = 4301
43
+ POST_IN_USE = 4302
44
+
45
+ # 字典 5000-5999
46
+ DICT_TYPE_NOT_FOUND = 5000
47
+ DICT_CODE_EXISTS = 5001
48
+ DICT_TYPE_IN_USE = 5002
49
+ DICT_SYSTEM_PROTECTED = 5003 # 系统内置字典禁止修改/删除
50
+
51
+ # 素材 6000-6999
52
+ MATERIAL_NOT_FOUND = 6000
53
+ MATERIAL_UPLOAD_FAILED = 6001
54
+ MATERIAL_GROUP_NOT_FOUND = 6002
55
+
56
+ # 配置 7000-7999
57
+ CONFIG_NOT_FOUND = 7000
58
+ CONFIG_KEY_EXISTS = 7001
59
+
60
+ # 通知 8000-8999
61
+ NOTICE_NOT_FOUND = 8000
62
+ SMS_SEND_FAILED = 8001
63
+
64
+ # 日志 9000-9999
65
+ LOG_NOT_FOUND = 9000
66
+
67
+ # 协议 9100-9199
68
+ AGREEMENT_NOT_FOUND = 9100
69
+ AGREEMENT_CODE_EXISTS = 9101
70
+ AGREEMENT_NOT_PUBLISHED = 9102
71
+ AGREEMENT_ALREADY_AGREED = 9103
72
+ AGREEMENT_HISTORY_NOT_FOUND = 9104
73
+ AGREEMENT_HISTORY_HAS_USERS = 9105
74
+ AGREEMENT_HAS_USERS = 9106
75
+ AGREEMENT_NEEDS_RECONFIRM = 9107
76
+
77
+ # 用户 9200-9299
78
+ USER_NOT_FOUND = 9200
79
+ USER_PHONE_EXISTS = 9201
80
+ USER_EMAIL_EXISTS = 9202
81
+ USER_PASSWORD_ERROR = 9203
82
+ USER_DISABLED = 9204
83
+
84
+ # 文章 9300-9399
85
+ ARTICLE_NOT_FOUND = 9300
86
+ ARTICLE_CATEGORY_NOT_FOUND = 9310
87
+ ARTICLE_CATEGORY_CODE_EXISTS = 9311
88
+ ARTICLE_CATEGORY_IN_USE = 9312
89
+ ARTICLE_COLLECTION_NOT_FOUND = 9320
90
+ ARTICLE_COLLECTION_NOT_EMPTY = 9321
91
+
92
+ # 相册/照片 9400-9499
93
+ ALBUM_NOT_FOUND = 9400
94
+ ALBUM_NOT_EMPTY = 9401
95
+ PHOTO_NOT_FOUND = 9410
96
+ PHOTO_COVER_EXISTS = 9411
97
+
98
+ # 视频 9500-9599
99
+ VIDEO_NOT_FOUND = 9500
100
+ VIDEO_CATEGORY_NOT_FOUND = 9510
101
+ VIDEO_CATEGORY_CODE_EXISTS = 9511
102
+ VIDEO_CATEGORY_IN_USE = 9512
103
+ VIDEO_COLLECTION_NOT_FOUND = 9520
104
+ VIDEO_COLLECTION_NOT_EMPTY = 9521
105
+
106
+ # 智能客服 9600-9699
107
+ CS_CONFIG_NOT_FOUND = 9600
108
+ CS_KB_NOT_FOUND = 9610
109
+ CS_CHUNK_NOT_FOUND = 9620
110
+ CS_SESSION_NOT_FOUND = 9630
111
+ CS_CONFIG_INACTIVE = 9640
112
+ CS_EMBEDDING_FAILED = 9650
113
+
114
+
115
+ class AppException(Exception):
116
+ """应用异常 - 业务代码只管 raise,不管怎么返回"""
117
+
118
+ def __init__(self, code: ErrorCode, msg: str | None = None, http_status: int = 400):
119
+ self.code = code
120
+ self.msg = msg or code.name
121
+ self.http_status = http_status
122
+
123
+
124
+ class NotFoundException(AppException):
125
+ def __init__(self, msg: str = "资源不存在"):
126
+ super().__init__(ErrorCode.NOT_FOUND, msg, 404)
127
+
128
+
129
+ class UnauthorizedException(AppException):
130
+ def __init__(self, msg: str = "未登录"):
131
+ super().__init__(ErrorCode.UNAUTHORIZED, msg, 401)
132
+
133
+
134
+ class ForbiddenException(AppException):
135
+ def __init__(self, msg: str = "无权限"):
136
+ super().__init__(ErrorCode.FORBIDDEN, msg, 403)
reglow/common/i18n.py ADDED
@@ -0,0 +1,77 @@
1
+ """轻量国际化工具"""
2
+ from typing import Any
3
+
4
+ DEFAULT_LOCALE = "zh-CN"
5
+ SUPPORTED_LOCALES: set[str] = {"zh-CN", "en-US"}
6
+
7
+ _MESSAGES: dict[str, dict[str, str]] = {
8
+ "zh-CN": {
9
+ "common.success": "操作成功",
10
+ "common.fail": "操作失败",
11
+ "common.server_error": "服务器内部错误",
12
+ "common.validation_failed": "参数校验失败",
13
+ "auth.login_success": "登录成功",
14
+ "auth.logout_success": "退出成功",
15
+ "auth.captcha_error": "验证码错误",
16
+ "auth.slide_verify_failed": "滑动验证失败,请重试",
17
+ "auth.sms_sent": "验证码发送成功",
18
+ },
19
+ "en-US": {
20
+ "common.success": "Success",
21
+ "common.fail": "Failed",
22
+ "common.server_error": "Internal server error",
23
+ "common.validation_failed": "Validation failed",
24
+ "auth.login_success": "Login successful",
25
+ "auth.logout_success": "Logout successful",
26
+ "auth.captcha_error": "Invalid verification code",
27
+ "auth.slide_verify_failed": "Slide verification failed, please try again",
28
+ "auth.sms_sent": "Verification code sent",
29
+ },
30
+ }
31
+
32
+
33
+ def register_locale(locale: str, messages: dict[str, str]) -> None:
34
+ """注册新的语言或追加翻译消息。
35
+
36
+ 如果 locale 已存在,messages 会合并到已有翻译中(新 key 覆盖旧 key)。
37
+ 如果 locale 不存在,则新增该语言。
38
+ """
39
+ SUPPORTED_LOCALES.add(locale)
40
+ if locale in _MESSAGES:
41
+ _MESSAGES[locale].update(messages)
42
+ else:
43
+ _MESSAGES[locale] = messages
44
+
45
+
46
+ def add_messages(locale: str, messages: dict[str, str]) -> None:
47
+ """向已有语言追加翻译消息(register_locale 的别名)"""
48
+ register_locale(locale, messages)
49
+
50
+
51
+ def normalize_locale(value: str | None) -> str:
52
+ if not value:
53
+ return DEFAULT_LOCALE
54
+ locale = value.split(",", 1)[0].split(";", 1)[0].strip()
55
+ if locale in SUPPORTED_LOCALES:
56
+ return locale
57
+ lower = locale.lower()
58
+ if lower.startswith("en"):
59
+ return "en-US"
60
+ if lower.startswith("zh"):
61
+ return "zh-CN"
62
+ return DEFAULT_LOCALE
63
+
64
+
65
+ def get_locale_from_request(request: Any) -> str:
66
+ headers = getattr(request, "headers", {}) or {}
67
+ return normalize_locale(headers.get("X-Locale") or headers.get("Accept-Language"))
68
+
69
+
70
+ def translate(key: str, locale: str = DEFAULT_LOCALE, **params: Any) -> str:
71
+ current = normalize_locale(locale)
72
+ text = _MESSAGES.get(current, {}).get(key) or _MESSAGES[DEFAULT_LOCALE].get(key) or key
73
+ return text.format(**params) if params else text
74
+
75
+
76
+ def translate_request(request: Any, key: str, **params: Any) -> str:
77
+ return translate(key, get_locale_from_request(request), **params)