hframe 0.1.0__tar.gz

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 (43) hide show
  1. hframe-0.1.0/PKG-INFO +1329 -0
  2. hframe-0.1.0/README.md +1312 -0
  3. hframe-0.1.0/pyproject.toml +29 -0
  4. hframe-0.1.0/setup.cfg +4 -0
  5. hframe-0.1.0/src/hframe/__init__.py +108 -0
  6. hframe-0.1.0/src/hframe/config.py +107 -0
  7. hframe-0.1.0/src/hframe/exceptions.py +126 -0
  8. hframe-0.1.0/src/hframe/handler/__init__.py +13 -0
  9. hframe-0.1.0/src/hframe/handler/config.py +108 -0
  10. hframe-0.1.0/src/hframe/handler/generic.py +501 -0
  11. hframe-0.1.0/src/hframe/handler/hooks.py +64 -0
  12. hframe-0.1.0/src/hframe/handler/request.py +77 -0
  13. hframe-0.1.0/src/hframe/infra/__init__.py +32 -0
  14. hframe-0.1.0/src/hframe/infra/cache.py +108 -0
  15. hframe-0.1.0/src/hframe/infra/db_util.py +134 -0
  16. hframe-0.1.0/src/hframe/infra/mysql.py +1231 -0
  17. hframe-0.1.0/src/hframe/infra/redis.py +929 -0
  18. hframe-0.1.0/src/hframe/infra/wechat.py +327 -0
  19. hframe-0.1.0/src/hframe/logging.py +95 -0
  20. hframe-0.1.0/src/hframe/registry.py +111 -0
  21. hframe-0.1.0/src/hframe/repository/__init__.py +9 -0
  22. hframe-0.1.0/src/hframe/repository/base.py +212 -0
  23. hframe-0.1.0/src/hframe/repository/version.py +85 -0
  24. hframe-0.1.0/src/hframe/service/__init__.py +12 -0
  25. hframe-0.1.0/src/hframe/service/config.py +140 -0
  26. hframe-0.1.0/src/hframe/service/data.py +346 -0
  27. hframe-0.1.0/src/hframe/service/generic.py +482 -0
  28. hframe-0.1.0/src/hframe/util/__init__.py +14 -0
  29. hframe-0.1.0/src/hframe/util/auth.py +154 -0
  30. hframe-0.1.0/src/hframe/util/crypto.py +33 -0
  31. hframe-0.1.0/src/hframe/util/data.py +162 -0
  32. hframe-0.1.0/src/hframe/util/format.py +92 -0
  33. hframe-0.1.0/src/hframe/util/transform.py +43 -0
  34. hframe-0.1.0/src/hframe/util/verify.py +40 -0
  35. hframe-0.1.0/src/hframe/util/yaml_util.py +26 -0
  36. hframe-0.1.0/src/hframe/view/__init__.py +12 -0
  37. hframe-0.1.0/src/hframe/view/base.py +395 -0
  38. hframe-0.1.0/src/hframe/view/h5.py +42 -0
  39. hframe-0.1.0/src/hframe.egg-info/PKG-INFO +1329 -0
  40. hframe-0.1.0/src/hframe.egg-info/SOURCES.txt +41 -0
  41. hframe-0.1.0/src/hframe.egg-info/dependency_links.txt +1 -0
  42. hframe-0.1.0/src/hframe.egg-info/requires.txt +34 -0
  43. hframe-0.1.0/src/hframe.egg-info/top_level.txt +1 -0
hframe-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,1329 @@
1
+ Metadata-Version: 2.1
2
+ Name: hframe
3
+ Version: 0.1.0
4
+ Summary: 分层架构后端框架 — Handler → Service → Repository
5
+ Author-email: Huey <421425339@qq.com>
6
+ License: MIT
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+ Provides-Extra: django
10
+ Provides-Extra: mysql
11
+ Provides-Extra: redis
12
+ Provides-Extra: wechat
13
+ Provides-Extra: infra
14
+ Provides-Extra: all
15
+ Provides-Extra: test
16
+ Provides-Extra: dev
17
+
18
+ # heims 使用说明
19
+
20
+ > 基础后端管理框架 — 基于 Django 的后端快速开发框架
21
+ > 版本:1.1.14 | 作者:Huey | Python >= 3.7.4
22
+
23
+ ---
24
+
25
+ ## 目录
26
+
27
+ 1. [项目概述](#1-项目概述)
28
+ 2. [安装与依赖](#2-安装与依赖)
29
+ 3. [配置管理](#3-配置管理)
30
+ 4. [核心模块详解](#4-核心模块详解)
31
+ - 4.1 [配置模块 (config.py)](#41-配置模块-configpy)
32
+ - 4.2 [日志工具 (log_util.py)](#42-日志工具-log_utilpy)
33
+ - 4.3 [通用工具函数 (functions.py)](#43-通用工具函数-functionspy)
34
+ - 4.4 [异常定义 (exceptions.py)](#44-异常定义-exceptionspy)
35
+ - 4.5 [缓存层 (redis_util.py)](#45-缓存层-redis_utilpy)
36
+ - 4.6 [数据库层 (mysql_util.py)](#46-数据库层-mysql_utilpy)
37
+ - 4.7 [服务层](#47-服务层)
38
+ - 4.8 [视图层 (common_views.py)](#48-视图层-common_viewspy)
39
+ - 4.9 [微信工具 (wechat_util.py)](#49-微信工具-wechat_utilpy)
40
+ 5. [初始化机制](#5-初始化机制)
41
+ 6. [视图开发指南](#6-视图开发指南)
42
+ - 6.1 [BaseView — 基础视图](#61-baseview--基础视图)
43
+ - 6.2 [ModuleView — 模块视图](#62-moduleview--模块视图)
44
+ - 6.3 [ListView — 列表查询](#63-listview--列表查询)
45
+ - 6.4 [DetailView — 详情查询](#64-detailview--详情查询)
46
+ - 6.5 [EditView — 添加/编辑](#65-editview--添加编辑)
47
+ - 6.6 [DeleteView — 删除](#66-deleteview--删除)
48
+ - 6.7 [H5 微信视图](#67-h5-微信视图)
49
+ 7. [数据库操作指南](#7-数据库操作指南)
50
+ - 7.1 [连接与配置](#71-连接与配置)
51
+ - 7.2 [基础查询](#72-基础查询)
52
+ - 7.3 [条件筛选 (search_dict)](#73-条件筛选-search_dict)
53
+ - 7.4 [批量操作与事务](#74-批量操作与事务)
54
+ - 7.5 [DataService — 关联数据服务](#75-dataservice--关联数据服务)
55
+ 8. [缓存操作指南](#8-缓存操作指南)
56
+ 9. [公开 API](#9-公开-api)
57
+
58
+ ---
59
+
60
+ ## 1. 项目概述
61
+
62
+ heims 是一个基于 Django 的后端快速开发框架,内置了以下能力:
63
+
64
+ | 能力 | 说明 |
65
+ |------|------|
66
+ | **配置管理** | 通过 `config.yaml` 外部化配置,覆盖默认值 |
67
+ | **数据库** | MySQL 客户端,支持 SSH 隧道、连接池(pool_size:5)、批量操作、事务、表结构缓存 |
68
+ | **缓存** | Redis 客户端,支持 SSH 隧道、连接池、哨兵/集群、序列化/压缩、`@cached` 装饰器 |
69
+ | **日志** | 按日期分割的日志文件,支持上下文管理器 |
70
+ | **认证** | Token 认证(Header / Session / GET / Cookie 多渠道获取) |
71
+ | **权限** | 页面权限 + 功能权限控制 |
72
+ | **视图模板** | 开箱即用的 CRUD 视图模板(列表/详情/编辑/删除) |
73
+ | **关联处理** | 外键(N:1)、多对多(N:N)、一对多(1:N)三种关联模式 |
74
+ | **微信** | 公众号 OAuth2.0 授权 + 小程序登录,自动创建用户 |
75
+ | **工具函数** | 加密、树形结构、字典操作、命名转换、类型检查等 |
76
+
77
+ 核心设计模式:
78
+
79
+ - **抽象基类 + 实现**:`CacheClient/RedisClient`、`DBClient/MySQLWithSSH` — 方便切换后端
80
+ - **工厂模式**:`RedisUtil.new_redis_client()`、`MySQLUtil.new_db()`
81
+ - **Mixin 模式**:`H5Mixin` 组合注入微信登录能力
82
+ - **模板方法模式**:`pre_get → do_get → ap_get` 生命周期
83
+ - **多级缓存**:表结构(内存 → Redis → DB → 文件)
84
+
85
+ ---
86
+
87
+ ## 2. 安装与依赖
88
+
89
+ ### 安装
90
+
91
+ ```bash
92
+ pip install heims
93
+ ```
94
+
95
+ ### 核心依赖
96
+
97
+ | 包 | 用途 |
98
+ |----|------|
99
+ | Django >= 2.0.7 | Web 框架 |
100
+ | DBUtils >= 3.1.2 | 数据库连接池 |
101
+ | mysqlclient >= 1.4.6 | MySQL 驱动 |
102
+ | redis >= 4.3.6 | Redis 客户端 |
103
+ | sshtunnel == 0.3.2 | SSH 隧道 |
104
+ | requests >= 2.31.0 | HTTP 客户端 |
105
+ | PyYAML >= 6.0 | YAML 配置解析 |
106
+
107
+ ---
108
+
109
+ ## 3. 配置管理
110
+
111
+ 框架通过 `config.yaml` 文件进行配置覆盖。在项目根目录下创建 `config.yaml`:
112
+
113
+ ```yaml
114
+ # config.yaml 示例
115
+ BASE_INFO:
116
+ secret_key: "your-secret-key"
117
+ auth_token_name: "my_auth_token"
118
+ admin_role_id: 1
119
+ user_table:
120
+ user_info: "sys_user"
121
+ user_role: "sys_user_role"
122
+ user_permission: "sys_user_permission"
123
+
124
+ MYSQL_INFO:
125
+ ssh_enable: true
126
+ ssh_host: "10.0.0.1"
127
+ ssh_port: 22
128
+ ssh_user: "root"
129
+ ssh_password: "your-ssh-password"
130
+ host: "127.0.0.1"
131
+ port: 3306
132
+ user: "db_user"
133
+ password: "db_password"
134
+ database: "my_database"
135
+ pool_size: 5
136
+
137
+ REDIS_INFO:
138
+ ssh_enable: true
139
+ ssh_host: "10.0.0.1"
140
+ ssh_port: 22
141
+ ssh_user: "root"
142
+ ssh_password: "your-ssh-password"
143
+ host: "127.0.0.1"
144
+ port: 6379
145
+ password: "redis-password"
146
+ db: 0
147
+ max_connections: 50
148
+
149
+ WECHAT_INFO:
150
+ applet:
151
+ app_id: "wx_applet_appid"
152
+ app_secret: "wx_applet_secret"
153
+ public_account:
154
+ app_id: "wx_pa_appid"
155
+ app_secret: "wx_pa_secret"
156
+ ```
157
+
158
+ **配置说明**:
159
+
160
+ | 配置项 | 类型 | 用途 |
161
+ |--------|------|------|
162
+ | `BASE_INFO.secret_key` | str | 加密盐值,用于 Token 加密 |
163
+ | `BASE_INFO.auth_token_name` | str | Token 在请求中的键名 |
164
+ | `BASE_INFO.admin_role_id` | int | 管理员角色 ID |
165
+ | `BASE_INFO.user_table` | dict | 用户相关表名映射 |
166
+ | `MYSQL_INFO` | dict | MySQL 数据库连接配置 |
167
+ | `REDIS_INFO` | dict | Redis 缓存连接配置 |
168
+ | `WECHAT_INFO` | dict | 微信小程序和公众号配置 |
169
+
170
+ **SSH 隧道**:MySQL 和 Redis 都支持通过 SSH 隧道连接,只需设置 `ssh_enable: true` 并提供 SSH 连接参数。
171
+
172
+ ---
173
+
174
+ ## 4. 核心模块详解
175
+
176
+ ### 4.1 配置模块 (config.py)
177
+
178
+ 定义了五大数据字典,可通过 `config.yaml` 覆盖:
179
+
180
+ ```python
181
+ from heims.common import config
182
+
183
+ secret = config.BASE_INFO['secret_key']
184
+ db_config = config.MYSQL_INFO
185
+ redis_config = config.REDIS_INFO
186
+ error_info = config.ERROR_INFO
187
+ ```
188
+
189
+ **错误码定义**(`ERROR_INFO`):
190
+
191
+ | 键 | code | 说明 |
192
+ |----|------|------|
193
+ | `no_login` | 60001 | 未登录 |
194
+ | `no_permission` | 60002 | 无权限 |
195
+ | `code_error` | 61001 | 验证码错误 |
196
+ | `user_disabled` | 62001 | 用户被禁用 |
197
+
198
+ ---
199
+
200
+ ### 4.2 日志工具 (log_util.py)
201
+
202
+ `LogUtil` 是基于 Python `logging` 的日志类,按日期分割日志文件。
203
+
204
+ ```python
205
+ from heims import LogUtil
206
+
207
+ # 基本使用
208
+ logger = LogUtil(name="my_module", base_dir="/path/to/base")
209
+ logger.info("处理完成")
210
+ logger.error("处理失败", exc_info=True)
211
+ logger.debug("调试信息")
212
+ logger.warning("警告信息")
213
+ ```
214
+
215
+ **特点**:
216
+
217
+ - 日志文件位于 `{base_dir}/log/{YYYYMMDD}.log`
218
+ - 支持 `with` 上下文管理器,自动释放文件句柄
219
+ - 类级别缓存机制,同一 name 不会重复创建 handler
220
+ - 日志格式:`时间 - logger名称 - 级别 - 消息内容`
221
+
222
+ ```python
223
+ # 上下文管理器
224
+ with LogUtil(name="my_task") as logger:
225
+ logger.info("任务开始")
226
+ # ... 业务逻辑 ...
227
+ logger.info("任务完成")
228
+ # 退出 with 时自动关闭
229
+ ```
230
+
231
+ ---
232
+
233
+ ### 4.3 通用工具函数 (functions.py)
234
+
235
+ 提供丰富的工具函数集合:
236
+
237
+ #### 加密与 ID 生成
238
+
239
+ ```python
240
+ from heims.common import functions
241
+
242
+ # MD5
243
+ h = functions.md5("hello")
244
+
245
+ # 加盐加密(用于密码)
246
+ encrypted = functions.crypt("password123")
247
+
248
+ # 唯一 ID 生成(时间戳 + 随机数)
249
+ new_id = functions.make_id()
250
+ ```
251
+
252
+ #### JSON 序列化
253
+
254
+ ```python
255
+ # 支持 datetime、date、decimal.Decimal 等特殊类型
256
+ data = {"time": datetime.datetime.now(), "price": decimal.Decimal("19.99")}
257
+ json_str = functions.json_dumps(data)
258
+ ```
259
+
260
+ #### 字典操作
261
+
262
+ ```python
263
+ # 多 key 优先级取值(类似 lodash 的 get)
264
+ value = functions.get_data_from_dict(data, ["field_a", "field_b", "field_c"], default="")
265
+
266
+ # 深度合并字典
267
+ functions.deep_copy_dict(source_dict, dest_dict)
268
+
269
+ # 选择性复制字段
270
+ new_dict = functions.copy_dict(source, keys=["name", "age", "email"])
271
+ new_dict = functions.copy_dict(source, filter_keys=["password", "token"])
272
+
273
+ # 删除字段
274
+ functions.drop_key_from_dict(data, filter_keys=["password"])
275
+
276
+ # 批量重命名键
277
+ functions.change_keys(data, {"old_key": "new_key"})
278
+
279
+ # 递归转换所有键的命名风格
280
+ functions.trans_key(data, "camel") # → 驼峰
281
+ functions.trans_key(data, "snake") # → 下划线
282
+ ```
283
+
284
+ #### 命名风格转换
285
+
286
+ ```python
287
+ functions.to_snake_case("UserName") # → "user_name"
288
+ functions.to_camel_case("user_name") # → "userName"
289
+ ```
290
+
291
+ #### 树形结构
292
+
293
+ ```python
294
+ # 将扁平父子关系列表转为树
295
+ flat_data = [
296
+ {"id": 1, "parent": 0, "name": "总公司"},
297
+ {"id": 2, "parent": 1, "name": "研发部"},
298
+ {"id": 3, "parent": 1, "name": "市场部"},
299
+ ]
300
+ tree = functions.build_tree(flat_data, sort_field="name")
301
+ ```
302
+
303
+ #### 类型检查
304
+
305
+ ```python
306
+ # 检查可迭代对象元素类型
307
+ functions.check_all_elements_type([1, 2, 3], int) # True
308
+ functions.check_all_elements_type([1, "2", 3], int) # True (宽松模式)
309
+ functions.check_all_elements_type([1, "2", 3], int, strict=True) # False (严格模式)
310
+
311
+ # 判断字符串是否为纯整数
312
+ functions.is_integer("123") # True
313
+ functions.is_integer("12a") # False
314
+
315
+ # 安全 int 转换
316
+ functions.set_default_int("123", 0) # 123
317
+ functions.set_default_int("abc", 0) # 0
318
+ ```
319
+
320
+ #### YAML 文件操作
321
+
322
+ ```python
323
+ config_data = functions.read_yaml_file("/path/to/config.yaml")
324
+ functions.write_yaml_file({"key": "value"}, "/path/to/output.yaml")
325
+ ```
326
+
327
+ ---
328
+
329
+ ### 4.4 异常定义 (exceptions.py)
330
+
331
+ #### RenderException — 中断视图执行并返回响应
332
+
333
+ ```python
334
+ from heims.common.exceptions import RenderException
335
+
336
+ # 直接创建响应并抛出,中断视图后续执行
337
+ raise RenderException.create_exception(code=400, msg="参数错误")
338
+
339
+ # 快捷抛出
340
+ RenderException.raise_exception(code=403, msg="无权访问")
341
+
342
+ # 重定向异常
343
+ RenderException.raise_redirect_exception("/login.html")
344
+ ```
345
+
346
+ #### CommonException — 通用业务异常
347
+
348
+ ```python
349
+ from heims.common.exceptions import CommonException
350
+
351
+ if not is_valid:
352
+ raise CommonException("数据不合法")
353
+ ```
354
+
355
+ ---
356
+
357
+ ### 4.5 缓存层 (redis_util.py)
358
+
359
+ `RedisUtil` 是静态工厂,`RedisClient` 是完整实现。
360
+
361
+ ```python
362
+ from heims import RedisUtil
363
+
364
+ # 创建 Redis 客户端
365
+ cache = RedisUtil.new_redis_client(
366
+ ssh_enable=True,
367
+ ssh_host="10.0.0.1",
368
+ ssh_port=22,
369
+ ssh_user="root",
370
+ ssh_password="xxx",
371
+ host="127.0.0.1",
372
+ port=6379,
373
+ password="redis-pass",
374
+ db=0,
375
+ max_connections=50,
376
+ serializer="json", # 可选 json/pickle
377
+ enable_compression=False # 是否启用 zlib 压缩
378
+ )
379
+ ```
380
+
381
+ **基础操作**:
382
+
383
+ ```python
384
+ cache.set("key", "value", ex=3600) # 设置,带过期时间
385
+ cache.set("key", {"a": 1}) # 自动序列化
386
+ result = cache.get("key") # 自动反序列化
387
+ cache.delete("key")
388
+ exists = cache.exists("key")
389
+ cache.expire("key", 7200) # 设置过期时间
390
+ cache.ttl("key") # 获取剩余时间
391
+
392
+ # 自增自减
393
+ cache.incr("counter") # → 1
394
+ cache.incr("counter", 5) # → 6
395
+ cache.decr("counter") # → 5
396
+ ```
397
+
398
+ **Hash 操作**:
399
+
400
+ ```python
401
+ cache.hset("user:1001", "name", "张三")
402
+ name = cache.hget("user:1001", "name")
403
+ all_fields = cache.h_get_all("user:1001")
404
+ cache.hm_set("user:1001", {"name": "张三", "age": "25"})
405
+ ```
406
+
407
+ **List 操作**:
408
+
409
+ ```python
410
+ cache.l_push("queue", "task1") # 左入队
411
+ cache.r_push("queue", "task2") # 右入队
412
+ items = cache.lrange("queue", 0, -1) # 获取全部
413
+ ```
414
+
415
+ **Set / Sorted Set**:
416
+
417
+ ```python
418
+ cache.s_add("tags", "python", "redis")
419
+ members = cache.s_members("tags")
420
+
421
+ cache.z_add("leaderboard", {"user_a": 100, "user_b": 85})
422
+ top10 = cache.z_range("leaderboard", 0, 9, desc=True)
423
+ ```
424
+
425
+ **管道与批量操作**:
426
+
427
+ ```python
428
+ # 管道(事务)
429
+ with cache.pipeline(transaction=True) as pipe:
430
+ pipe.set("key1", "val1")
431
+ pipe.set("key2", "val2")
432
+
433
+ # 批量获取
434
+ values = cache.mget(["key1", "key2", "key3"])
435
+
436
+ # 批量设置
437
+ cache.mset({"key1": "val1", "key2": "val2"})
438
+
439
+ # 模糊查询
440
+ keys = cache.get_keys("user:*")
441
+ ```
442
+
443
+ **@cached 装饰器**:
444
+
445
+ ```python
446
+ # 自动缓存函数返回值
447
+ @cache.cached(ttl=300, prefix="data")
448
+ def get_user_list(page):
449
+ # 这个函数的结果将自动缓存 300 秒
450
+ return db.query("SELECT * FROM user")
451
+
452
+ # 自定义 key 生成函数
453
+ @cache.cached(key_func=lambda *args, **kw: f"user:{args[0]}:{args[1]}", ttl=600)
454
+ def get_data(category, id):
455
+ return fetch_from_db(category, id)
456
+ ```
457
+
458
+ **统计与维护**:
459
+
460
+ ```python
461
+ stats = cache.get_stats()
462
+ # {'operations': 1000, 'cache_hits': 850, 'cache_hit_rate': '85.00%'}
463
+
464
+ cache.ping() # 检查连接
465
+ info = cache.info() # Redis INFO
466
+ conn_info = cache.get_connection_info()
467
+ cache.clear_db() # 清空当前数据库
468
+ cache.close() # 关闭连接
469
+ ```
470
+
471
+ ---
472
+
473
+ ### 4.6 数据库层 (mysql_util.py)
474
+
475
+ `MySQLUtil` 是静态工厂,`MySQLWithSSH` 是完整实现。
476
+
477
+ ```python
478
+ from heims import MySQLUtil
479
+
480
+ # 创建 MySQL 客户端
481
+ dao = MySQLUtil.new_db(
482
+ ssh_enable=True,
483
+ ssh_host="10.0.0.1",
484
+ ssh_port=22,
485
+ ssh_user="root",
486
+ ssh_password="xxx",
487
+ host="127.0.0.1",
488
+ port=3306,
489
+ user="db_user",
490
+ password="db_pass",
491
+ database="my_db",
492
+ pool_size=5,
493
+ cache=cache # 可选的 RedisClient 实例,用于表结构缓存
494
+ )
495
+ ```
496
+
497
+ #### 基础 CRUD
498
+
499
+ ```python
500
+ # 查询单条
501
+ user = dao.get_info("sys_user", where={"id": 1})
502
+
503
+ # 查询多条(分页)
504
+ users = dao.get_list("sys_user", page=1, page_size=20, order="id DESC")
505
+
506
+ # 查询单个值
507
+ count = dao.query_value("SELECT COUNT(*) FROM sys_user WHERE status = %s", (1,))
508
+
509
+ # 添加
510
+ dao.upsert("sys_user", {"name": "张三", "status": 1})
511
+
512
+ # 更新
513
+ dao.update("sys_user", {"status": 0}, where={"id": 1})
514
+
515
+ # 删除
516
+ dao.delete("sys_user", where={"id": 1})
517
+ ```
518
+
519
+ #### 条件筛选 (search_dict)
520
+
521
+ search_dict 是框架的核心条件构建方式,支持丰富的操作符和组合:
522
+
523
+ ```python
524
+ # 等值查询
525
+ dao.get_list("sys_user", search_dict={
526
+ "status": 1,
527
+ "department_id": [1, 2, 3], # IN 查询
528
+ "name": "like:张三", # LIKE
529
+ "created_at": ">=:2024-01-01", # >=
530
+ "id": "!=:100", # !=
531
+ "price": "between:100|500", # BETWEEN
532
+ "email": "llike:%@qq.com", # 左模糊 LIKE
533
+ "phone": "rlike:138%", # 右模糊 LIKE
534
+ "title": "not_like:%test%", # NOT LIKE
535
+ })
536
+
537
+ # AND/OR 组合
538
+ dao.get_list("sys_user", search_dict={
539
+ "status": 1,
540
+ ":or": { # OR 条件组
541
+ "name": "张三",
542
+ "email": "zhangsan@qq.com"
543
+ }
544
+ })
545
+
546
+ # 嵌套 AND/OR
547
+ dao.get_list("sys_user", search_dict={
548
+ ":and": {
549
+ "status": 1,
550
+ ":or": {
551
+ "department_id": 1,
552
+ "role_id": [1, 2]
553
+ }
554
+ }
555
+ })
556
+ ```
557
+
558
+ #### 条件操作符速查表
559
+
560
+ | 操作符 | 格式 | 示例 | SQL |
561
+ |--------|------|------|-----|
562
+ | 等于 | `value` | `{"id": 1}` | `id = 1` |
563
+ | IN | `[list]` | `{"id": [1,2,3]}` | `id IN (1,2,3)` |
564
+ | > | `'>:value'` | `{"id": ">:100"}` | `id > 100` |
565
+ | >= | `'>=:value'` | `{"id": ">=:100"}` | `id >= 100` |
566
+ | < | `'<:value'` | `{"id": "<:100"}` | `id < 100` |
567
+ | <= | `'<=:value'` | `{"id": "<=:100"}` | `id <= 100` |
568
+ | != | `'!=:value'` | `{"id": "!=:100"}` | `id != 100` |
569
+ | BETWEEN | `'between:v1\|v2'` | `{"price": "between:100\|500"}` | `price BETWEEN 100 AND 500` |
570
+ | LIKE | `'like:xxx'` | `{"name": "like:%张"}` | `name LIKE '%张'` |
571
+ | 左LIKE | `'llike:xxx'` | `{"name": "llike:张%"}` | `name LIKE '张%'` |
572
+ | 右LIKE | `'rlike:xxx'` | `{"name": "rlike:%张"}` | `name LIKE '%张'` |
573
+ | NOT LIKE | `'not_like:xxx'` | `{"name": "not_like:%张"}` | `name NOT LIKE '%张'` |
574
+
575
+ #### 批量操作
576
+
577
+ ```python
578
+ # 批量插入
579
+ rows = [
580
+ {"name": "张三", "age": 25},
581
+ {"name": "李四", "age": 30},
582
+ ]
583
+ dao.batch_insert("sys_user", ["name", "age"], rows, batch_size=500)
584
+
585
+ # 批量插入(忽略重复)
586
+ dao.batch_insert("sys_user", ["name", "age"], rows, ignore_duplicate=True)
587
+
588
+ # 批量插入(重复时更新)
589
+ dao.batch_insert("sys_user", ["name", "age"], rows, on_duplicate_update=["age"])
590
+ ```
591
+
592
+ #### 事务
593
+
594
+ ```python
595
+ # 上下文管理器,自动 commit / rollback
596
+ with dao.transaction() as conn:
597
+ conn.execute("UPDATE account SET balance = balance - 100 WHERE id = 1")
598
+ conn.execute("UPDATE account SET balance = balance + 100 WHERE id = 2")
599
+ ```
600
+
601
+ #### 表结构操作
602
+
603
+ ```python
604
+ # 获取表结构(已缓存)
605
+ columns = dao.get_table_structure("sys_user")
606
+
607
+ # 重置表结构缓存
608
+ dao.reset_table_structure()
609
+
610
+ # 检查列是否存在
611
+ valid = dao.check_columns("sys_user", ["name", "age"])
612
+
613
+ # 过滤有效列
614
+ valid_cols = dao.filter_columns("sys_user", ["name", "age", "unknown_field"])
615
+ ```
616
+
617
+ #### 原生 SQL
618
+
619
+ ```python
620
+ # 查询多条
621
+ rows = dao.query("SELECT id, name FROM sys_user WHERE status = %s", (1,))
622
+
623
+ # 查询单条
624
+ row = dao.query_one("SELECT * FROM sys_user WHERE id = %s", (1,))
625
+
626
+ # 执行(INSERT/UPDATE/DELETE)
627
+ affected = dao.execute("UPDATE sys_user SET status = 0 WHERE id = %s", (1,))
628
+
629
+ # 存储过程
630
+ dao.call_procedure("sp_update_user", (1, "张三"))
631
+
632
+ # 获取连接(用于复杂操作)
633
+ with dao.get_connection() as conn:
634
+ cursor = conn.cursor()
635
+ cursor.execute("SELECT ...")
636
+ ```
637
+
638
+ ---
639
+
640
+ ### 4.7 服务层
641
+
642
+ #### UserService — 用户服务
643
+
644
+ 全静态方法,无需实例化。
645
+
646
+ ```python
647
+ from heims import UserService
648
+
649
+ # 用户登录(4种方式)
650
+ # 方式1:根据 user_id 直接登录
651
+ user_info = UserService.login(dao, cache, user_id=1)
652
+
653
+ # 方式2:用户名密码登录
654
+ user_info = UserService.login(dao, cache, username="admin", password="123456")
655
+
656
+ # 方式3:手机号/邮箱 + 验证码登录
657
+ user_info = UserService.login(dao, cache, mobile="13800138000", code="1234")
658
+
659
+ # 方式4:微信 openid/unionid 登录
660
+ user_info = UserService.login(dao, cache, openid="oXXXXXXX")
661
+
662
+ # 获取用户完整信息(角色、权限、菜单树)
663
+ user = UserService.get_info_by_id(1, dao, cache)
664
+
665
+ # 获取到的用户信息结构
666
+ # {
667
+ # "id": 1,
668
+ # "username": "admin",
669
+ # "role_list": [...], # 角色列表
670
+ # "permission_list": [...], # 权限列表
671
+ # "menu_list": [...] # 菜单树
672
+ # }
673
+ ```
674
+
675
+ #### DataService — 关联数据服务
676
+
677
+ 封装了 CRUD 操作,并自动处理外键、多对多、一对多三种关联。
678
+
679
+ ```python
680
+ from heims import DataService
681
+
682
+ # 基础 CRUD
683
+ ds = DataService(
684
+ dao=dao,
685
+ cache=cache,
686
+ table="sys_user",
687
+ column_list=["id", "name", "email", "status", "department_id", "created_at"]
688
+ )
689
+
690
+ # 查询列表(分页、筛选、排序、分组)
691
+ result = ds.get_list({
692
+ "page": 1,
693
+ "page_size": 20,
694
+ "search_dict": {"status": 1},
695
+ "order": "id DESC",
696
+ "group_by": "department_id"
697
+ })
698
+
699
+ # 查询详情
700
+ detail = ds.get_detail({"id": 1})
701
+
702
+ # 添加
703
+ ds.edit({"name": "新用户", "email": "new@test.com", "status": 1})
704
+
705
+ # 更新
706
+ ds.edit({"id": 1, "name": "新名字"})
707
+
708
+ # 软删除(is_del=1)或硬删除
709
+ ds.delete({"id": 1})
710
+ ```
711
+
712
+ **三种关联模式配置**:
713
+
714
+ ```python
715
+ ds = DataService(
716
+ dao=dao,
717
+ cache=cache,
718
+ table="sys_user",
719
+ column_list=["id", "name", "department_id"],
720
+
721
+ # 1. 外键关联 (N:1) — 查询时自动 JOIN 获取关联表数据
722
+ fk_info=[
723
+ {
724
+ "table": "sys_department",
725
+ "fk_ids": "department_id", # 本表外键字段
726
+ "columns": ["id", "dept_name"], # 关联表要查询的列
727
+ "show_name": "department" # 返回数据中的键名
728
+ }
729
+ ],
730
+
731
+ # 2. 多对多关联 (N:N) — 通过中间表关联
732
+ # 场景举例:订单表(order)通过中间表(order_item)关联商品表(product)
733
+ m2m_info=[
734
+ {
735
+ "show_name": "products", # 返回数据中的键名,默认取中间表名加"_list"
736
+ "search_info": [
737
+ # search_info[0] — 中间表配置(如 order_item)
738
+ {
739
+ "table": "order_item", # 中间表表名
740
+ "fk_ids": {"id": "order_id"}, # 主表主键 → 中间表外键 (此处 order_item.order_id 关联 order.id)
741
+ "columns": ["id", "order_id", "product_id"], # 中间表要查询的字段
742
+ "expire": 0 # 缓存过期时间(秒),0 表示不缓存
743
+ },
744
+ # search_info[1] — 目标表配置(如 product)
745
+ {
746
+ "table": "product", # 目标表表名
747
+ "fk_ids": {"product_id": "id"},# 中间表外键 → 目标表主键(此处 order_item.product_id 关联 product.id)
748
+ "columns": ["id", "name", "price", "img"], # 目标表要查询的字段
749
+ "expire": 0 # 缓存过期时间
750
+ }
751
+ ]
752
+ },
753
+ # 第二个多对多关联示例:如文章(article)通过中间表关联标签(tag)
754
+ {
755
+ "show_name": "tags",
756
+ "search_info": [
757
+ {"table": "article_tag", "fk_ids": {"article_id": "id"}, "columns": ["id"]},
758
+ {"table": "tag", "fk_ids": {"tag_id": "id"}, "columns": ["id", "name", "color"]}
759
+ ]
760
+ }
761
+ ],
762
+
763
+ # 3. 一对多关联 (1:N) — 子表数据
764
+ content_info=[
765
+ {
766
+ "table": "user_log",
767
+ "fk_ids": "user_id",
768
+ "columns": ["id", "action", "created_at"],
769
+ "show_name": "logs",
770
+ "model": "1vn", # 1vn 模式(一对多)
771
+ "search_dict": {}, # 子表筛选条件
772
+ "order": "id DESC",
773
+ "page": 1,
774
+ "page_size": 100
775
+ }
776
+ ],
777
+ )
778
+ ```
779
+
780
+ ---
781
+
782
+ ### 4.8 视图层 (common_views.py)
783
+
784
+ 视图继承层次:
785
+
786
+ ```
787
+ View (Django)
788
+ └── BaseView # 基础视图
789
+ └── ModuleView # 模块视图(带关联配置)
790
+ ├── ListView # 列表查询
791
+ ├── DetailView # 详情查询
792
+ ├── EditView # 添加/编辑
793
+ └── DeleteView # 删除
794
+ ```
795
+
796
+ #### BaseView — 完整请求生命周期
797
+
798
+ 每次请求都会经历以下流程:
799
+
800
+ ```
801
+ set_config() → init_utils() → get_info_from_request() → log_in() → save_log() → check_permission() → 业务方法(do_get / do_post) → 释放资源
802
+ ```
803
+
804
+ **核心属性**:
805
+
806
+ | 属性 | 类型 | 说明 |
807
+ |------|------|------|
808
+ | `self.trace` | str | 请求唯一标识 |
809
+ | `self.user_info` | dict | 当前用户完整信息 |
810
+ | `self.user_id` | int | 当前用户 ID |
811
+ | `self.get_data` | dict | GET 参数 |
812
+ | `self.post_data` | dict | POST 参数 |
813
+ | `self.resp_data` | dict | 响应数据 `{code, msg, data}` |
814
+ | `self.logger` | LogUtil | 日志实例 |
815
+ | `self.cache` | RedisClient | Redis 客户端 |
816
+ | `self.dao` | MySQLWithSSH | 数据库客户端 |
817
+ | `self.template_name` | str | 模板路径(页面渲染时使用) |
818
+
819
+ **统一响应方法**:
820
+
821
+ ```python
822
+ # 成功
823
+ self.set_data({"total": 100, "list": [...]}) # 设置响应数据
824
+ return self.return_success() # 返回 {"code": 200, "msg": "", "data": {...}}
825
+
826
+ # 失败
827
+ return self.return_error(code=400, msg="参数错误")
828
+ return self.return_error(code=403, msg="无权访问")
829
+
830
+ # 页面渲染
831
+ self.template_name = "index.html"
832
+ return self.return_render({"title": "首页"})
833
+
834
+ # 设置 Cookie
835
+ self.set_cookie_data.append({
836
+ "key": "auth_token",
837
+ "value": "xxx",
838
+ "options": {"max_age": 86400, "httponly": True}
839
+ })
840
+ ```
841
+
842
+ **日志记录**:
843
+
844
+ ```python
845
+ def do_get(self, request):
846
+ self.logger.info(f"查询用户列表, page={self.get_data.get('page')}")
847
+ # ... 业务逻辑 ...
848
+ self.save_response_log(self.resp_data) # 保存响应日志到文件
849
+ return self.return_success()
850
+ ```
851
+
852
+ ---
853
+
854
+ ### 4.9 微信工具 (wechat_util.py)
855
+
856
+ ```python
857
+ from heims import WechatUtil
858
+
859
+ wechat = WechatUtil(app_id="wx_app_id", app_secret="app_secret")
860
+
861
+ # OAuth2.0 授权登录(公众号)
862
+ result = wechat.log_by_code(code, client_type="public_account")
863
+ # → {"openid": "xxx", "access_token": "xxx", ...}
864
+
865
+ # 小程序 jscode2session
866
+ result = wechat.log_by_code(code, client_type="applet")
867
+ # → {"openid": "xxx", "session_key": "xxx", "unionid": "xxx"}
868
+
869
+ # 获取用户手机号
870
+ phone_info = wechat.get_phone_info(code)
871
+
872
+ # 生成小程序码
873
+ buffer = wechat.get_qr_code(page="pages/index/index", scene="id=123", _type="unlimited")
874
+ ```
875
+
876
+ ---
877
+
878
+ ## 5. 初始化机制
879
+
880
+ 导入 `heims` 包时自动执行 `initialize()`:
881
+
882
+ 1. 读取当前工作目录下的 `config.yaml`
883
+ 2. 将 YAML 配置深度合并到 `config.py` 中各配置字典
884
+ 3. 规则:YAML 的 section 名按规则映射到 config 中的字典名
885
+
886
+ 例如,YAML 中 `MYSQL_INFO` 会覆盖 `config.MYSQL_INFO` 的值。
887
+
888
+ **使用方式**:
889
+
890
+ ```python
891
+ import heims # 自动执行 initialize(),读取 config.yaml
892
+ ```
893
+
894
+ 或在配置初始化后:
895
+
896
+ ```python
897
+ from heims import initialize
898
+ initialize() # 手动调用初始化
899
+ ```
900
+
901
+ ---
902
+
903
+ ## 6. 视图开发指南
904
+
905
+ ### 6.1 BaseView — 基础视图
906
+
907
+ 用于需要自定义 GET/POST 业务逻辑的场景。
908
+
909
+ ```python
910
+ from heims import BaseView
911
+
912
+ class MyCustomView(BaseView):
913
+ """自定义视图"""
914
+
915
+ def set_config(self):
916
+ """配置页面参数"""
917
+ self.page_permission = {"my_permission": "/404.html"} # 权限检查
918
+ self.template_name = "" # 不为空时表示页面渲染,为空表示 AJAX/API
919
+
920
+ def pre_get(self, request):
921
+ """GET 请求前置处理"""
922
+ self.logger.info("pre_get: 准备查询")
923
+
924
+ def do_get(self, request):
925
+ """GET 请求核心逻辑"""
926
+ keyword = self.get_data.get("keyword", "")
927
+ result = some_query_logic(keyword)
928
+ self.set_data(result)
929
+
930
+ def ap_get(self, request):
931
+ """GET 请求后置处理"""
932
+ self.save_response_log(self.resp_data)
933
+
934
+ def pre_post(self, request):
935
+ """POST 请求前置处理"""
936
+ # 参数校验
937
+ if not self.post_data.get("name"):
938
+ self.return_error(code=400, msg="名称不能为空", raise_error=True)
939
+
940
+ def do_post(self, request):
941
+ """POST 请求核心逻辑"""
942
+ self.dao.upsert("my_table", self.post_data)
943
+ self.set_data({"id": self.post_data.get("id")})
944
+
945
+ def ap_post(self, request):
946
+ """POST 请求后置处理"""
947
+ self.save_response_log(self.resp_data)
948
+ ```
949
+
950
+ **请求生命周期**(模板方法模式):
951
+
952
+ | GET 请求 | POST 请求 | 说明 |
953
+ |----------|-----------|------|
954
+ | `pre_get()` | `pre_post()` | 前置处理(参数校验) |
955
+ | `do_get()` | `do_post()` | 核心业务逻辑 |
956
+ | `ap_get()` | `ap_post()` | 后置处理(日志等) |
957
+
958
+ ### 6.2 ModuleView — 模块视图
959
+
960
+ 继承 BaseView,增加 DataService 自动配置。适合需要关联处理的场景。
961
+
962
+ ```python
963
+ from heims import ModuleView
964
+
965
+ class MyModuleView(ModuleView):
966
+ """带关联配置的模块视图"""
967
+
968
+ def set_config(self):
969
+ super().set_config()
970
+
971
+ # 关联配置
972
+ self.fk_info = [
973
+ {
974
+ "table": "sys_department",
975
+ "fk_ids": "department_id",
976
+ "columns": ["id", "dept_name"],
977
+ "show_name": "department"
978
+ }
979
+ ]
980
+ self.m2m_info = []
981
+ self.content_info = []
982
+ ```
983
+
984
+ ### 6.3 ListView — 列表查询
985
+
986
+ 内置分页、排序、筛选、分组功能。
987
+
988
+ ```python
989
+ from heims import ListView
990
+
991
+ class MyListView(ListView):
992
+ """列表查询视图"""
993
+
994
+ def set_config(self):
995
+ super().set_config()
996
+ self.table = "sys_user"
997
+ self.column_list = ["id", "name", "email", "status", "created_at"]
998
+ self.page_permission = {"view_user_list": "/404.html"}
999
+ self.fk_info = [...] # 外键关联配置
1000
+
1001
+ def pre_get(self, request):
1002
+ """自定义筛选条件"""
1003
+ # 默认只查自己的数据
1004
+ self.search_dict["department_id"] = self.user_info["department_id"]
1005
+ # 前端传来的筛选
1006
+ if self.get_data.get("keyword"):
1007
+ self.search_dict["name"] = f"like:%{self.get_data['keyword']}%"
1008
+
1009
+ # URL 配置示例
1010
+ # path('api/user/list/', MyListView.as_view(), name='user_list')
1011
+ ```
1012
+
1013
+ ### 6.4 DetailView — 详情查询
1014
+
1015
+ ```python
1016
+ from heims import DetailView
1017
+
1018
+ class MyDetailView(DetailView):
1019
+ def set_config(self):
1020
+ super().set_config()
1021
+ self.table = "sys_user"
1022
+ self.column_list = ["id", "name", "email", "status", "created_at"]
1023
+
1024
+ # GET /api/user/detail/?id=1
1025
+ # 响应: {"code": 200, "msg": "", "data": {"id": 1, "name": "张三", ...}}
1026
+ ```
1027
+
1028
+ ### 6.5 EditView — 添加/编辑
1029
+
1030
+ 自动根据是否包含 `id` 判断是添加还是编辑。
1031
+
1032
+ ```python
1033
+ from heims import EditView
1034
+
1035
+ class MyEditView(EditView):
1036
+ def set_config(self):
1037
+ super().set_config()
1038
+ self.table = "sys_user"
1039
+ self.column_list = ["name", "email", "status", "department_id"]
1040
+ self.page_permission = {"edit_user": "/404.html"}
1041
+
1042
+ # POST /api/user/edit/ {"name": "新用户", "email": "new@test.com"} → 添加
1043
+ # POST /api/user/edit/ {"id": 1, "name": "新名字"} → 编辑
1044
+ ```
1045
+
1046
+ ### 6.6 DeleteView — 删除
1047
+
1048
+ 支持软删除(默认,`is_del=1`)和硬删除。
1049
+
1050
+ ```python
1051
+ from heims import DeleteView
1052
+
1053
+ class MyDeleteView(DeleteView):
1054
+ def set_config(self):
1055
+ super().set_config()
1056
+ self.table = "sys_user"
1057
+ self.hard_delete = False # 默认软删除,设为 True 即硬删除
1058
+ self.page_permission = {"delete_user": "/404.html"}
1059
+
1060
+ # POST /api/user/delete/ {"id": 1}
1061
+ ```
1062
+
1063
+ ### 6.7 H5 微信视图
1064
+
1065
+ 提供带微信 OAuth2.0 授权的视图模板,适用于公众号 H5 页面。
1066
+
1067
+ ```python
1068
+ from heims import H5ListView, H5EditView, H5DetailView, H5DeleteView, H5BaseView
1069
+
1070
+ class MyH5ListView(H5ListView):
1071
+ """H5 微信列表视图"""
1072
+ def set_config(self):
1073
+ super().set_config()
1074
+ self.table = "sys_user"
1075
+ self.column_list = ["id", "name", "email", "status", "created_at"]
1076
+ ```
1077
+
1078
+ **H5 视图特点**:
1079
+
1080
+ - 自动进行微信 OAuth2.0 授权
1081
+ - 获取用户 OpenID,自动创建/关联系统用户
1082
+ - Token 认证,保持登录态
1083
+ - 其余行为与普通视图完全一致
1084
+
1085
+ ---
1086
+
1087
+ ## 7. 数据库操作指南
1088
+
1089
+ ### 7.1 连接与配置
1090
+
1091
+ ```python
1092
+ from heims import MySQLUtil, config
1093
+
1094
+ # 方式1:使用 config.yaml 配置(推荐)
1095
+ dao = MySQLUtil.new_db(**config.MYSQL_INFO, cache=cache)
1096
+
1097
+ # 方式2:直接传参
1098
+ dao = MySQLUtil.new_db(
1099
+ ssh_enable=False,
1100
+ host="127.0.0.1",
1101
+ port=3306,
1102
+ user="root",
1103
+ password="password",
1104
+ database="my_db",
1105
+ pool_size=5
1106
+ )
1107
+ ```
1108
+
1109
+ **连接池说明**:使用 DBUtils 的 `PooledDB`,连接自动管理。`pool_size` 控制最大连接数。
1110
+
1111
+ **SSH 隧道**:当 `ssh_enable=True` 时,框架会自动建立 SSH 隧道,将数据库连接转发给远程服务器。
1112
+
1113
+ ### 7.2 基础查询
1114
+
1115
+ ```python
1116
+ # 查询全部
1117
+ rows = dao.get_list("sys_user", page=1, page_size=100)
1118
+ # 返回: {"total": 1000, "list": [...], "pages": ...}
1119
+
1120
+ # 条件查询
1121
+ rows = dao.get_list("sys_user",
1122
+ search_dict={"status": 1, "department_id": [1, 2]},
1123
+ order="id DESC",
1124
+ page=1, page_size=20
1125
+ )
1126
+
1127
+ # 查询单条
1128
+ user = dao.get_info("sys_user", where={"id": 1})
1129
+
1130
+ # 查询计数
1131
+ total = dao.query_value("SELECT count(*) FROM sys_user WHERE status=1")
1132
+ ```
1133
+
1134
+ ### 7.3 条件筛选 (search_dict)
1135
+
1136
+ 丰富灵活的条件构建器:
1137
+
1138
+ ```python
1139
+ # IN 查询
1140
+ {"id": [1, 2, 3, 4]} # → id IN (1,2,3,4)
1141
+
1142
+ # 模糊查询
1143
+ {"name": "like:%张"} # → name LIKE '%张'
1144
+ {"name": "llike:张%"} # → name LIKE '张%'
1145
+ {"name": "not_like:%test%"} # → name NOT LIKE '%test%'
1146
+
1147
+ # 范围查询
1148
+ {"age": ">:18"} # → age > 18
1149
+ {"age": "<=:60"} # → age <= 60
1150
+ {"created_at": ">=:2024-01-01"} # → created_at >= '2024-01-01'
1151
+
1152
+ # BETWEEN
1153
+ {"price": "between:100|500"} # → price BETWEEN 100 AND 500
1154
+
1155
+ # 不等于
1156
+ {"status": "!=:0"} # → status != 0
1157
+
1158
+ # OR 条件
1159
+ {":or": {"status": 0, "is_del": 1}} # → (status = 0 OR is_del = 1)
1160
+
1161
+ # 嵌套组合
1162
+ {":and": {
1163
+ "department_id": 1,
1164
+ ":or": {"status": 1, "status": 2}
1165
+ }}
1166
+ ```
1167
+
1168
+ ### 7.4 批量操作与事务
1169
+
1170
+ ```python
1171
+ # 批量插入(默认)
1172
+ rows = [{"name": f"user_{i}", "age": i} for i in range(1000)]
1173
+ dao.batch_insert("sys_user", ["name", "age"], rows, batch_size=500)
1174
+
1175
+ # 忽略重复
1176
+ dao.batch_insert("sys_user", ["name", "age"], rows, ignore_duplicate=True)
1177
+
1178
+ # 重复则更新
1179
+ dao.batch_insert("sys_user", ["name", "age"], rows, on_duplicate_update=["age"])
1180
+
1181
+ # 事务
1182
+ try:
1183
+ with dao.transaction() as conn:
1184
+ conn.execute("UPDATE account SET balance = balance - 100 WHERE id = 1")
1185
+ conn.execute("UPDATE account SET balance = balance + 100 WHERE id = 2")
1186
+ except Exception as e:
1187
+ # 自动回滚
1188
+ logger.error(f"事务失败: {e}")
1189
+ ```
1190
+
1191
+ ### 7.5 DataService — 关联数据服务
1192
+
1193
+ DataService 在 CRUD 基础上自动处理关联数据的查询和写入:
1194
+
1195
+ ```python
1196
+ from heims import DataService
1197
+
1198
+ ds = DataService(
1199
+ dao=dao,
1200
+ cache=cache,
1201
+ table="sys_article",
1202
+ column_list=["id", "title", "content", "category_id", "author_id"],
1203
+ fk_info=[{
1204
+ "table": "sys_category",
1205
+ "fk_ids": "category_id",
1206
+ "columns": ["id", "name"],
1207
+ "show_name": "category"
1208
+ }],
1209
+ m2m_info=[{
1210
+ "search_info": [
1211
+ {"search_dict": {}, "expire": 0},
1212
+ {"search_dict": {}, "expire": 0}
1213
+ ],
1214
+ "show_name": "tags"
1215
+ }]
1216
+ )
1217
+
1218
+ # 查询列表 → 自动关联 category 和 tags
1219
+ result = ds.get_list({"page": 1, "page_size": 20})
1220
+ # 响应中自动包含 category_name, tags 等关联数据
1221
+
1222
+ # 添加/编辑 → 自动处理关联表写入
1223
+ ds.edit({
1224
+ "id": 1,
1225
+ "title": "新标题",
1226
+ "category_id": 2,
1227
+ "tag_list": [1, 3, 5] # 多对多关联自动写入中间表
1228
+ })
1229
+ ```
1230
+
1231
+ ---
1232
+
1233
+ ## 8. 缓存操作指南
1234
+
1235
+ ```python
1236
+ from heims import RedisUtil, config
1237
+
1238
+ cache = RedisUtil.new_redis_client(**config.REDIS_INFO)
1239
+
1240
+ # 字符串
1241
+ cache.set("key", "value", ex=3600)
1242
+ value = cache.get("key")
1243
+
1244
+ # Hash(适合存储对象)
1245
+ cache.hm_set("user:1001", {"name": "张三", "email": "test@qq.com"})
1246
+ name = cache.hget("user:1001", "name")
1247
+ all_info = cache.h_get_all("user:1001")
1248
+
1249
+ # 列表(消息队列场景)
1250
+ cache.l_push("task_queue", json.dumps({"task_id": 1, "action": "send_email"}))
1251
+ task = cache.r_push("task_queue", json.dumps({"task_id": 2}))
1252
+
1253
+ # 装饰器缓存
1254
+ @cache.cached(ttl=300)
1255
+ def get_config():
1256
+ return db.query("SELECT * FROM sys_config")
1257
+ # 首次调用查询数据库,300秒内后续调用直接返回缓存
1258
+
1259
+ # 管道(减少网络往返)
1260
+ with cache.pipeline() as pipe:
1261
+ pipe.set("a", 1)
1262
+ pipe.set("b", 2)
1263
+ pipe.set("c", 3)
1264
+ ```
1265
+
1266
+ ---
1267
+
1268
+ ## 9. 公开 API
1269
+
1270
+ heims 包导出的所有公共 API 可直接使用:
1271
+
1272
+ ```python
1273
+ from heims import (
1274
+ # 初始化
1275
+ 'initialize',
1276
+ # 配置模块
1277
+ 'config',
1278
+ # 日志
1279
+ 'LogUtil',
1280
+ # Redis
1281
+ 'RedisUtil', 'RedisClient',
1282
+ # MySQL
1283
+ 'MySQLUtil', 'MySQLWithSSH',
1284
+ # 微信
1285
+ 'WechatUtil',
1286
+ # 视图
1287
+ 'BaseView', 'ModuleView', 'ListView', 'DetailView', 'EditView', 'DeleteView',
1288
+ # H5 视图
1289
+ 'H5DeleteView', 'H5EditView', 'H5DetailView', 'H5ListView', 'H5BaseView', 'H5ModuleView', 'H5Mixin',
1290
+ # 服务
1291
+ 'UserService', 'DataService', 'CodeService',
1292
+ )
1293
+ ```
1294
+
1295
+ ---
1296
+
1297
+ ## 快速开始示例
1298
+
1299
+ 一个完整的最小化视图示例:
1300
+
1301
+ ```python
1302
+ import heims
1303
+ from heims import ListView, EditView, DeleteView
1304
+
1305
+ class UserListView(ListView):
1306
+ """用户列表"""
1307
+ def set_config(self):
1308
+ super().set_config()
1309
+ self.table = "sys_user"
1310
+ self.column_list = ["id", "name", "email", "status", "created_at"]
1311
+ self.page_permission = {"view_user": "/404.html"}
1312
+
1313
+ class UserEditView(EditView):
1314
+ """用户编辑"""
1315
+ def set_config(self):
1316
+ super().set_config()
1317
+ self.table = "sys_user"
1318
+ self.column_list = ["name", "email", "status"]
1319
+ self.page_permission = {"edit_user": "/404.html"}
1320
+
1321
+ class UserDeleteView(DeleteView):
1322
+ """用户删除"""
1323
+ def set_config(self):
1324
+ super().set_config()
1325
+ self.table = "sys_user"
1326
+ self.page_permission = {"delete_user": "/404.html"}
1327
+ ```
1328
+
1329
+ 然后创建 `config.yaml` 配置数据库/Redis/微信信息,放在项目根目录。导入 heims 包时自动加载。