Skip to content

Service 开发

Service 是业务逻辑层,负责编排业务流程、事务管理和触发事件。所有 Service 继承 core\base\Service 基类。

基类功能

自动依赖注入

与 Controller 相同,基类通过反射自动注入子类声明的 protected 类型属性:

php
namespace app\service\system;

use app\repository\system\AdminRepository;
use app\repository\system\RoleRepository;
use core\base\Service;
use core\auth\TokenManager;

class AdminService extends Service
{
    protected AdminRepository $adminRepository;   // 自动注入
    protected RoleRepository $roleRepository;     // 自动注入
    protected TokenManager $tokenManager;          // 自动注入
}

事件触发

通过 $this->trigger() 方法触发事件,将副作用委托给 Listener 处理:

php
// 登录成功后触发事件
$this->trigger('admin.login.success', [
    'admin_id' => $admin['id'],
    'username' => $admin['username'],
    'ip'       => $ip,
    'user_agent' => $userAgent,
]);

// 登录失败后触发事件
$this->trigger('admin.login.failed', [
    'username' => $username,
    'ip'       => $ip,
    'message'  => '密码错误',
]);

日志记录

使用 $this->log() 方法写入文件日志(非数据库):

php
$this->log('管理员创建成功', ['admin_id' => $id], 'info');
$this->log('支付回调处理失败', ['order_no' => $orderNo], 'error');

业务异常

使用 $this->throwBusinessException() 抛出业务异常:

php
$this->throwBusinessException('用户名已存在');
$this->throwBusinessException('余额不足', 400);

// 也可直接抛出
throw new BusinessException('账户已被禁用');

缓存操作

php
// 读取缓存
$value = $this->cache('config_key');

// 写入缓存,默认 3600 秒
$this->cache('config_key', $value);
$this->cache('config_key', $value, 7200);

// 删除缓存
$this->forgetCache('config_key');

// 缓存标签
$this->cacheTag('config')->clear();

事务管理

数据库事务在 Service 层管理,使用 Db::startTrans() / commit() / rollback()

php
use think\facade\Db;

public function createAdmin(array $data): array
{
    Db::startTrans();
    try {
        // 创建管理员
        $admin = $this->adminRepository->create($data);

        // 分配角色
        if (!empty($data['role_ids'])) {
            $this->adminRepository->assignRoles($admin['id'], $data['role_ids']);
        }

        Db::commit();

        // 事务成功后触发事件
        $this->trigger('admin.created', $admin);

        return $admin;
    } catch (\Exception $e) {
        Db::rollback();
        throw $e;
    }
}

事件触发时机

$this->trigger() 应放在 Db::commit() 之后,确保数据已持久化再通知 Listener。

数据访问规则

Service 中禁止直接操作 Model 或 Db 查询:

php
// 错误 — 直接使用 Model 静态方法
$admin = Admin::where('username', $username)->find();

// 错误 — 直接使用 Db
$result = Db::table('admins')->where('id', $id)->find();

// 正确 — 通过 Repository
$admin = $this->adminRepository->findByUsername($username);

唯一例外:Db::startTrans() / commit() / rollback() 的事务管理允许在 Service 中使用。

副作用判断标准

Service 方法中的逻辑应区分"核心流程"和"副作用":

类型放置位置示例
核心流程Service 方法内创建记录、更新状态、生成 Token
副作用Listener记录日志、发送通知、清除缓存、更新统计

判断标准:如果该操作失败不影响主流程,就放到 Listener 中。

完整示例

php
namespace app\service\system;

use app\repository\system\AdminRepository;
use core\base\Service;
use core\auth\TokenManager;
use core\exception\BusinessException;
use think\facade\Db;

class AdminService extends Service
{
    protected AdminRepository $adminRepository;
    protected TokenManager $tokenManager;

    /**
     * 管理员登录
     */
    public function login(string $username, string $password, string $ip, string $userAgent): array
    {
        $loginContext = ['username' => $username, 'ip' => $ip, 'user_agent' => $userAgent];

        $admin = $this->adminRepository->findByUsername($username);
        if (!$admin) {
            $this->trigger('admin.login.failed', $loginContext + ['message' => '用户名不存在']);
            throw new BusinessException(lang('auth.login_failed'));
        }

        if ($admin['status'] != 1) {
            $this->trigger('admin.login.failed', $loginContext + ['admin_id' => $admin['id'], 'message' => '账户已被禁用']);
            throw new BusinessException(lang('auth.account_disabled'));
        }

        if (!password_verify($password, $admin['password'])) {
            $this->trigger('admin.login.failed', $loginContext + ['admin_id' => $admin['id'], 'message' => '密码错误']);
            throw new BusinessException(lang('auth.login_failed'));
        }

        $token = $this->tokenManager->generate([
            'admin_id' => $admin['id'],
            'username' => $admin['username'],
            'type'     => 'admin',
        ]);

        // 副作用交给 Listener:更新登录信息 + 记录日志
        $this->trigger('admin.login.success', $loginContext + ['admin_id' => $admin['id']]);

        return ['token' => $token, 'admin' => $this->getAdminInfo((int) $admin['id'])];
    }

    /**
     * 更新管理员状态
     */
    public function updateStatus(int $id, int $status): bool
    {
        $admin = $this->adminRepository->find($id);
        if (!$admin) {
            $this->throwBusinessException('管理员不存在');
        }

        return $this->adminRepository->update($id, ['status' => $status]);
    }
}

注意事项

  • Service 只调用 Repository,不直接使用 Model 静态方法或 Db::table()
  • 事务控制(Db::startTrans/commit/rollback)放在 Service 层
  • 副作用逻辑通过 $this->trigger() 委托给 Listener
  • 初始化逻辑可重写 initialize() 方法

基于 MIT 许可发布