# How to Extend BaseController for Custom Business Logic in ContiNew Admin

> Learn to extend BaseController in ContiNew Admin for custom business logic. Override preHandle or add methods to integrate your logic while keeping CRUD and token checks.

- Repository: [OpenContiNew/continew-admin](https://github.com/continew-org/continew-admin)
- Tags: how-to-guide
- Published: 2026-02-28

---

**Extend `BaseController` and override the `preHandle` hook or add new methods to inject custom logic while inheriting standard CRUD and Sa-Token permission checks.**

ContiNew Admin provides a robust foundation for REST APIs through its generic `BaseController` class. This article explains how to leverage inheritance and hook methods to add specialized endpoints or custom security rules without rewriting boilerplate code. By extending the base class located in `continew-common`, you inherit automatic CRUD operations, permission validation, and request signing bypass logic.

## Understanding the BaseController Architecture

The `BaseController` in [`continew-common/src/main/java/top/continew/admin/common/base/controller/BaseController.java`](https://github.com/continew-org/continew-admin/blob/main/continew-common/src/main/java/top/continew/admin/common/base/controller/BaseController.java) extends the framework's `AbstractCrudController` to provide a centralized entry point for RESTful operations. It implements a **pre-handle hook** that enforces Sa-Token permission checks, handles request signing bypass scenarios, and manages path-exclusion logic before executing any CRUD operation.

The class uses five generic type parameters to maintain type safety across layers:

- **S** – The service interface extending `BaseService<L, D, Q, C>`
- **L** – The list-type DTO used for pagination responses
- **D** – The detail-type DTO representing a single record
- **Q** – The query object encapsulating filter criteria
- **C** – The create/update request object for write operations

Concrete controllers declare which CRUD endpoints to expose using the `@CrudRequestMapping` annotation. This annotation triggers the generation of standard methods (list, get, create, update, delete, export) via the inherited `AbstractCrudController` implementation.

## Extending BaseController for Custom Endpoints

To add business-specific functionality, create a controller class that **extends** `BaseController` with the appropriate generic parameters. You can define new request-mapping methods using standard Spring MVC annotations (`@GetMapping`, `@PostMapping`, etc.) alongside the automatically generated CRUD endpoints.

The parent class injects the service layer into the protected `baseService` field, giving you direct access to business logic without additional wiring.

```java
package top.continew.admin.system.controller;

import top.continew.admin.common.base.controller.BaseController;
import top.continew.admin.system.model.req.user.ResetAttemptsReq;
import top.continew.admin.system.model.resp.user.UserResp;
import top.continew.admin.system.service.UserService;
import top.continew.admin.system.model.query.UserQuery;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@CrudRequestMapping(value = "/system/user", api = {Api.PAGE, Api.LIST, Api.GET, Api.CREATE, Api.UPDATE, Api.BATCH_DELETE})
public class CustomUserController
        extends BaseController<UserService, UserResp, UserResp, UserQuery, ResetAttemptsReq> {

    @SaCheckPermission("system:user:resetAttempts")
    @PostMapping("/{id}/reset-attempts")
    public void resetLoginAttempts(@PathVariable Long id) {
        // Directly call the service layer through the injected baseService
        baseService.resetLoginAttempts(id);
    }
}

```

## Overriding the Pre-Handle Hook for Custom Security Logic

The `preHandle` method in `BaseController` serves as the primary extension point for customizing security and validation logic. This method receives the target method, its arguments, and the CRUD API type, allowing you to intercept requests before they reach the business layer.

**Permission prefix generation** follows a consistent pattern managed by `CrudApiPermissionPrefixCache`. The system extracts a resource name from the controller class (e.g., `system:user`) and constructs permission strings as `prefix:apiName` (e.g., `system:user:create`). **Sign-request bypass** occurs automatically when the request contains a valid Sa-Token signature parameter, skipping permission checks for trusted clients.

Override `preHandle` to implement special rules while optionally delegating to `super.preHandle(...)` to retain default behavior:

```java
@Override
public void preHandle(CrudApi crudApi, Object[] args,
                      Method targetMethod, Class<?> targetClass) throws Exception {
    // Skip permission checks for the custom "reset-attempts" endpoint
    if ("resetLoginAttempts".equals(targetMethod.getName())) {
        return;
    }
    // Retain the default permission checks for all other methods
    super.preHandle(crudApi, args, targetMethod, targetClass);
}

```

Alternatively, apply the `@SaIgnore` annotation to individual methods or the entire class to completely bypass permission verification.

## Managing CRUD API Exposure

Control which standard endpoints are available by configuring the `api` attribute in `@CrudRequestMapping`. Pass an array of `Api` enum values to selectively enable pagination (`Api.PAGE`), listing (`Api.LIST`), retrieval (`Api.GET`), creation (`Api.CREATE`), updates (`Api.UPDATE`), or batch deletion (`Api.BATCH_DELETE`).

Omitting an enum value from the array prevents `AbstractCrudController` from generating that endpoint, effectively hiding it from the API surface. This approach allows you to expose only the operations relevant to your domain while maintaining the underlying infrastructure.

## Summary

- Extend `BaseController<S, L, D, Q, C>` with concrete generic types to inherit CRUD operations, permission checks, and request signing logic.
- Access the service layer through the protected `baseService` field without additional dependency injection.
- Add custom endpoints by defining standard Spring MVC mapping methods within the extended class.
- Override `preHandle(CrudApi, Object[], Method, Class<?>)` to implement custom security rules or bypass default permission checks for specific methods.
- Use `@CrudRequestMapping(api = {...})` to declaratively control which standard CRUD endpoints are exposed.
- Apply `@SaIgnore` to skip permission verification entirely, or rely on the automatic sign-request bypass for trusted clients.

## Frequently Asked Questions

### What is the difference between BaseController and AbstractCrudController?

`BaseController` extends `AbstractCrudController` and adds ContiNew Admin-specific security logic, including Sa-Token permission enforcement and request signing bypass. `AbstractCrudController` (part of the `continew-starter-extension-crud` dependency) provides the generic CRUD method implementations, while `BaseController` acts as the local customization layer for the admin system.

### How does the permission prefix get generated for custom endpoints?

`CrudApiPermissionPrefixCache` extracts the resource identifier from the controller class name and mapping path (e.g., `system:user`). For custom endpoints, you must explicitly specify the full permission string in `@SaCheckPermission` (e.g., `"system:user:resetAttempts"`), as the automatic prefix generation only applies to standard CRUD operations.

### Can I disable permission checks for specific methods?

Yes. Apply the `@SaIgnore` annotation to the method to skip all permission verification. Alternatively, override `preHandle` and return early for specific method names, or implement conditional logic that bypasses `super.preHandle()` while retaining it for other operations.

### How do I access the service layer in my custom controller?

The parent class injects the service implementation into the protected `baseService` field. Since your controller declares the service type as the first generic parameter (`S`), `baseService` is already typed to your specific service interface (e.g., `UserService`), allowing direct invocation of business methods without additional `@Autowired` declarations.