Token导航 LogoToken导航TokenDH.com
待分类需要联网github未标认证来源可访问许可证需确认审计通过

abp-service-patternsabp 服务模式

Agent Skill

abp-service-patterns 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

1,048

周安装

42

GitHub Stars

21

下载量

339
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:abp-service-patterns(abp 服务模式)
来源仓库:https://github.com/thapaliyabikendra/ai-artifacts
仓库路径:skills/abp-service-patterns
安装命令:
npx skills add https://github.com/thapaliyabikendra/ai-artifacts --skill abp-service-patterns
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/thapaliyabikendra/ai-artifacts --skill abp-service-patterns

简介

abp-service-patterns 提供 ABP 框架应用层的服务模式实现参考。

  • 适用于在 Codex、Claude、Cursor、Gemini CLI 中构建符合 ABP 规范的应用服务。
  • 通过 npx skills add 从 GitHub 仓库安装,包含应用服务类结构和依赖注入示例。
  • 安装前需确认项目采用 ABP 架构,避免在非标准项目中引入不必要依赖。
  • 当前无 SKILL.md 文件,功能依赖仓库内代码示例,建议查看源码了解完整用法。

SKILL.md

ABP Service Patterns

Application layer patterns for ABP Framework.

Application Service Pattern

public class PatientAppService : ApplicationService, IPatientAppService
{
    private readonly IRepository<Patient, Guid> _patientRepository;
    private readonly PatientManager _patientManager;  // Domain service
    private readonly ClinicApplicationMappers _mapper;

    public PatientAppService(
        IRepository<Patient, Guid> patientRepository,
        PatientManager patientManager,
        ClinicApplicationMappers mapper)
    {
        _patientRepository = patientRepository;
        _patientManager = patientManager;
        _mapper = mapper;
    }

    [Authorize(ClinicPermissions.Patients.Default)]
    public async Task<PatientDto> GetAsync(Guid id)
    {
        var patient = await _patientRepository.GetAsync(id);
        return _mapper.PatientToDto(patient);
    }

    [Authorize(ClinicPermissions.Patients.Create)]
    public async Task<PatientDto> CreateAsync(CreatePatientDto input)
    {
        var patient = await _patientManager.CreateAsync(
            input.FirstName, input.LastName, input.Email, input.DateOfBirth);
        return _mapper.PatientToDto(patient);
    }

    [Authorize(ClinicPermissions.Patients.Edit)]
    public async Task<PatientDto> UpdateAsync(Guid id, UpdatePatientDto input)
    {
        var patient = await _patientRepository.GetAsync(id);
        _mapper.UpdatePatientFromDto(input, patient);
        await _patientRepository.UpdateAsync(patient);
        return _mapper.PatientToDto(patient);
    }

    [Authorize(ClinicPermissions.Patients.Delete)]
    public async Task DeleteAsync(Guid id)
    {
        await _patientRepository.DeleteAsync(id);
    }
}

Object Mapping with Mapperly

ABP 10.x uses Mapperly (source generator) instead of AutoMapper.

// Application/ClinicApplicationMappers.cs
[Mapper]
public partial class ClinicApplicationMappers
{
    // Entity to DTO
    public partial PatientDto PatientToDto(Patient patient);
    public partial List<PatientDto> PatientsToDtos(List<Patient> patients);

    // DTO to Entity (creation)
    public partial Patient CreateDtoToPatient(CreatePatientDto dto);

    // DTO to Entity (update) - ignores Id
    [MapperIgnoreTarget(nameof(Patient.Id))]
    public partial void UpdatePatientFromDto(UpdatePatientDto dto, Patient patient);

    // Complex mapping with navigation properties
    [MapProperty(nameof(Appointment.Patient.FirstName), nameof(AppointmentDto.PatientName))]
    [MapProperty(nameof(Appointment.Doctor.FullName), nameof(AppointmentDto.DoctorName))]
    public partial AppointmentDto AppointmentToDto(Appointment appointment);
}

Register in Module:

public override void ConfigureServices(ServiceConfigurationContext context)
{
    context.Services.AddSingleton<ClinicApplicationMappers>();
}

Unit of Work

ABP automatically manages UoW for application service methods.

public class AppointmentAppService : ApplicationService
{
    // This method is automatically wrapped in a UoW
    // All changes are committed together or rolled back on exception
    public async Task<AppointmentDto> CreateAsync(CreateAppointmentDto input)
    {
        var patient = await _patientRepository.GetAsync(input.PatientId);
        patient.LastAppointmentDate = input.AppointmentDate;

        var appointment = new Appointment(
            GuidGenerator.Create(),
            input.PatientId,
            input.DoctorId,
            input.AppointmentDate);

        await _appointmentRepository.InsertAsync(appointment);

        // Both changes committed together automatically
        return _mapper.AppointmentToDto(appointment);
    }
}

Manual UoW Control:

[UnitOfWork(isTransactional: false)]  // Disable for read-only
public async Task GenerateLargeReportAsync() { }

public async Task ProcessBatchAsync(List<Guid> ids)
{
    foreach (var id in ids)
    {
        using (var uow = _unitOfWorkManager.Begin(requiresNew: true))
        {
            await ProcessItemAsync(id);
            await uow.CompleteAsync();
        }
    }
}

Filter DTO Pattern

Separate query filters from pagination for clean, self-documenting APIs.

Filter DTO:

public class PatientFilter
{
    public Guid? DoctorId { get; set; }
    public string? Name { get; set; }
    public string? Email { get; set; }
    public bool? IsActive { get; set; }
    public DateTime? CreatedAfter { get; set; }
    public DateTime? CreatedBefore { get; set; }
}

AppService with WhereIf:

public async Task<PagedResultDto<PatientDto>> GetListAsync(
    PagedAndSortedResultRequestDto input,
    PatientFilter filter)
{
    // Trim string inputs
    filter.Name = filter.Name?.Trim();
    filter.Email = filter.Email?.Trim();

    // Default sorting
    if (input.Sorting.IsNullOrWhiteSpace())
        input.Sorting = nameof(PatientDto.FirstName);

    var queryable = await _patientRepository.GetQueryableAsync();

    var query = queryable
        .WhereIf(filter.DoctorId.HasValue, x => x.DoctorId == filter.DoctorId)
        .WhereIf(!filter.Name.IsNullOrWhiteSpace(),
            x => x.FirstName.Contains(filter.Name) || x.LastName.Contains(filter.Name))
        .WhereIf(!filter.Email.IsNullOrWhiteSpace(),
            x => x.Email.ToLower().Contains(filter.Email.ToLower()))
        .WhereIf(filter.IsActive.HasValue, x => x.IsActive == filter.IsActive)
        .WhereIf(filter.CreatedAfter.HasValue, x => x.CreationTime >= filter.CreatedAfter)
        .WhereIf(filter.CreatedBefore.HasValue, x => x.CreationTime <= filter.CreatedBefore);

    var totalCount = await AsyncExecuter.CountAsync(query);

    var patients = await AsyncExecuter.ToListAsync(
        query.OrderBy(input.Sorting).PageBy(input.SkipCount, input.MaxResultCount));

    return new PagedResultDto<PatientDto>(totalCount, _mapper.PatientsToDtos(patients));
}

ResponseModel Wrapper

public class ResponseModel<T>
{
    public bool IsSuccess { get; set; }
    public T Data { get; set; }
    public string Message { get; set; }

    public static ResponseModel<T> Success(T data, string message = null)
        => new() { IsSuccess = true, Data = data, Message = message };

    public static ResponseModel<T> Failure(string message)
        => new() { IsSuccess = false, Message = message };
}

// Usage
public async Task<ResponseModel<PatientDto>> GetAsync(Guid id)
{
    var patient = await _patientRepository.FirstOrDefaultAsync(x => x.Id == id);
    if (patient == null)
        return ResponseModel<PatientDto>.Failure("Patient not found");

    return ResponseModel<PatientDto>.Success(_mapper.PatientToDto(patient));
}

CommonDependencies Pattern

Reduce constructor bloat by grouping cross-cutting dependencies.

public class CommonDependencies<T>
{
    public IDistributedEventBus DistributedEventBus { get; set; }
    public IDataFilter DataFilter { get; set; }
    public ILogger<T> Logger { get; set; }
    public IGuidGenerator GuidGenerator { get; set; }
}

// Register
context.Services.AddTransient(typeof(CommonDependencies<>));

// Usage
public class PatientAppService : ApplicationService
{
    private readonly IRepository<Patient, Guid> _patientRepository;
    private readonly CommonDependencies<PatientAppService> _common;

    public PatientAppService(
        IRepository<Patient, Guid> patientRepository,
        CommonDependencies<PatientAppService> common)
    {
        _patientRepository = patientRepository;
        _common = common;
    }

    public async Task<PatientDto> CreateAsync(CreatePatientDto input)
    {
        _common.Logger.LogInformation("Creating patient: {Name}", input.FirstName);
        var patient = new Patient(_common.GuidGenerator.Create(), /*...*/);
        await _patientRepository.InsertAsync(patient);
        await _common.DistributedEventBus.PublishAsync(new PatientCreatedEto { Id = patient.Id });
        return _mapper.PatientToDto(patient);
    }
}

Structured Logging

public async Task<PatientDto> CreateAsync(CreatePatientDto input)
{
    _logger.LogInformation(
        "[{Service}] {Method} - Started - Input: {@Input}",
        nameof(PatientAppService), nameof(CreateAsync), input);

    try
    {
        var patient = await _patientManager.CreateAsync(/*...*/);

        _logger.LogInformation(
            "[{Service}] {Method} - Completed - PatientId: {PatientId}",
            nameof(PatientAppService), nameof(CreateAsync), patient.Id);

        return _mapper.PatientToDto(patient);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex,
            "[{Service}] {Method} - Failed - Error: {Message}",
            nameof(PatientAppService), nameof(CreateAsync), ex.Message);
        throw;
    }
}

Input Sanitization

public static class InputSanitization
{
    public static string TrimAndLower(this string value) => value?.Trim()?.ToLowerInvariant();
    public static string TrimAndUpper(this string value) => value?.Trim()?.ToUpperInvariant();
}

// Usage
public async Task<PatientDto> CreateAsync(CreatePatientDto input)
{
    input.Email = input.Email.TrimAndLower();
    input.FirstName = input.FirstName?.Trim();
    // ...
}

Mapping Validation Patterns

Common Bug: Copy-Paste Property Mapping

Manual mappings (especially in select new clauses) are prone to copy-paste errors:

// ❌ BUG: Wrong property copied - IsPutawayCompleted mapped from wrong source!
select new LicensePlateDto()
{
    IsInboundQCChecklistCompleted = lc.IsInboundQCChecklistCompleted,
    IsPutawayCompleted = lc.IsInboundQCChecklistCompleted,  // BUG! Should be lc.IsPutawayCompleted
    IsHold = lc.IsHold
}

// ✅ CORRECT: Use Mapperly to prevent copy-paste errors
[Mapper]
public partial class LicensePlateMapper
{
    public partial LicensePlateDto ToDto(LicensePlate entity);
}

// Or if manual mapping is required, double-check similar-named properties
select new LicensePlateDto()
{
    IsInboundQCChecklistCompleted = lc.IsInboundQCChecklistCompleted,
    IsPutawayCompleted = lc.IsPutawayCompleted,  // ✅ Correct property
    IsHold = lc.IsHold
}

Manual Mapping Checklist

When manual mapping is unavoidable (e.g., complex projections), verify:

  • Each DTO property maps to the correct entity property
  • Similar-named properties double-checked (e.g., IsXxxCompleted vs IsYyyCompleted)
  • Null checks on optional navigation properties
  • No copy-paste from adjacent lines without modification

High-Risk Property Patterns

Be extra careful with these patterns that look similar:

DTO PropertyWrong SourceCorrect Source
IsPutawayCompletedentity.IsInboundCompletedentity.IsPutawayCompleted
UpdatedAtentity.CreatedAtentity.LastModificationTime
CustomerNameentity.ShipperNameentity.CustomerName
TargetDateentity.SourceDateentity.TargetDate

Best Practices

  1. Thin AppServices - Orchestrate, don't implement business logic
  2. Delegate to Domain - Use domain services for complex rules
  3. Use Mapperly - Source-generated mapping for performance (prevents copy-paste bugs)
  4. WhereIf pattern - Clean optional filtering
  5. Structured logging - Consistent format for tracing
  6. Input sanitization - Trim and normalize inputs
  7. Authorization - Always check permissions
  8. Verify manual mappings - Double-check similar-named property assignments

Related Skills

  • abp-entity-patterns - Domain layer patterns
  • abp-infrastructure-patterns - Cross-cutting concerns
  • fluentvalidation-patterns - Input validation

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

37.51%
按下载量换算127

Claude

28.32%
按下载量换算96

Cursor

16.91%
按下载量换算57

Gemini CLI

9.51%
按下载量换算32

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills