RuoYi BaseController: Template Method Pattern for Unified Controller Logic
This article analyzes RuoYi 3.9.2's BaseController class, which encapsulates common controller operations like date binding, pagination, response handling, and user context retrieval using the template method pattern, enabling consistent CRUD controller implementations across the framework.
Overview
Based on RuoYi 3.9.2 and Spring Boot 3, the BaseController in package com.ruoyi.common.core.controller provides a set of reusable methods for CRUD controllers such as SysUserController, SysRoleController, SysDeptController, and SysConfigController. Controllers like SysLoginController, CaptchaController, and CommonController do not inherit it because they do not need pagination or table responses.
The base class supplies a logger field and four categories of methods:
Initialization : initBinder registers a date converter.
Pagination : startPage, startOrderBy, clearPage, getDataTable.
Response : success, error, warn, toAjax, redirect.
User context : getLoginUser, getUserId, getDeptId, getUsername.
Logger Field
protected final Logger logger = LoggerFactory.getLogger(this.getClass());Using this.getClass() ensures the logger prints the actual subclass name, not BaseController, aiding debugging.
initBinder: Global Date Conversion
The @InitBinder annotated method registers a custom PropertyEditorSupport for Date.class:
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) {
setValue(DateUtils.parseDate(text));
}
});
}This runs before each request binding. The framework's DateUtils extends Apache Commons Lang3's DateUtils and supports formats like yyyy-MM-dd, yyyy-MM-dd HH:mm:ss, yyyy/MM/dd, yyyy.MM.dd. The parser is lenient: 2026-02-30 becomes March 2, 2026-13-01 becomes Jan 1, 2027; unmatched formats (e.g., ISO with 'T') return null without throwing. Business code must validate dates.
This conversion applies to form/query parameter binding (e.g., list(SysUser user)), not to @RequestBody JSON where @JsonFormat on BaseEntity fields handles serialization.
Pagination Methods: Standard Two-Step Query
All delegate to PageUtils and MyBatis PageHelper:
protected void startPage() { PageUtils.startPage(); }
protected void startOrderBy() {
PageDomain pageDomain = TableSupport.buildPageRequest();
if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) {
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
PageHelper.orderBy(orderBy);
}
}
protected void clearPage() { PageUtils.clearPage(); }
protected TableDataInfo getDataTable(List<?> list) {
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(HttpStatus.SUCCESS);
rspData.setMsg("查询成功");
rspData.setRows(list);
rspData.setTotal(new PageInfo(list).getTotal());
return rspData;
} startPage()reads pageNum (default 1), pageSize (default 10), orderByColumn, isAsc (default asc), reasonable (default true) from request, converts column to underscore, validates via SqlUtil.escapeOrderBySql (whitelist: alphanumeric, underscore, space, comma, dot, max 500 chars), stores in ThreadLocal; PageHelper interceptor appends LIMIT to the next query and cleans up. startOrderBy() only sets sorting, no pagination. clearPage() manually clears ThreadLocal when startPage() is called but no query executes (exception or branch skip), preventing leakage on thread reuse. getDataTable() wraps results in TableDataInfo; total comes from PageInfo (the list is a Page object). Without startPage(), total equals returned rows.
Example from SysConfigController:
public TableDataInfo list(SysConfig config) {
startPage();
List<SysConfig> list = configService.selectConfigList(config);
return getDataTable(list);
}Three lines handle pagination parameters, query, and table response; business code never touches PageHelper API.
Response Methods: Thin Wrapper Over AjaxResult
All delegate to AjaxResult static factories:
public AjaxResult success() { return AjaxResult.success(); }
public AjaxResult success(String message) { return AjaxResult.success(message); }
public AjaxResult success(Object data) { return AjaxResult.success(data); }
public AjaxResult error() { return AjaxResult.error(); }
public AjaxResult error(String message) { return AjaxResult.error(message); }
public AjaxResult warn(String message) { return AjaxResult.warn(message); }
protected AjaxResult toAjax(int rows) { return rows > 0 ? AjaxResult.success() : AjaxResult.error(); }
protected AjaxResult toAjax(boolean result) { return result ? success() : error(); }
public String redirect(String url) { return StringUtils.format("redirect:{}", url); } success(200), error (500), warn (601). Default messages: success() → "操作成功", error() → "操作失败"; warn requires explicit message.
Overload ambiguity: success(String) treats string as message; to return string as data, cast to Object or wrap. warn (601) used for business pre-check failures (e.g., delete department with children/users). In SysDeptController.remove:
public AjaxResult remove(@PathVariable Long deptId) {
if (deptService.hasChildByDeptId(deptId)) {
return warn("存在下级部门,不允许删除");
}
if (deptService.checkDeptExistUser(deptId)) {
return warn("部门存在用户,不允许删除");
}
deptService.checkDeptDataScope(deptId);
return toAjax(deptService.deleteDeptById(deptId));
}601 means request valid but business condition unmet; frontend shows warning toast, not error. Both 500 and 601 go to Promise failure branch. toAjax translates service return (int rows or boolean) to success/error. Service CRUD methods return int, so toAjax(int) is used; toAjax(boolean) is unused. redirect returns redirect:{url} for Spring MVC view resolution (HTTP 302). Unused in the front-end separated project; requires controller not annotated with @RestController.
User Methods: Current Login from Security Context
All build on getLoginUser():
public LoginUser getLoginUser() { return SecurityUtils.getLoginUser(); }
public Long getUserId() { return getLoginUser().getUserId(); }
public Long getDeptId() { return getLoginUser().getDeptId(); }
public String getUsername() { return getLoginUser().getUsername(); } SecurityUtils.getLoginUser()extracts Authentication from SecurityContextHolder, principal is the LoginUser set at login. If unavailable, a ServiceException with 401 is thrown (wrapped in try-catch), not null.
Usage in SysDictDataController.add:
public AjaxResult add(@Validated @RequestBody SysDictData dict) {
dict.setCreateBy(getUsername());
return toAjax(dictDataService.insertDictData(dict));
}One line fills creator, next translates insert result to unified response.
Design Reflections
Inheritance vs. Utility Class
Methods could be in a helper class injected into controllers. RuoYi chose inheritance because these methods appear in almost every CRUD controller; inheritance gives shortest call path ( startPage() vs helper.startPage()) without injection. Cost: consumes Java's single inheritance slot. In practice, controller layer rarely needs a second base class, so trade-off is worthwhile.
Not a Strict Template Method Pattern
Classic template method defines algorithm skeleton with abstract steps for subclasses. BaseController has no abstract methods or skeleton; it provides ready-made actions. The "template" in the title means "standardized controller writing style": pagination two-step, CRUD one-line toAjax, consistent across repo. It's a "template-style method collection" — subclasses reuse, not override.
@InitBinder in Base Class vs. Global @ControllerAdvice
Spring allows global @ControllerAdvice + @InitBinder for date conversion without inheritance. RuoYi places it in BaseController to align with existing base-class structure, avoiding extra config class. Downside: conversion only works for controllers inheriting BaseController; login, captcha, file upload, monitoring controllers lack it. If they need date binding, they must add their own. Both approaches valid; RuoYi's choice fits its base-class system.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Tech Enthusiast
Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
