15
0

base.md 21 KB

Poyee Base 框架说明文档

1. 概述

com.poyee.base 包是 Poyee Checklist 项目的核心基础框架,提供了一套完整的分层架构实现,包括控制层(Controller)、服务层(Service)、数据访问层(Mapper)以及相关的数据传输对象(DTO)、请求对象(Req)和实体对象(Entity)。该框架采用了面向对象的设计思想,通过抽象基类和泛型机制,实现了代码复用和业务逻辑的统一管理。

2. 核心组件

2.1 基础架构

2.1.1 控制层(Controller)

BaseController

BaseController 是所有控制器的基类,提供了通用的请求处理和日志记录功能。

public abstract class BaseController<S extends BaseService<T, R>, T extends BaseReq, R extends BaseDto> {
    @Autowired
    protected S baseService;
    
    // 日志记录
    public void console(String label, Object obj) { ... }
    
    // 分页查询
    public Result<R> listPage(T req, boolean checkUser) { ... }
    
    // 详情查询
    public Result<R> info(Integer id) { ... }
    
    // 获取请求对象
    public HttpServletRequest getRequest() { ... }
}

2.1.2 服务层(Service)

BaseService 接口

BaseService 接口定义了服务层的通用方法,包括查询、分页、计数等操作。

public interface BaseService<T extends BaseReq, R extends BaseDto> extends IService<R> {
    Result<R> listPage(T req, boolean checkUser);
    List<R> selectJoinAllList(MPJLambdaWrapper<R> wrapper, T req);
    QueryWrapper<R> checkWrapper(T req, boolean checkUser);
    Long selectCount(MPJLambdaWrapper<R> wrapper);
    List<R> getAllList(T req, boolean checkUser);
    Result<R> info(Integer id);
    Result<R> getOne(T req);
    Result<R> getOneDto(T req);
    UserInfo getUserInfo();
    <Q extends BaseReq> IPage<R> ipage(MPJLambdaWrapper<R> wrapper, Q req);
    <Q extends BaseReq> Result<R> ipageResult(MPJLambdaWrapper<R> wrapper, Q req);
    MPJLambdaWrapper<R> mpjWrapper(Object req);
    MPJLambdaWrapper<R> mpjWrapper(Object req, String alias);
    MPJLambdaWrapper<R> mpjWrapper(MPJLambdaWrapper<R> mpjLambdaWrapper, Object req, String alias);
}

BaseServiceImpl 实现类

BaseServiceImplBaseService 接口的实现类,提供了各种通用方法的具体实现。

public class BaseServiceImpl<M extends IBaseMapper<R>, T extends BaseReq, R extends BaseDto> 
        extends ServiceImpl<M, R> implements BaseService<T, R> {
    
    // 检查用户权限
    public <P extends BaseReq> void checkAndSetUserId(P p) { ... }
    
    // 分页查询
    @Override
    public Result<R> listPage(T req, boolean checkUser) { ... }
    
    // 获取单条数据
    @Override
    public Result<R> info(Integer id) { ... }
    
    // 构建查询条件
    @Override
    public QueryWrapper<R> checkWrapper(T req, boolean checkUser) { ... }
    
    // 构建MPJ查询条件
    @Override
    public MPJLambdaWrapper<R> mpjWrapper(Object req) { ... }
}

2.1.3 数据访问层(Mapper)

IBaseMapper 接口

IBaseMapper 接口继承了 MyBatis-Plus 的 BaseMapperMPJBaseMapper,提供了基础的数据库操作方法。

public interface IBaseMapper<T> extends BaseMapper<T>, MPJBaseMapper<T> {
    // 基础方法由 BaseMapper 和 MPJBaseMapper 提供
}

IBaseProvider 接口

IBaseProvider 接口定义了动态 SQL 构建的方法,用于复杂查询场景。

public interface IBaseProvider<T extends BaseReq, R extends BaseDto> {
    List<Map<String, Object>> selectListMap(@Param("req") T req, @Param("clazz") Class<R> clazz);
    long pageCount(String sql);
    List<Map<String, Object>> page(String sql);
    Map<String, Object> selectOneMap(@Param("req") T req, @Param("clazz") Class<R> clazz);
    Map<String, Object> selectByIdMap(@Param("id") Serializable id, @Param("clazz") Class<R> clazz);
    Map<String, Object> selectSumMap(@Param("req") T req, @Param("clazz") Class<R> clazz);
    int save(T req);
    int update(T req);
}

BaseProvider 实现类

BaseProviderIBaseProvider 接口的抽象实现,提供了 SQL 构建的核心功能。

public abstract class BaseProvider {
    // 数据库操作类型
    public DbMethodEnums dbMethod;
    // 表计数器
    public int tableCount = 0;
    // 是否分页
    public boolean isPage = false;
    // 存储 insert
    public Object insert;
    // 记录 UPDATE
    public Object update;
    // 记录 SELECT
    public Object select;
    // 主表信息
    public TableInfo superTable;
    // 分页查询
    public MPage mPage;
    // 标记是否前端传排序字段
    public boolean isFrontSort = false;
    // 分页查询
    public String pageCountSql;
    // 记录 INSERT key-value(线程安全)
    public final Map<String, Object> insertMap = new HashMap<>();
    // 记录 UPDATE key-value(线程安全)
    public final Map<String, Object> updateMap = new HashMap<>();
    // 存储表信息的映射(线程安全)
    public final Map<String, TableInfo> tableInfos = new ConcurrentHashMap<>();
    // 记录 WHERE 条件(线程安全)
    public final List<WhereInfo> whereInfos = new CopyOnWriteArrayList<>();
    // 记录 SELECT 列(线程安全)
    public final List<String> selectColumns = new CopyOnWriteArrayList<>();
    // 记录 LEFT JOIN 信息(线程安全)
    public final List<LeftJoinInfo> leftJoinInfos = new CopyOnWriteArrayList<>();
    // 记录 ORDER BY 信息(线程安全)
    // ...
}

2.2 数据对象

2.2.1 基础请求对象(BaseReq)

BaseReq 是所有请求对象的基类,提供了分页、排序等通用参数。

@Data
public class BaseReq {
    @ApiModelProperty(hidden = true)
    @TableField(exist = false)
    public static List<String> types = Arrays.asList("pageNo", "pageSize", "sidx", "sord", "orderBy", "limit", "desensit");
    
    @ApiModelProperty(value = "页码,查询时使用", notes = "查询时使用", reference = "1", example = "1")
    @TableField(exist = false)
    private Integer pageNo = 1;
    
    @ApiModelProperty(value = "每页数量,查询时使用", notes = "查询时使用", reference = "10", example = "10")
    @TableField(exist = false)
    private Integer pageSize = 10;
    
    @ApiModelProperty(value = "排序字段,查询时使用", notes = "查询时使用", reference = " createTime ")
    @TableField(exist = false)
    private String sidx;
    
    @ApiModelProperty(value = "排序规则,查询时使用", notes = "查询时使用", reference = " ase ")
    @TableField(exist = false)
    private String sord;
    
    @ApiModelProperty(hidden = true)
    @TableField(exist = false)
    private String orderBy;
    
    @ApiModelProperty(hidden = true)
    @TableField(exist = false)
    private Integer limit;
    
    public Integer getLimit() {
        return (pageNo - 1) * pageSize;
    }
}

2.2.2 基础数据传输对象(BaseDto)

BaseDto 是所有数据传输对象的基类,通常包含实体的基本属性和一些扩展属性。

@Data
public class BaseDto {
    // 基础属性
}

2.2.3 基础实体对象(BaseEntity)

BaseEntity 是所有实体对象的基类,提供了分页、排序等通用参数。

@Data
public class BaseEntity {
    @ApiModelProperty(hidden = true)
    @TableField(exist = false)
    public static List<String> types = Arrays.asList("pageNo", "pageSize", "sidx", "sord", "orderBy", "limit", "desensit");
    
    @ApiModelProperty(value = "页码,查询时使用", notes = "查询时使用", reference = "1")
    @TableField(exist = false)
    private Integer pageNo = 1;
    
    @ApiModelProperty(value = "每页数量,查询时使用", notes = "查询时使用", reference = "10")
    @TableField(exist = false)
    private Integer pageSize = 10;
    
    @ApiModelProperty(value = "排序字段,查询时使用", notes = "查询时使用", reference = " createTime ")
    @TableField(exist = false)
    private String sidx;
    
    @ApiModelProperty(value = "排序规则,查询时使用", notes = "查询时使用", reference = " ase ")
    @TableField(exist = false)
    private String sord;
    
    @ApiModelProperty(hidden = true)
    @TableField(exist = false)
    private String orderBy;
    
    @ApiModelProperty(hidden = true)
    @TableField(exist = false)
    private Integer limit;
    
    public static boolean notIn(String type) {
        return !types.contains(type);
    }
    
    public Integer getLimit() {
        return (pageNo - 1) * pageSize;
    }
}

2.2.4 结果对象(Result)

Result 是统一的响应结果对象,用于封装接口返回数据。

@Data
@ApiModel("通用返回结果")
@JsonIgnoreProperties(ignoreUnknown = true)
public class Result<T> implements Serializable {
    @ApiModelProperty("是否成功")
    private boolean success;
    
    @ApiModelProperty("状态码")
    private Integer code;
    
    @ApiModelProperty("返回消息")
    private String msg;
    
    @ApiModelProperty("返回数据")
    private T data;
    
    @ApiModelProperty("总记录数")
    private Long total;
    
    // 静态工厂方法
    public static Result ok() { ... }
    public static <R extends BaseDto> Result<R> ok(R data) { ... }
    public static Result error(String msg) { ... }
    public static Result error(Integer code, String msg) { ... }
    public static <R extends BaseDto> Result<R> page(IPage<R> iPage) { ... }
    // ...
}

2.2.5 用户信息对象(UserInfo)

UserInfo 封装了当前登录用户的信息,用于权限控制和数据过滤。

@Data
@ToString
public class UserInfo implements Serializable {
    // 用户ID
    private Integer id;
    // 用户ID
    private String userId;
    // 用户头像
    private String avatar;
    // 角色编码
    private String roleCode;
    // 角色编码
    private String role;
    // 请求时间
    private Long iat;
    // 过期时间
    private Long exp;
    // 名称
    private String displayName;
    // ...
    
    // 商家ID
    private Integer merchantId;
    // 商家头像
    private String merchantAvatar;
    // 商家名称
    private String merchantName;
    // 数据权限
    private DataAuth dataAuth;
    // 是否是商家
    private boolean merchant;
    // ...
    
    // 判断是否有合作伙伴角色权限
    public boolean hasPartnerRoleAuth(Roles role) { ... }
}

2.3 工具类和注解

2.3.1 国际化支持

i18n 注解

i18n 注解用于标记需要国际化的字段、方法或类。

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface i18n {
    boolean value() default true;
    String sheetName() default "";
    I18nFormat[] format() default {};
}

I18nUtils 工具类

I18nUtils 提供了获取国际化消息的方法。

@Configuration
public class I18nUtils {
    // 定义占位符的正则表达式模式
    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(\\d+)\\}");
    
    private MessageProperties messageProperties;
    
    // 存储消息的Map,键是消息键,值是包含语言和消息的Map
    private static Map<String, Map<String, String>> messages;
    
    // 初始化国际化工具类,并加载消息属性
    @Autowired
    public I18nUtils(MessageProperties messageProperties) { ... }
    
    // 根据枚举和参数获取国际化消息
    public static String get(I18nMessageEnums i18n, Object... args) { ... }
    
    // ...
}

I18nMessageEnums 枚举

I18nMessageEnums 定义了系统中所有的国际化消息。

@Getter
public enum I18nMessageEnums {
    // 请求成功
    SUCCESS("success", "成功", "zh"),
    // 请求失败
    REQUEST_ERROR("request_error", "请求失败", "zh"),
    // 无权操作
    NO_PERMISSION("no_permission", "无权操作{0}", "zh"),
    // ...
    
    private String code; // 多语言标识
    private String message; // 默认信息
    private String lang; // 默认语言
    
    I18nMessageEnums(String code, String message, String lang) { ... }
}

2.3.2 Servlet 工具类

ServletUtils 提供了获取请求、响应、会话以及用户信息的方法。

public class ServletUtils {
    // 获取请求对象
    public static HttpServletRequest getRequest() { ... }
    
    // 获取响应对象
    public static HttpServletResponse getResponse() { ... }
    
    // 获取会话对象
    public static HttpSession getSession() { ... }
    
    // 获取用户信息
    public static UserInfo getUserInfo() { ... }
    
    // 判断是否国际版
    public static boolean isI18n() { ... }
    
    // 判断接口是否支持国际版
    public static boolean isI18nSupport() { ... }
    
    // ...
}

2.3.3 数据库注解

Select 注解

Select 注解用于指定查询字段。

@Select(table = UserEntity.class, fieldName = "username", distinct = true)
private String username;

Where 注解

Where 注解用于指定查询条件。

@Where(fieldName = "status", operator = EQ)
private String status;

LeftJoin 注解

LeftJoin 注解用于指定左连接查询。

@LeftJoin(table = RoleEntity.class, leftTable = UserEntity.class, fieldName = "id", leftFieldName = "roleId")
private String roleName;

OrderBy 注解

OrderBy 注解用于指定排序字段。

@OrderBy(fieldName = "createTime", sort = "DESC")
private String createTime;

3. 使用示例

3.1 创建控制器

@Slf4j
@Api(value = "球队基础信息管理", tags = "球队基础信息管理")
@RestController
@RequestMapping("/api/v1/teamInfo")
public class TeamBaseInfoController extends BaseController<TeamBaseInfoService, TeamBaseInfoReq, TeamBaseInfoDto> {
    
    @ApiOperation(value = "详情", notes = "详情")
    @ApiResponses({
        @ApiResponse(code = 200, message = "返回结果:")
    })
    @Log(title = "详情", businessType = BusinessType.UPDATE)
    @UserLoginToken(faceVerify = false, roles = {Roles.ADMIN, Roles.SHIPPING, Roles.CUSTOMER})
    @i18n(format = {I18nFormat.SEARCH})  // 支持国际化
    @PostMapping("/detail/{id}")
    public Result<TeamBaseInfoDto> detail(@PathVariable("id") Long id) {
        return baseService.detail(id);
    }
}

3.2 创建服务

public interface TeamBaseInfoService extends BaseService<TeamBaseInfoReq, TeamBaseInfoDto> {
    Page<TeamInfoPageDto> page(TeamInfoPageReq req);
    Result add(TeamInfoCreateReq req);
    Result update(TeamInfoUpdateReq req);
    Result delete(RemoveInfoReq req);
    List<TeamBaseInfoDto> searchTeamInfosByTeamSimpleName(TeamBaseInfoReq req);
    List<TeamBaseInfoDto> getByIds(List<Long> ids);
}

3.3 实现服务

@Slf4j
@Service
public class TeamBaseInfoServiceImpl extends BaseServiceImpl<TeamBaseInfoMapper, TeamBaseInfoReq, TeamBaseInfoDto> implements TeamBaseInfoService {
    
    @Override
    public Result add(TeamInfoCreateReq req) {
        // 格式化简称
        String simpleName = ObjectUtil.getSimpleName(req.getNameEn());
        // 查询是否已经存在该名称
        TeamBaseInfoDto dto = (TeamBaseInfoDto) baseMapper.selectOne(
            new TeamBaseInfoReq(req.getSport(), simpleName), TeamBaseInfoDto.class);
        if (Objects.nonNull(dto) && Objects.nonNull(dto.getId())) {
            return Result.error(I18nUtils.get(DATA_EXIST, req.getNameEn()));
        }
        req.setSimpleName(simpleName);
        return Result.ret(baseMapper.insert(req));
    }
    
    @Override
    public Result update(TeamInfoUpdateReq req) {
        // 实现更新逻辑
        // ...
        return Result.ret(baseMapper.updateById(dto));
    }
    
    // 其他方法实现
    // ...
}

3.4 创建 Mapper

public interface TeamBaseInfoMapper extends IBaseMapper<TeamBaseInfoDto>, IBaseProvider<TeamBaseInfoReq, TeamBaseInfoDto> {
    // 可以添加自定义的查询方法
}

3.5 创建请求对象

@Data
@ApiModel(value = "球队基础信息请求参数", description = "球队基础信息请求参数")
@TableName("tzy_team_info")
public class TeamBaseInfoReq extends BaseReq {
    @ApiModelProperty(value = "运动")
    @TableField(value = "sport")
    @Where(fieldName = "sport", operator = EQ)
    private String sport;
    
    @ApiModelProperty(value = "简称")
    @TableField(value = "simple_name")
    @Where(fieldName = "simple_name", operator = EQ)
    private String simpleName;
    
    // 构造方法
    public TeamBaseInfoReq() {}
    
    public TeamBaseInfoReq(String sport, String simpleName) {
        this.sport = sport;
        this.simpleName = simpleName;
    }
}

3.6 创建数据传输对象

@Data
@ApiModel(value = "球队基础信息返回参数", description = "球队基础信息返回参数")
@TableName("tzy_team_info")
public class TeamBaseInfoDto extends BaseDto {
    @ApiModelProperty(value = "ID")
    @TableId(value = "id")
    private Long id;
    
    @ApiModelProperty(value = "区分是否商家创建:merchant=商家创建")
    @TableField(value = "code")
    private String code;
    
    @ApiModelProperty(value = "中文名")
    @TableField(value = "display_name")
    private String displayName;
    
    @ApiModelProperty(value = "英文名")
    @TableField(value = "display_name_en")
    private String displayNameEn;
    
    @ApiModelProperty(value = "运动")
    @TableField(value = "sport")
    private String sport;
    
    @ApiModelProperty(value = "头像")
    @TableField(value = "head_url")
    private String headUrl;
    
    @ApiModelProperty(value = "国际化:其他语言翻译:json 字符串")
    @TableField(value = "translations")
    private String translations;
    
    @ApiModelProperty(value = "简称")
    @TableField(value = "simple_name")
    private String simpleName;
}

4. 最佳实践

4.1 分层架构

  • 控制层(Controller):负责处理请求、参数校验和返回结果,不包含业务逻辑。
  • 服务层(Service):负责业务逻辑处理,包括数据校验、业务规则和事务管理。
  • 数据访问层(Mapper):负责数据库操作,包括 CRUD 和复杂查询。

4.2 代码生成

框架提供了代码生成模板,可以快速生成基础的 Controller、Service、ServiceImpl、Mapper 等类。

// controller.java.vm
package ${package.Controller};

import ${package.Parent}.base.controller.BaseController;
import ${package.Parent}.dto.${entity}Dto;
import ${package.Entity}.${entity};
import ${package.Service}.${table.serviceName};
// ...

@RestController
@RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")
public class ${table.controllerName} extends BaseController<${table.serviceName}, ${entity}, ${entity}Dto> {
}

4.3 国际化支持

使用 i18n 注解和 I18nUtils 工具类可以轻松实现国际化支持。

@i18n(format = {I18nFormat.SEARCH})  // 支持国际化
@PostMapping("/detail/{id}")
public Result<TeamBaseInfoDto> detail(@PathVariable("id") Long id) {
    return baseService.detail(id);
}

4.4 异常处理

使用全局异常处理器 GlobalExceptionHandler 统一处理异常,并返回友好的错误信息。

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(AuthException.class)
    public Result handleAuthException(AuthException e) {
        return Result.error(e.getCode(), e.getMessage());
    }
    
    @ExceptionHandler(BusinessException.class)
    public Result handleBusinessException(BusinessException e) {
        return Result.error(e.getCode(), e.getMessage());
    }
    
    // 其他异常处理方法
    // ...
}

4.5 数据权限控制

通过 UserInfoDataAuth 实现数据权限控制,确保用户只能访问有权限的数据。

// 在 BaseServiceImpl 中的 checkWrapper 方法中实现数据权限控制
UserInfo userInfo = checkUser ? ServletUtils.getUserInfo() : null;
if (null != userInfo && checkUser) {
    switch (userInfo.getDataAuth()) {
        case DEPT: // 部门[商家]级
            if ("merchantId".equals(name) || "merId".equals(name)) {
                wrapper = wrapper.eq(column, userInfo.getMerchantId());
            }
            break;
        case PERSON: // 个人
            if ("userId".equals(name) || "accountId".equals(name) || "createBy".equals(name)) {
                wrapper = wrapper.eq(column, userInfo.getId());
            }
            break;
        default:
            if (null != value) {
                wrapper = wrapper.eq(column, value);
            }
    }
}

5. 总结

Poyee Base 框架提供了一套完整的分层架构实现,通过抽象基类和泛型机制,实现了代码复用和业务逻辑的统一管理。该框架具有以下特点:

  1. 分层架构:清晰的控制层、服务层、数据访问层分离,职责明确。
  2. 代码复用:通过抽象基类和泛型机制,减少重复代码。
  3. 统一响应:使用 Result 对象统一封装接口返回数据。
  4. 国际化支持:通过 i18n 注解和 I18nUtils 工具类实现国际化。
  5. 数据权限控制:通过 UserInfoDataAuth 实现数据权限控制。
  6. 异常处理:使用全局异常处理器统一处理异常。

通过使用 Poyee Base 框架,可以快速构建高质量的企业级应用,提高开发效率和代码质量。