news 2026/10/14 2:42:47

CRMEB 商城系统 DAO 数据访问层深度解析:从 BaseDao 抽象基类到业务落地的分层实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CRMEB 商城系统 DAO 数据访问层深度解析:从 BaseDao 抽象基类到业务落地的分层实践
  • 后端
  • 电商
  • 前端

【免费下载链接】CRMEB

🔥CRMEB开源商城免费开源多语言商城系统,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.md,DAO(Data Access Object)层位于控制器(Controller)与模型(Model)之间,主要职责包括:

  1. 实现数据持久层访问,对数据库进行 CURD(增删改查)操作;
  2. 依赖数据库连接,实现对表的基本增删改查功能;
  3. 封装数据库操作原语(查询、插入、更新等),简化开发难度;
  4. 与数据库解耦,提供统一接口,便于扩展和维护。

具体到代码组织方式,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.php,public 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属性,并通过 PHPDoc@method标注了大量可直接透传到 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.php(abstract 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)统计符合条件的记录数$search=true走搜索器机制
selectList(...)条件列表查询$field='*'、$page=0、$limit=0、$order=''、$with=[]、$search=false
selectModel(...)返回可继续链式操作的模型同上,返回BaseModel
getCount($where)不带搜索器的简单计数直接用where($where)
getDistinctCount($where, $field)去重计数生成COUNT(distinct(field))
get($id, $field, $with)按主键或条件取单条$id支持数组条件
be($map, $field)判断记录是否存在返回 bool
getOne($where, $field, $with)按条件取单条内部转调get()
value($where, $field)取单个字段值默认取主键
getColumn($where, $field, $key)取字段值数组$key作为数组键名
delete($id, $key)删除支持数组条件与指定键
destroy($id, $force)软删除/真删除透传模型destroy
update($id, $data, $key)更新支持数组条件
batchUpdate($ids, $data, $key)批量更新基于whereIn
save($data)插入单条调用模型create
saveAll($data)批量插入调用模型saveAll
sum($where, $field, $search)字段求和支持搜索器
getMax/getMin取最大/最小值空条件时作用于全表
getFieldValue(...)取关联字段值透传模型能力

3.3 搜索器机制:search() 与 withSearch

BaseDao 的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)为:

  1. 通过ReflectionClass反射出当前模型类的所有方法;
  2. 对$where中的每个键,拼接出'search' . Str::studly($key) . 'Attr'方法名;
  3. 若模型存在该方法,则将该键归入搜索器集合$with,交给 ThinkPHP 的withSearch()处理;
  4. 不存在的键则转为普通where条件[字段, '=', 值],且三元数组条件会原样保留(支持自定义操作符如BETWEEN);
  5. 通过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 业务级复合操作:库存、金额与并发安全

除了基础 CRUD,BaseDao 还沉淀了商城业务高频使用的一组“复合操作”:

  • 高精度金额增减:bc()/bcInc()/bcDec()基于 PHP 的bcmath扩展实现金额的高精度加减,支持指定精度$acc(默认 2 位小数),减法时若余额不足返回 false,避免浮点误差与透支(BaseDao.php);
  • 减库存加销量 / 加库存减销量:decStockIncSales()与incStockDecSales()在商品下单、退款场景中同步维护stock(库存)与sales(销量)两个字段,并支持秒杀等活动的quota(限购额度)联动;其中incStockDecSales会先读取当前销量,回滚时销量不会扣成负数(BaseDao.php);
  • getFieldValue:透传模型的关联字段取值能力,用于组合查询。

这些复合操作把“多字段事务性更新”的细节封闭在 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 现有代码的约定,在二次开发中新增数据访问能力只需四步:

  1. 确定业务域与模型:在 crmeb/app/dao 对应子目录(没有则新建)创建XxxDao.php,命名遵循「业务名 + Dao」,一个 DAO 对应一个数据表或业务模块;
  2. 继承 BaseDao 并实现 setModel():返回对应模型类名,如return Xxx::class;,即可获得 BaseDao 的全部通用能力;
  3. 在模型上按需声明搜索器:在 crmeb/app/model 对应模型类中新增searchXxxAttr($query, $value)方法,DAO 的search()即可自动识别并应用;
  4. 在 DAO 中补充业务方法:将组合查询、聚合统计、库存/金额联动等操作封装为语义化方法,供服务层调用。

调用侧保持与服务层一致的惯例——在XxxServices的构造函数中注入XxxDao:

public 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.php(class 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 数据层的钥匙。

  • 后端
  • 电商
  • 前端

【免费下载链接】CRMEB

🔥CRMEB开源商城免费开源多语言商城系统,Tp6框架商城,系统可商用;包含小程序商城、H5商城、公众号商城、PC商城、App,支持分销、拼团、砍价、秒杀、优惠券、积分、会员等级、小程序直播、页面DIY,前后端分离,方便二开,更有详细使用文档、接口文档、数据字典、二开文档/视频教程,欢迎大家提出宝贵意见和建议

项目地址:https://gitcode.com/gh_mirrors/cr/CRMEB
点击查看免费下载

相关推荐

上一篇:突破性四足机器人控制框架:从强化学习到模型预测的完整技术指南
下一篇:揭秘萌音播放器:高颜值动漫风格音乐播放器完整实战指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 2:41:36

风电并网下电力系统低碳调度Matlab优化实践

1. 项目背景与核心挑战在新型电力系统建设背景下,风电等可再生能源的大规模并网给传统电力调度带来了全新挑战。去年参与某省级电网调度系统升级时,我们团队就深刻体会到:当风电渗透率超过30%后,仅靠传统确定性调度模型已无法应对…

作者头像 李华
网站建设 2026/10/14 2:41:15

转化的幻觉与象形的退场——“两创”问题的一个哲学重述

转化的幻觉与象形的退场——“两创”问题的一个哲学重述摘要:“创造性转化、创新性发展”作为新时代文化论述的核心方针,其实践指向清楚,但背后隐含着一个少被说破的预设:传统文化仿佛一宗现成材料,当代立场站在材料之…

作者头像 李华
网站建设 2026/10/14 2:40:55

基于Gin与TF-IDF的新闻推荐系统实践

1. 项目概述新闻推荐系统是当前互联网内容平台的核心组件之一。作为一名长期从事后端开发的工程师,我最近用Go语言的Gin框架实现了一个基于TF-IDF和余弦相似度的新闻推荐系统原型。这个项目从零开始搭建,完整实现了新闻数据的采集、处理、特征提取和相似…

作者头像 李华
网站建设 2026/10/14 2:40:03

Vue资讯项目第三次迭代:状态管理与性能优化实战

简介:这份资源是Vue.js新闻应用项目的第三次迭代源码包,面向已掌握Vue基础、希望通过完整项目巩固组件化开发与工程化配置的前端学习者。项目基于Vue CLI搭建,涵盖组件、路由、状态管理与样式组织等核心模块,适合作为练手项目或课…

作者头像 李华
网站建设 2026/10/14 2:39:53

金蝶云星空返利管理实施全攻略:政策配置、计算与结算避坑指南

简介:金蝶云星空 V7.3 产品培训课件中的返利管理专题,面向电商与分销领域的实施顾问、财务及运营人员,系统讲解如何通过返利政策激励经销商、提升销量与市场占有率。课件从返利管理的需求背景切入,围绕业务痛点逐步展开整体方案、…

作者头像 李华