恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
CRMEB 商城系统 DAO 数据访问层深度解析:从 BaseDao 抽象基类到业务落地的分层实践
首页
资讯中心
/
CRMEB 商城系统 DAO 数据访问层深度解析:从 BaseDao 抽象基类到业务落地的分层实践
CRMEB 商城系统 DAO 数据访问层深度解析:从 BaseDao 抽象基类到业务落地的分层实践
发布时间:2026/10/14 2:42:53
后端电商前端【免费下载链接】CRMEBCRMEB开源商城免费开源多语言商城系统Tp6框架商城系统可商用包含小程序商城、H5商城、公众号商城、PC商城、App支持分销、拼团、砍价、秒杀、优惠券、积分、会员等级、小程序直播、页面DIY前后端分离方便二开更有详细使用文档、接口文档、数据字典、二开文档/视频教程欢迎大家提出宝贵意见和建议项目地址https://gitcode.com/gh_mirrors/cr/CRMEB点击查看免费下载导读本文以 CRMEB 开源商城系统的 crmeb/app/dao/README.md 为骨架系统讲解其数据访问对象DAO层的职责边界、抽象设计、通用能力矩阵与真实调用链。读者将理解 CRMEB 基于 ThinkPHP 6 的「控制器 → 服务层 → DAO → 模型」分层架构中DAO 层如何屏蔽数据库差异、统一 CRUD 接口并掌握在业务中新增一个 DAO、复用搜索器与高精度金额操作的完整实战路径。CRMEB 是使用 ThinkPHP 6 框架开发的商用商城系统包含小程序、H5、公众号、PC 商城与 App 等端。在它的app目录中crmeb/app/dao承担了全部数据持久层访问职责。本文依据该目录的 README.md 展开并结合 BaseDao.php、UserDao.php 等真实源码逐层拆解。一、DAO 层是什么CRMEB 数据访问层的职责定位根据 crmeb/app/dao/README.mdDAOData Access Object层位于控制器Controller与模型Model之间主要职责包括实现数据持久层访问对数据库进行 CURD增删改查操作依赖数据库连接实现对表的基本增删改查功能封装数据库操作原语查询、插入、更新等简化开发难度与数据库解耦提供统一接口便于扩展和维护。具体到代码组织方式README 明确了三条规则crmeb/app/dao目录下每个文件对应一个数据表或业务模块如user/UserDao.php对应user用户表order/StoreOrderDao.php对应订单模块文件内封装了对该表的基本操作方法如查找、插入、更新、删除、计数、求和等方法的参数与返回值类型为模型对象Model实现数据与业务的解耦并封装了丰富的查询条件方便上层调用底层利用ThinkPHP 的 ActiveRecord 机制实现数据操作。从目录结构看crmeb/app/dao下按业务域划分了activity、agent、article、diy、order、other、product、service、shipping、sms、system、user、wechat等子目录对应商城系统的营销活动、分销代理、订单履约、商品、服务、物流、系统配置、用户、微信等核心业务模块。DAO 层带来的工程收益README 总结了使用 DAO 层的四大好处这些收益在 CRMEB 这种多端、多业务模块的大型商城中体现得尤为明显提供对象化的数据操作接口上层业务以方法调用替代手写 SQL代码可读性与复用性大幅提升屏蔽数据库差异提高移植性CRMEB 通过配置文件统一管理数据库连接业务代码不感知底层是 MySQL 还是其他数据库方便测试和扩展DAO 方法职责单一、入参出参类型明确便于单元测试与后续功能扩展实现业务和数据层的分离服务层Services只关心业务规则数据访问细节全部收敛到 DAO这也是 CRMEB 前后端分离、便于二次开发的关键设计之一。二、分层架构中的位置控制器 → 服务层 → DAO → 模型在 CRMEB 中一次典型的数据请求遵循如下调用链Controller控制器→ Services服务层→ DAO数据访问层→ Model模型→ 数据库以用户模块为例控制器接收并校验 HTTP 请求参数UserServices.php 通过构造函数注入UserDao见 UserServices.phppublic function __construct(UserDao $dao) { $this-dao $dao; }UserServices调用$this-dao-getList(...)、$this-dao-get($uid, $field)等 DAO 方法获取数据DAO 内部通过getModel()实例化对应的 Model再由 ThinkPHP 的 ActiveRecord 执行 SQL。服务层基类 BaseServices.php 中声明了protected $dao属性并通过 PHPDocmethod标注了大量可直接透传到 DAO 的方法get、getOne、batchUpdate、sum、update、be、value、count、getCount、getColumn、delete、save、saveAll、selectList、bcInc、bcDec、decStockIncSales、incStockDecSales等服务层因此可以把 DAO 当作自己的“数据能力底座”直接使用。这条链路清晰印证了 README 的结论——DAO 层负责项目底层数据操作其他业务要操作数据库时统一调用它。三、BaseDao 抽象基类DAO 层的统一能力底座crmeb/app/dao下所有 DAO 类都继承自 BaseDao.phpabstract class BaseDao。它做了三件核心的事统一模型来源、统一 CRUD 原语、统一查询搜索机制。3.1 统一模型来源setModel()BaseDao 定义了唯一一个必须由子类实现的抽象方法abstract protected function setModel(): string;每个业务 DAO 只需返回自己对应的模型类名即可绑定数据表。以 UserDao.php 为例protected function setModel(): string { return User::class; }其余所有继承的方法都通过getModel()获得模型实例protected function getModel() { return app()-make($this-setModel()); }即用容器解析模型类。这样新增一个 DAO 的成本被压缩到极小继承 BaseDao、实现 setModel、按需补充业务方法即可。此外 BaseDao 还提供了setJoinModel()钩子用于 join 联表查询时的模型设置。3.2 通用 CRUD 能力矩阵BaseDao 内置了一套覆盖面很全的通用方法全部以array $where为查询条件入口返回值以模型对象或think\Collection为主方法作用关键参数/默认值count($where, $search true)统计符合条件的记录数$searchtrue走搜索器机制selectList(...)条件列表查询$field*、$page0、$limit0、$order、$with[]、$searchfalseselectModel(...)返回可继续链式操作的模型同上返回BaseModelgetCount($where)不带搜索器的简单计数直接用where($where)getDistinctCount($where, $field)去重计数生成COUNT(distinct(field))get($id, $field, $with)按主键或条件取单条$id支持数组条件be($map, $field)判断记录是否存在返回 boolgetOne($where, $field, $with)按条件取单条内部转调get()value($where, $field)取单个字段值默认取主键getColumn($where, $field, $key)取字段值数组$key作为数组键名delete($id, $key)删除支持数组条件与指定键destroy($id, $force)软删除/真删除透传模型destroyupdate($id, $data, $key)更新支持数组条件batchUpdate($ids, $data, $key)批量更新基于whereInsave($data)插入单条调用模型createsaveAll($data)批量插入调用模型saveAllsum($where, $field, $search)字段求和支持搜索器getMax/getMin取最大/最小值空条件时作用于全表getFieldValue(...)取关联字段值透传模型能力3.3 搜索器机制search() 与 withSearchBaseDao 的search()是整个查询体系的核心入口它与 ThinkPHP 的**模型搜索器Search Scope**深度结合public function search(array $where [], bool $search true) { if ($where) { return $this-withSearchSelect($where, $search); } else { return $this-getModel(); } }其实现逻辑见getSearchData()与withSearchSelect()BaseDao.php为通过ReflectionClass反射出当前模型类的所有方法对$where中的每个键拼接出search . Str::studly($key) . Attr方法名若模型存在该方法则将该键归入搜索器集合$with交给 ThinkPHP 的withSearch()处理不存在的键则转为普通where条件[字段, , 值]且三元数组条件会原样保留支持自定义操作符如BETWEEN通过filterWhere()过滤掉表中不存在的字段避免 SQL 报错BaseDao.php。这正是 README 所说“提供丰富的查询条件以方便调用”的实现原理。以用户模型 User.php 为例它定义了searchUidAttr、searchAccountAttr、searchPwdAttr、searchUidsAttr、searchLikeAttr、searchPhoneAttr、searchGroupIdAttr、searchIsPromoterAttr、searchStatusAttr、searchLevelAttr、searchSpreadUidAttr、searchUserTypeAttr、searchPayCountAttr、searchNicknameAttr等数十个搜索器见 User.php。其中searchUidAttr自动区分单值与数组public function searchUidAttr($query, $value) { if (is_array($value)) $query-whereIn(uid, $value); else $query-where(uid, $value); }这意味着业务层只要往$where里塞[uid [1,2,3]]DAO 就会自动生成IN查询调用方无需关心 SQL 细节。3.4 业务级复合操作库存、金额与并发安全除了基础 CRUDBaseDao 还沉淀了商城业务高频使用的一组“复合操作”高精度金额增减bc()/bcInc()/bcDec()基于 PHP 的bcmath扩展实现金额的高精度加减支持指定精度$acc默认 2 位小数减法时若余额不足返回 false避免浮点误差与透支BaseDao.php减库存加销量 / 加库存减销量decStockIncSales()与incStockDecSales()在商品下单、退款场景中同步维护stock库存与sales销量两个字段并支持秒杀等活动的quota限购额度联动其中incStockDecSales会先读取当前销量回滚时销量不会扣成负数BaseDao.phpgetFieldValue透传模型的关联字段取值能力用于组合查询。这些复合操作把“多字段事务性更新”的细节封闭在 DAO 内服务层无需重复实现。四、业务 DAO 实战以 UserDao 为例的调用示范UserDao.php 是 CRMEB 中最典型的业务 DAO它在继承 BaseDao 通用能力的基础上针对用户模块补充了大量业务方法。下面从分页列表、关联预载入、聚合统计三个角度拆解其写法。4.1 带搜索器的分页列表public function getList(array $where, string $field *, int $page 0, int $limit 0): array { return $this-search($where)-field($field)-with([label]) -when($page $limit, function ($query) use ($page, $limit) { $query-page($page, $limit); })-select()-toArray(); }这里search($where)先应用模型搜索器再通过with([label])预载入关联用户标签when($page $limit, ...)实现条件分页最终以数组返回。对应服务层 UserServices.php 中的getUserList()它先从getPageValue()取分页参数再调用$this-dao-getList($where, $field, $page, $limit)与$this-getCount($where)组装出compact(list, count)返回给控制器。4.2 关联聚合与条件预载入分销场景的getAgentUserList()展示了 DAO 的“高级用法”——用闭包定义关联子查询做聚合统计return $this-search($where)-field($field)-with([ extract function ($query) { $query-field(sum(extract_price) as extract_count_price,count(id) as extract_count_num,uid) -where(status, 1)-group(uid); }, order function ($query) { $query-field(sum(pay_price) as order_price,count(id) as order_count,uid) -where(paid, 1)-where(refund_status, 0)-whereIn(pid, [-1, 0])-group(uid); }, bill function ($query) { $query-field(sum(number) as brokerage_money,uid) -where(category, now_money)-where(type, brokerage) -where(status, 1)-where(pm, 1)-group(uid); }, spreadCount function ($query) { $query-field(count(*) as spread_count,spread_uid)-group(spread_uid); }, spreadUser function ($query) { $query-field(uid,phone,nickname); }, agentLevel function ($query) { $query-field(id,name); } ])-when($page $limit, function ($query) use ($page, $limit) { $query-page($page, $limit); })-order(uid desc)-select()-toArray();一次查询即可同时拿到该用户的提现统计、已支付订单统计、分销佣金总额、下级人数、上级信息、代理等级是 DAO 层支撑分销报表类页面的典型实现。4.3 原生聚合与时间窗口统计getTrendData()展示了在 DAO 内直接使用 ThinkPHP 时间查询与 SQL 聚合函数public function getTrendData($time, $type, $timeType) { return $this-getModel() -when($type ! , function ($query) use ($type) { $query-where(user_type, $type); }) -where(function ($query) use ($time) { if ($time[0] $time[1]) { $query-whereDay(add_time, $time[0]); } else { $time[1] date(Y/m/d, strtotime($time[1]) 86400); $query-whereTime(add_time, between, $time); } }) -field(FROM_UNIXTIME(add_time,$timeType) as days,count(uid) as num) -group(days)-select()-toArray(); }这类方法把时间粒度格式化$timeType如%Y-%m-%d、日期区间边界处理都封装在 DAO 中服务层只需传入起止时间与统计维度即可。五、新增一个业务 DAO 的落地路径结合 CRMEB 现有代码的约定在二次开发中新增数据访问能力只需四步确定业务域与模型在 crmeb/app/dao 对应子目录没有则新建创建XxxDao.php命名遵循「业务名 Dao」一个 DAO 对应一个数据表或业务模块继承 BaseDao 并实现 setModel()返回对应模型类名如return Xxx::class;即可获得 BaseDao 的全部通用能力在模型上按需声明搜索器在 crmeb/app/model 对应模型类中新增searchXxxAttr($query, $value)方法DAO 的search()即可自动识别并应用在 DAO 中补充业务方法将组合查询、聚合统计、库存/金额联动等操作封装为语义化方法供服务层调用。调用侧保持与服务层一致的惯例——在XxxServices的构造函数中注入XxxDaopublic function __construct(XxxDao $dao) { $this-dao $dao; }常见注意事项软删除与物理删除需要保留数据时使用destroy($id, $force false)走模型软删除delete()为直接删除务必确认业务语义金额操作必须走 bcmath涉及余额、佣金、退款等场景请使用bcInc/bcDec而非浮点运算CRMEB 中用户余额操作如 UserDao.php 的incPayCount、incField均以inc/dec数据库原子自增实现避免并发覆盖where 键名与模型字段一致filterWhere()会过滤表中不存在的条件字段非表字段的自定义条件需借助模型搜索器承载分页参数统一由服务层注入参考 BaseServices.php 的getPageValue()它从请求参数读取page/limit并受database.page.limitMax等配置约束。六、与模型层的关系ActiveRecord 的最后一公里BaseDao 的底层数据操作最终落在模型对象上。CRMEB 的模型统一继承自 BaseModel.phpclass BaseModel extends Model即 ThinkPHP 6 的think\Model并通过ModelTrait混入额外的查询与业务辅助能力namespace crmeb\basic; use crmeb\traits\ModelTrait; use think\db\Query; use think\Model; class BaseModel extends Model { }这使得 DAO 返回的模型对象既是“数据行”又是“查询构建器”既能直接-save()持久化修改也能继续-with()、-page()、-group()等链式操作。DAO 方法如selectModel()刻意返回BaseModel而不是数组正是为了让上层有最大的链式自由度而面向控制器输出时再通过-toArray()或-select()-toArray()转成数组。七、小结回到 crmeb/app/dao/README.md 的核心论断CRMEB 的 dao 目录负责项目底层数据操作其他业务统一通过它访问数据库。通过本文可以确认这一设计并非一句口号而是由BaseDao的抽象方法 通用 CRUD 矩阵 搜索器反射机制 业务复合操作 各业务 DAO 的分层继承共同落实的面向数据表一个 DAO 对应一张表/一个业务模块setModel()统一模型来源面向业务以模型对象为出入参提供丰富条件查询与关联聚合屏蔽 SQL 细节面向工程屏蔽数据库差异、降低开发难度、便于测试与扩展实现业务与数据层的彻底分离。对于准备基于 CRMEB 做二次开发的开发者而言理解 DAO 层这套「继承 BaseDao → 实现 setModel → 按需添加搜索器与业务方法」的模式就等于拿到了通往 CRMEB 数据层的钥匙。赞分享后端电商前端【免费下载链接】CRMEBCRMEB开源商城免费开源多语言商城系统Tp6框架商城系统可商用包含小程序商城、H5商城、公众号商城、PC商城、App支持分销、拼团、砍价、秒杀、优惠券、积分、会员等级、小程序直播、页面DIY前后端分离方便二开更有详细使用文档、接口文档、数据字典、二开文档/视频教程欢迎大家提出宝贵意见和建议项目地址https://gitcode.com/gh_mirrors/cr/CRMEB点击查看免费下载相关推荐Java 数据访问对象DAO模式深度实战在 java-design-patterns 项目中实现数据库抽象层Java 数据访问对象DAO模式深度实战在 java design patterns 项目中实现数据库抽象层 导读 本文围绕 java design pa示例工程教程Actual-Server数据访问对象数据持久化抽象层Actual Server数据访问对象数据持久化抽象层 痛点场景本地优先应用的数据持久化挑战 你是否曾经在开发本地优先Local First应用时面临后端金融科技数据同步Actual-Server数据访问层数据库操作抽象Actual Server数据访问层数据库操作抽象 痛点传统数据库操作的复杂性 你是否曾经在Node.js项目中遇到过这样的困扰每次编写数据库操作代码时后端金融科技数据同步上一篇突破性四足机器人控制框架从强化学习到模型预测的完整技术指南下一篇揭秘萌音播放器高颜值动漫风格音乐播放器完整实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考