{
+ boolean USE_GENERATED_KEYS = true;
+ String KEY_PROPERTY = "id";
+
+ /**
+ * 选择性插入
+ *
+ * 仅插入非null字段
+ *
+ * @param entity 实体
+ * @return 影响行数
+ */
+ @InsertProvider(SqlProvider.class)
+ @Options(useGeneratedKeys = USE_GENERATED_KEYS, keyProperty = KEY_PROPERTY)
int insert(T entity);
+
+ /**
+ * 全字段插入
+ *
+ * 无论是否为null均进行插入
+ *
+ * @param entity 实体
+ * @return 影响行数
+ */
+ @InsertProvider(SqlProvider.class)
+ @Options(useGeneratedKeys = USE_GENERATED_KEYS, keyProperty = KEY_PROPERTY)
+ int insertAll(T entity);
+
+ /**
+ * 批量选择性插入
+ *
+ * @param list 实体列表
+ * @return 影响行数
+ */
+ @InsertProvider(SqlProvider.class)
+ @Options(useGeneratedKeys = USE_GENERATED_KEYS, keyProperty = KEY_PROPERTY)
+ int batchInsert(@Param("list") List list);
+
+ /**
+ * 根据ID删除
+ *
+ * @param id 主键ID
+ * @return 影响行数
+ */
+ @DeleteProvider(SqlProvider.class)
+ int delete(@Param("id") Long id);
+
+ /**
+ * 批量ID删除
+ *
+ * @param ids ID列表
+ * @return 影响行数
+ */
+ @DeleteProvider(SqlProvider.class)
+ int batchDelete(@Param("ids") List ids);
+
+ /**
+ * 根据条件删除
+ *
+ * @param example 条件构造器
+ * @return 影响行数
+ */
+ @DeleteProvider(SqlProvider.class)
+ int deleteByExample(@Param("example") Example example);
+
+ /**
+ * 选择性更新
+ *
+ * 仅更新非null字段
+ *
+ * @param entity 实体
+ * @return 影响行数
+ */
+ @UpdateProvider(SqlProvider.class)
+ int update(@Param("entity") T entity);
+
+ /**
+ * 全字段更新
+ *
+ * 无论是否为null均进行更新
+ *
+ * @param entity 实体
+ * @return 影响行数
+ */
+ @UpdateProvider(SqlProvider.class)
+ int updateAll(@Param("entity") T entity);
+
+ /**
+ * 根据条件选择性更新
+ *
+ * @param entity 实体
+ * @param example 条件构造器
+ * @return 影响行数
+ */
+ @UpdateProvider(SqlProvider.class)
+ int updateByExample(@Param("entity") T entity, @Param("example") Example example);
+
+ /**
+ * 根据条件更新全字段
+ *
+ * @param entity 实体
+ * @param example 条件构造器
+ * @return 影响行数
+ */
+ @UpdateProvider(SqlProvider.class)
+ int updateByExampleAll(@Param("entity") T entity, @Param("example") Example example);
+
+ /**
+ * 根据ID获取实体
+ *
+ * @param id 主键ID
+ * @return 实体对象
+ */
+ @SelectProvider(SqlProvider.class)
+ T get(@Param("id") Long id);
+
+ /**
+ * 批量获取实体
+ *
+ * @param ids ID列表
+ * @return 实体列表
+ */
+ @SelectProvider(SqlProvider.class)
+ List batchGet(@Param("ids") List ids);
+
+ /**
+ * 根据条件获取实体
+ *
+ * 多个时仅返回ID倒序排序首个
+ *
+ * @param example 条件构造器
+ * @return 实体对象
+ */
+ @SelectProvider(SqlProvider.class)
+ T getByExample(@Param("example") Example example);
+
+ /**
+ * 根据条件列表查询
+ *
+ * @param example 条件构造器
+ * @return 实体列表
+ */
+ @SelectProvider(SqlProvider.class)
+ List list(@Param("example") Example example);
+
+ /**
+ * 根据条件数量查询
+ *
+ * @param example 条件构造器
+ * @return 数量
+ */
+ @SelectProvider(SqlProvider.class)
+ int count(@Param("example") Example example);
}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/BaseService.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/BaseService.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8a886adf97a0afa0611e520fc7f625c7f086ec7
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/BaseService.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base;
+
+import cn.cliveyuan.robin.base.condition.PageQueryExample;
+import cn.cliveyuan.robin.base.condition.Query;
+import cn.cliveyuan.robin.base.common.Pagination;
+
+import java.util.List;
+
+/**
+ * 基础 Service
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public interface BaseService {
+
+ /**
+ * 选择性插入
+ *
+ * 仅插入非null字段
+ *
+ * @param entity 实体
+ * @return 影响行数
+ */
+ int insert(T entity);
+
+ /**
+ * 全字段插入
+ *
+ * 无论是否为null均进行插入
+ *
+ * @param entity 实体
+ * @return 影响行数
+ */
+ int insertAll(T entity);
+
+ /**
+ * 选择性保存
+ *
entity.id = null -> 插入
+ * entity.id != null -> 更新
+ *
+ * @param entity 实体
+ * @return 影响行数
+ */
+ int save(T entity);
+
+ /**
+ * 全字段保存
+ * entity.id = null -> 插入
+ * entity.id != null -> 更新
+ *
+ * @param entity 实体
+ * @return 影响行数
+ */
+ int saveAll(T entity);
+
+ /**
+ * 批量选择性插入
+ *
+ * @param list 实体列表
+ * @return 影响行数
+ */
+ int batchInsert(List list);
+
+ /**
+ * 根据ID删除
+ *
+ * @param id 主键ID
+ * @return 影响行数
+ */
+ int delete(Long id);
+
+ /**
+ * 批量ID删除
+ *
+ * @param ids ID列表
+ * @return 影响行数
+ */
+ int batchDelete(List ids);
+
+ /**
+ * 根据条件删除
+ *
+ * @return 影响行数
+ */
+ int deleteByExample(Query query);
+
+ /**
+ * 选择性更新
+ *
+ * 仅更新非null字段
+ *
+ * @param entity 实体
+ * @return 影响行数
+ */
+ int update(T entity);
+
+ /**
+ * 全字段更新
+ *
+ * 无论是否为null均进行更新
+ *
+ * @param entity 实体
+ * @return 影响行数
+ */
+ int updateAll(T entity);
+
+ /**
+ * 根据条件选择性更新
+ *
+ * @param entity 实体 (set)
+ * @param query 查询条件 (where)
+ * @return 影响行数
+ */
+ int updateByExample(T entity, Query query);
+
+ /**
+ * 根据条件更新全字段
+ *
+ * @param entity 实体 (set)
+ * @param query 查询条件 (where)
+ * @return 影响行数
+ */
+ int updateByExampleAll(T entity, Query query);
+
+ /**
+ * 根据ID获取实体
+ *
+ * @param id 主键ID
+ * @return 实体对象
+ */
+ T get(Long id);
+
+ /**
+ * 批量获取实体
+ *
+ * @param ids ID列表
+ * @return 实体列表
+ */
+ List batchGet(List ids);
+
+ /**
+ * 根据条件获取实体
+ *
+ * 多个时仅返回ID倒序排序首个
+ *
+ * @param query 查询条件
+ * @return 实体对象
+ */
+ T getByExample(Query query);
+
+ /**
+ * 根据条件数量查询
+ *
+ * @param query 查询条件
+ * @return 条数
+ */
+ int count(Query query);
+
+ /**
+ * 根据条件列表查询
+ *
+ * @param query 查询条件
+ * @return 实体列表
+ */
+ List list(Query query);
+
+ /**
+ * 分页查询
+ *
+ * @param query 查询条件
+ * @return 实体分页
+ */
+ Pagination page(PageQueryExample query);
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/BaseServiceImpl.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/BaseServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..7b9afb651caac88ed09339fad64b4d2312393953
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/BaseServiceImpl.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base;
+
+import cn.cliveyuan.robin.base.condition.Criteria;
+import cn.cliveyuan.robin.base.condition.Example;
+import cn.cliveyuan.robin.base.condition.GeneratedCriteria;
+import cn.cliveyuan.robin.base.condition.LambdaCriteria;
+import cn.cliveyuan.robin.base.condition.PageQueryExample;
+import cn.cliveyuan.robin.base.condition.Query;
+import cn.cliveyuan.robin.base.common.Pagination;
+import cn.cliveyuan.robin.base.util.ReflectUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.Assert;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 基础 Service 实现类
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+@SuppressWarnings({"serial", "unchecked"})
+public class BaseServiceImpl implements BaseService {
+
+ @Autowired
+ private BaseMapper baseMapper;
+
+ @Override
+ public int insert(T entity) {
+ this.checkEntity(entity);
+ return baseMapper.insert(entity);
+ }
+
+ @Override
+ public int insertAll(T entity) {
+ this.checkEntity(entity);
+ return baseMapper.insertAll(entity);
+ }
+
+ @Override
+ public int save(T entity) {
+ this.checkEntity(entity);
+ return ReflectUtils.isIdNull(entity) ? this.insert(entity) : this.update(entity);
+ }
+
+ @Override
+ public int saveAll(T entity) {
+ this.checkEntity(entity);
+ return ReflectUtils.isIdNull(entity) ? this.insertAll(entity) : this.updateAll(entity);
+ }
+
+ @Override
+ public int batchInsert(List list) {
+ this.checkEntityList(list);
+ return baseMapper.batchInsert(list);
+ }
+
+ @Override
+ public int delete(Long id) {
+ this.checkId(id);
+ return baseMapper.delete(id);
+ }
+
+ @Override
+ public int batchDelete(List ids) {
+ this.checkIdList(ids);
+ return baseMapper.batchDelete(ids);
+ }
+
+ @Override
+ public int deleteByExample(Query query) {
+ this.checkQuery(query);
+ return baseMapper.deleteByExample(this.convertQueryToExample(query));
+ }
+
+ @Override
+ public int update(T entity) {
+ this.checkEntity(entity);
+ return baseMapper.update(entity);
+ }
+
+ @Override
+ public int updateAll(T entity) {
+ this.checkEntity(entity);
+ return baseMapper.updateAll(entity);
+ }
+
+ @Override
+ public int updateByExample(T entity, Query query) {
+ this.checkEntity(entity);
+ return baseMapper.updateByExample(entity, this.convertQueryToExample(query));
+ }
+
+ @Override
+ public int updateByExampleAll(T entity, Query query) {
+ this.checkEntity(entity);
+ return baseMapper.updateByExampleAll(entity, this.convertQueryToExample(query));
+ }
+
+ @Override
+ public T get(Long id) {
+ this.checkId(id);
+ return baseMapper.get(id);
+ }
+
+ @Override
+ public List batchGet(List ids) {
+ this.checkIdList(ids);
+ return baseMapper.batchGet(ids);
+ }
+
+ @Override
+ public T getByExample(Query query) {
+ this.checkQuery(query);
+ return baseMapper.getByExample(this.convertQueryToExample(query));
+ }
+
+ @Override
+ public int count(Query query) {
+ this.checkQuery(query);
+ return baseMapper.count(this.convertQueryToExample(query));
+ }
+
+ @Override
+ public List list(Query query) {
+ this.checkQuery(query);
+ return baseMapper.list(this.convertQueryToExample(query));
+ }
+
+ @Override
+ public Pagination page(PageQueryExample query) {
+ this.checkQuery(query);
+ Example example = this.convertQueryToExample(query);
+ int page = this.getPage(query.getPageNo());
+ int rowsPerPage = this.getRowsPerPage(query.getPageSize());
+ example.setLimitStart(this.getOffset(page, rowsPerPage));
+ example.setLimitEnd(rowsPerPage);
+ int totalCount = baseMapper.count(example);
+ if (totalCount == 0) {
+ return Pagination.buildEmpty();
+ }
+ return new Pagination<>(totalCount, baseMapper.list(example), page, rowsPerPage);
+ }
+
+ private Class> getEntityClass() {
+ return ReflectUtils.getSuperClassGenericType(getClass(), 0);
+ }
+
+ private Example convertQueryToExample(Query query) {
+ T entity = query.getEntity();
+ if (Objects.nonNull(entity)) {
+ Map fieldAndValue = ReflectUtils.resolveEntityFieldAndValue(entity);
+ GeneratedCriteria criteria = query.getExistCriteria();
+ if (criteria instanceof LambdaCriteria) {
+ LambdaCriteria lambdaCriteria = (LambdaCriteria) criteria;
+ fieldAndValue.forEach(lambdaCriteria::eq);
+ } else if (criteria instanceof Criteria) {
+ fieldAndValue.forEach(criteria::eq);
+ }
+ }
+ return (Example) query;
+ }
+
+ private void checkId(Long id) {
+ Assert.notNull(id, "id can't be null");
+ }
+
+ private void checkEntity(T entity) {
+ Assert.notNull(entity, "entity can't be null");
+ }
+
+ private void checkQuery(Query query) {
+ Assert.notNull(query, "query can't be null");
+ }
+
+ private void checkIdList(List idList) {
+ Assert.isTrue(Objects.nonNull(idList) && idList.size() > 0, "ids can't be empty");
+ }
+
+ private void checkEntityList(List list) {
+ Assert.isTrue(Objects.nonNull(list) && list.size() > 0, "list can't be empty");
+ }
+
+ private int getPage(Integer page) {
+ if (Objects.isNull(page) || page <= 0) {
+ page = PageQueryExample.DEFAULT_PAGE_NO;
+ }
+ return page;
+ }
+
+ private int getRowsPerPage(Integer rowsPerPage) {
+ if (Objects.isNull(rowsPerPage) || rowsPerPage <= 0) {
+ rowsPerPage = PageQueryExample.DEFAULT_PAGE_SIZE;
+ }
+ return rowsPerPage;
+ }
+
+ private int getOffset(int page, int rowsPerPage) {
+ return (page - 1) * rowsPerPage;
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/annotation/TableField.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/annotation/TableField.java
new file mode 100644
index 0000000000000000000000000000000000000000..691807eb4fcc1e3648671c947714d00d0bd379d0
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/annotation/TableField.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 表字段标识
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
+public @interface TableField {
+
+ /**
+ * 数据库字段值
+ */
+ String value() default "";
+
+ /**
+ * 是否保存时忽略
+ */
+ boolean ignoreSaving() default false;
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/annotation/TableId.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/annotation/TableId.java
new file mode 100644
index 0000000000000000000000000000000000000000..96048ab14b72d33718e7f1afa4093b7ef51bbf9f
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/annotation/TableId.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 表主键标识
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
+public @interface TableId {
+ /**
+ * 字段名(该值可无)
+ */
+ String value() default "";
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/annotation/TableName.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/annotation/TableName.java
new file mode 100644
index 0000000000000000000000000000000000000000..cb35086cbb098289dcab1fc387625365f43106de
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/annotation/TableName.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 表名标识
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
+public @interface TableName {
+ /**
+ * 实体对应的表名
+ */
+ String value() default "";
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/common/ApiErrorCode.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/common/ApiErrorCode.java
new file mode 100644
index 0000000000000000000000000000000000000000..82f8da1c597a5bcf5e03bd56fd9484614a2bdb84
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/common/ApiErrorCode.java
@@ -0,0 +1,32 @@
+package cn.cliveyuan.robin.base.common;
+
+/**
+ * @author Clive Yuan
+ * @date 2020/12/24
+ */
+public enum ApiErrorCode {
+ /**
+ * 成功
+ */
+ SUCCESS(0, "success"),
+ /**
+ * 失败
+ */
+ FAIL(-1, "fail");
+
+ private final int code;
+ private final String msg;
+
+ ApiErrorCode(int code, String msg) {
+ this.code = code;
+ this.msg = msg;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/common/ApiResponse.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/common/ApiResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d143ca3f9504cf32c3ac2b451b49ccc84fdf9fe
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/common/ApiResponse.java
@@ -0,0 +1,110 @@
+package cn.cliveyuan.robin.base.common;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * 统一接口响应对象
+ *
+ * @author Clive Yuan
+ * @date 2020/12/24
+ */
+public class ApiResponse implements Serializable {
+ private static final long serialVersionUID = 1L;
+ /**
+ * 编码
+ */
+ private int code = ApiErrorCode.FAIL.getCode();
+ /**
+ * 消息
+ */
+ private String msg;
+ /**
+ * 数据
+ */
+ private T data;
+
+
+ public ApiResponse() {
+ }
+
+
+ public static ApiResponse success(T data) {
+ ApiResponse apiResponse = new ApiResponse<>();
+ apiResponse.setCode(0);
+ apiResponse.setMsg("success");
+ apiResponse.setData(data);
+ return apiResponse;
+ }
+
+ public static ApiResponse fail(ApiErrorCode apiErrorCode) {
+ ApiResponse apiResponse = new ApiResponse<>();
+ apiResponse.setCode(apiErrorCode.getCode());
+ apiResponse.setMsg(apiErrorCode.getMsg());
+ return apiResponse;
+ }
+
+ public static ApiResponse fail(ApiErrorCode apiErrorCode, T data) {
+ ApiResponse apiResponse = new ApiResponse<>();
+ apiResponse.setCode(apiErrorCode.getCode());
+ apiResponse.setMsg(apiErrorCode.getMsg());
+ apiResponse.setData(data);
+ return apiResponse;
+ }
+
+ public static ApiResponse fail(int code, String msg) {
+ ApiResponse apiResponse = new ApiResponse<>();
+ apiResponse.setCode(code);
+ apiResponse.setMsg(msg);
+ return apiResponse;
+ }
+
+
+ public int getCode() {
+ return code;
+ }
+
+ public void setCode(int code) {
+ this.code = code;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+ public void setMsg(String msg) {
+ this.msg = msg;
+ }
+
+ public T getData() {
+ return data;
+ }
+
+ public void setData(T data) {
+ this.data = data;
+ }
+
+ @Override
+ public String toString() {
+ return "ApiResponse{" +
+ "code=" + code +
+ ", msg='" + msg + '\'' +
+ ", data=" + data +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ApiResponse> that = (ApiResponse>) o;
+ return code == that.code &&
+ Objects.equals(msg, that.msg) &&
+ Objects.equals(data, that.data);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(code, msg, data);
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/common/PageQueryRequest.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/common/PageQueryRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..90de824ab361811dbb5b119a6734d3a59b429592
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/common/PageQueryRequest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.common;
+
+/**
+ * 分页查询
+ *
+ * @author Clive Yuan
+ * @date 2020/12/07
+ */
+public class PageQueryRequest {
+
+ /**
+ * 页码 (最小为1)
+ */
+ private Integer pageNo;
+
+ /**
+ * 每页条数 (最小为1)
+ */
+ private Integer pageSize;
+
+ /**
+ * 实体
+ */
+ private T entity;
+
+ public Integer getPageNo() {
+ return pageNo;
+ }
+
+ public void setPageNo(Integer pageNo) {
+ this.pageNo = pageNo;
+ }
+
+ public Integer getPageSize() {
+ return pageSize;
+ }
+
+ public void setPageSize(Integer pageSize) {
+ this.pageSize = pageSize;
+ }
+
+ public T getEntity() {
+ return entity;
+ }
+
+ public void setEntity(T entity) {
+ this.entity = entity;
+ }
+
+ @Override
+ public String toString() {
+ return "PageQueryRequest{" +
+ "pageNo=" + pageNo +
+ ", pageSize=" + pageSize +
+ ", entity=" + entity +
+ '}';
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/common/Pagination.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/common/Pagination.java
new file mode 100644
index 0000000000000000000000000000000000000000..aee6a5c0440bca2fd736728959ccd68bf481e825
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/common/Pagination.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.common;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 分页返回对象
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public class Pagination implements Serializable {
+
+ public static final int DEFAULT_PAGE_NUM = 1;
+ public static final int DEFAULT_PAGE_SIZE = 20;
+ public static final int MAX_PAGE_SIZE = 200;
+ private static final long serialVersionUID = 1L;
+ /**
+ * 总条数
+ */
+ private int totalCount;
+
+ /**
+ * 数据列表
+ */
+ private List dataList;
+
+ /**
+ * 是否有下一页
+ */
+ private boolean hasNext;
+
+ public Pagination() {
+
+ }
+
+ /**
+ * 构建空分页
+ *
+ * @return 返回空分页
+ */
+ public static Pagination buildEmpty() {
+ return new Pagination(0, new ArrayList<>(), DEFAULT_PAGE_NUM, DEFAULT_PAGE_SIZE);
+ }
+
+ public Pagination(int totalCount, List dataList, int pageNo, int pageSize) {
+ this.totalCount = totalCount;
+ this.dataList = dataList;
+ int pageNum = (totalCount / pageSize) + ((totalCount % pageSize == 0) ? 0 : 1);
+ this.hasNext = pageNo < pageNum;
+ }
+
+ public int getTotalCount() {
+ return totalCount;
+ }
+
+ public void setTotalCount(int totalCount) {
+ this.totalCount = totalCount;
+ }
+
+ public List getDataList() {
+ return dataList;
+ }
+
+ public void setDataList(List dataList) {
+ this.dataList = dataList;
+ }
+
+ public boolean isHasNext() {
+ return hasNext;
+ }
+
+ public void setHasNext(boolean hasNext) {
+ this.hasNext = hasNext;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Pagination> that = (Pagination>) o;
+ return totalCount == that.totalCount &&
+ hasNext == that.hasNext &&
+ Objects.equals(dataList, that.dataList);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(totalCount, dataList, hasNext);
+ }
+
+ @Override
+ public String toString() {
+ return "Pagination{" +
+ "totalCount=" + totalCount +
+ ", dataList=" + dataList +
+ ", hasNext=" + hasNext +
+ '}';
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/AbstractExample.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/AbstractExample.java
new file mode 100644
index 0000000000000000000000000000000000000000..851467e947d9f516732a17a62d246b83aa118ce8
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/AbstractExample.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+import cn.cliveyuan.robin.base.util.RobinStrUtils;
+import cn.cliveyuan.robin.base.util.SqlUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 抽象查询示例
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+@SuppressWarnings({"serial", "unchecked"})
+public abstract class AbstractExample> implements Example {
+
+ /**
+ * 排序语句
+ */
+ private String orderByClause;
+
+ /**
+ * 是否去重
+ */
+ private boolean distinct;
+
+ /**
+ * 条件
+ */
+ private List> oredCriteria;
+
+ /**
+ * offset
+ */
+ private int limitStart;
+
+ /**
+ * limit
+ */
+ private int limitEnd;
+
+ public AbstractExample() {
+ oredCriteria = new ArrayList<>();
+ }
+
+
+ public String getOrderByClause() {
+ return Objects.nonNull(orderByClause) ? orderByClause : "`id` DESC";
+ }
+
+ public boolean isDistinct() {
+ return distinct;
+ }
+
+ public List> getOredCriteria() {
+ return oredCriteria;
+ }
+
+ public int getLimitStart() {
+ return limitStart;
+ }
+
+ public int getLimitEnd() {
+ return limitEnd;
+ }
+
+ @Override
+ public void setDistinct(boolean distinct) {
+ this.distinct = distinct;
+ }
+
+ @Override
+ public void setLimitStart(int limitStart) {
+ this.limitStart = limitStart;
+ }
+
+ @Override
+ public void setLimitEnd(int limitEnd) {
+ this.limitEnd = limitEnd;
+ }
+
+ @Override
+ public Criteria createCriteria() {
+ Criteria criteria = this.createCriteriaInternal();
+ if (oredCriteria.size() == 0) {
+ oredCriteria.add((GeneratedCriteria) criteria);
+ }
+ return criteria;
+ }
+
+ @Override
+ public LambdaCriteria createLambdaCriteria() {
+ LambdaCriteria criteria = this.createLambdaCriteriaInternal();
+ if (oredCriteria.size() == 0) {
+ oredCriteria.add((GeneratedCriteria) criteria);
+ }
+ return criteria;
+ }
+
+ @Override
+ public void or(GeneratedCriteria criteria) {
+ oredCriteria.add(criteria);
+ }
+
+ @Override
+ public Criteria or() {
+ Criteria criteria = this.createCriteriaInternal();
+ oredCriteria.add((GeneratedCriteria) criteria);
+ return criteria;
+ }
+
+ @Override
+ public LambdaCriteria orLambdaCriteria() {
+ LambdaCriteria criteria = this.createLambdaCriteriaInternal();
+ oredCriteria.add((GeneratedCriteria) criteria);
+ return criteria;
+ }
+
+ @Override
+ public void clear() {
+ oredCriteria.clear();
+ orderByClause = null;
+ distinct = false;
+ }
+
+ @Override
+ public GeneratedCriteria getExistCriteria() {
+ if (oredCriteria.size() > 0) {
+ return oredCriteria.get(0);
+ }
+ return this.createCriteria();
+ }
+
+ private Criteria createCriteriaInternal() {
+ return new Criteria<>();
+ }
+
+ private LambdaCriteria createLambdaCriteriaInternal() {
+ return new LambdaCriteria<>();
+ }
+
+ /**
+ * 添加排序
+ *
+ * @param columnName 字段名
+ * @param isAsc 是否正序
+ */
+ public void addOrderBy(String columnName, boolean isAsc) {
+ SqlUtils.checkColumnName(columnName);
+ if (Objects.isNull(this.orderByClause)) {
+ this.orderByClause = RobinStrUtils.EMPTY;
+ }
+ String direction = (isAsc ? SqlKeyword.ASC : SqlKeyword.DESC).getKeyword();
+ String orderBy = String.format(", `%s` %s", columnName, direction);
+ this.orderByClause += orderBy;
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/AbstractQuery.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/AbstractQuery.java
new file mode 100644
index 0000000000000000000000000000000000000000..77d562b1f5ba32435a93870d472baf7adb96967c
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/AbstractQuery.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+import java.util.Objects;
+
+/**
+ * 抽象查询
+ *
+ * @author Clive Yuan
+ * @date 2020/10/30
+ */
+public abstract class AbstractQuery extends ConditionExample implements Query {
+
+ private T entity;
+
+ @Override
+ public void setEntity(T entity) {
+ Objects.requireNonNull(entity);
+ this.entity = entity;
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ entity = null;
+ }
+
+ @Override
+ public T getEntity() {
+ return entity;
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Compare.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Compare.java
new file mode 100644
index 0000000000000000000000000000000000000000..461949d11a8be1aae05102fd837f7cc08fdb9af7
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Compare.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+import java.io.Serializable;
+
+/**
+ * 比较
+ *
+ * @author Clive Yuan
+ * @date 2020/10/28
+ */
+public interface Compare extends Serializable {
+
+ /**
+ * 等于 =
+ *
+ * @param column 字段
+ * @param val 值
+ * @return children
+ */
+ Children eq(R column, Object val);
+
+ /**
+ * 不等于 <>
+ *
+ * @param column 字段
+ * @param val 值
+ * @return children
+ */
+ Children ne(R column, Object val);
+
+ /**
+ * 大于 >
+ *
+ * @param column 字段
+ * @param val 值
+ * @return children
+ */
+ Children gt(R column, Object val);
+
+ /**
+ * 大于等于 >=
+ *
+ * @param column 字段
+ * @param val 值
+ * @return children
+ */
+ Children ge(R column, Object val);
+
+ /**
+ * 小于 <
+ *
+ * @param column 字段
+ * @param val 值
+ * @return children
+ */
+ Children lt(R column, Object val);
+
+ /**
+ * 小于等于 <=
+ *
+ * @param column 字段
+ * @param val 值
+ * @return children
+ */
+ Children le(R column, Object val);
+
+ /**
+ * BETWEEN 值1 AND 值2
+ *
+ * @param column 字段
+ * @param val1 值1
+ * @param val2 值2
+ * @return children
+ */
+ Children between(R column, Object val1, Object val2);
+
+ /**
+ * NOT BETWEEN 值1 AND 值2
+ *
+ * @param column 字段
+ * @param val1 值1
+ * @param val2 值2
+ * @return children
+ */
+ Children notBetween(R column, Object val1, Object val2);
+
+ /**
+ * LIKE '%值%'
+ *
+ * @param column 字段
+ * @param val 值
+ * @return children
+ */
+ Children like(R column, Object val);
+
+ /**
+ * NOT LIKE '%值%'
+ *
+ * @param column 字段
+ * @param val 值
+ * @return children
+ */
+ Children notLike(R column, Object val);
+
+ /**
+ * LIKE '%值'
+ *
+ * @param column 字段
+ * @param val 值
+ * @return children
+ */
+ Children likeLeft(R column, Object val);
+
+ /**
+ * LIKE '值%'
+ *
+ * @param column 字段
+ * @param val 值
+ * @return children
+ */
+ Children likeRight(R column, Object val);
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/ConditionExample.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/ConditionExample.java
new file mode 100644
index 0000000000000000000000000000000000000000..3b752d9f2aaee8280515f080f12aa18f954a1807
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/ConditionExample.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+/**
+ * 条件示例
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public class ConditionExample extends AbstractExample> {
+
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Conditional.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Conditional.java
new file mode 100644
index 0000000000000000000000000000000000000000..2c2654f0761b69e269925eb241f829ebb749ae68
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Conditional.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+/**
+ * 条件构造器
+ *
+ * @author Clive Yuan
+ * @date 2020/10/30
+ */
+public interface Conditional {
+
+ /**
+ * 创建条件
+ *
+ *
+ * - 如果条件列表为空则默认放入
+ * - 否则不放入
+ *
+ *
+ * @return
+ */
+ Criteria createCriteria();
+
+ /**
+ * 创建Lambda条件
+ *
+ *
+ * - 如果条件列表为空则默认放入
+ * - 否则不放入
+ *
+ *
+ * @return
+ */
+ LambdaCriteria createLambdaCriteria();
+
+ /**
+ * 设置或者条件
+ *
+ * @param criteria 条件
+ */
+ void or(GeneratedCriteria criteria);
+
+ /**
+ * 创建或者条件
+ *
+ * 默认放入条件列表
+ *
+ * @return
+ */
+ Criteria or();
+
+ /**
+ * 创建Lambda或者条件
+ *
+ *
+ *
+ * @return
+ */
+ LambdaCriteria orLambdaCriteria();
+
+ /**
+ * 获取首个条件
+ *
+ *
+ * - 如果条件存在则获取
+ * - 否则创建Criteria并放入
+ *
+ *
+ * @return 需判断类型是LambdaCriteria还是Criteria
+ */
+ GeneratedCriteria getExistCriteria();
+
+ /**
+ * 清除所有
+ */
+ void clear();
+
+ /**
+ * 设置是否去重
+ *
+ * @param distinct 是否去重
+ */
+ void setDistinct(boolean distinct);
+
+ /**
+ * offset
+ *
+ * @param limitStart offset
+ */
+ void setLimitStart(int limitStart);
+
+ /**
+ * limit
+ *
+ * @param limitEnd limit
+ */
+ void setLimitEnd(int limitEnd);
+
+ /**
+ * 添加排序
+ *
+ * @param columnName 字段名 (如果字段名不合法将抛出异常)
+ * @param isAsc 是否正序
+ */
+ void addOrderBy(String columnName, boolean isAsc);
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Criteria.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Criteria.java
new file mode 100644
index 0000000000000000000000000000000000000000..4de0e52bbdf532c5422feb3ac29e8bf26758201d
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Criteria.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+/**
+ * 条件集合
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public class Criteria extends GeneratedCriteria> {
+
+ @Override
+ protected String columnToString(String column) {
+ return column;
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Criterion.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Criterion.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f597dc7a3d4ee4f2e60c48423aa22a14bb04b97
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Criterion.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 条件明细
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public class Criterion {
+
+ private String condition;
+
+ private Object value;
+
+ private Object secondValue;
+
+ private boolean noValue;
+
+ private boolean singleValue;
+
+ private boolean likeValue;
+
+ private boolean betweenValue;
+
+ private boolean listValue;
+
+ private String typeHandler;
+
+ private SqlLike sqlLike;
+
+ public String getCondition() {
+ return condition;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public Object getSecondValue() {
+ return secondValue;
+ }
+
+ public boolean isNoValue() {
+ return noValue;
+ }
+
+ public boolean isSingleValue() {
+ return singleValue;
+ }
+
+ public boolean isBetweenValue() {
+ return betweenValue;
+ }
+
+ public boolean isListValue() {
+ return listValue;
+ }
+
+ public String getTypeHandler() {
+ return typeHandler;
+ }
+
+ public boolean isLikeValue() {
+ return likeValue;
+ }
+
+ public SqlLike getSqlLike() {
+ return sqlLike;
+ }
+
+ protected Criterion(String condition) {
+ super();
+ this.condition = condition;
+ this.typeHandler = null;
+ this.noValue = true;
+ }
+
+ protected Criterion(String condition, Object value, String typeHandler) {
+ this(condition, value, typeHandler, (SqlLike) null);
+ }
+
+ protected Criterion(String condition, Object value, String typeHandler, SqlLike sqlLike) {
+ super();
+ this.condition = condition;
+ this.value = value;
+ this.typeHandler = typeHandler;
+ if (Objects.nonNull(sqlLike)) {
+ this.likeValue = true;
+ this.sqlLike = sqlLike;
+ } else {
+ if (value instanceof List>) {
+ this.listValue = true;
+ } else {
+ this.singleValue = true;
+ }
+ }
+ }
+
+ protected Criterion(String condition, Object value) {
+ this(condition, value, (String) null);
+ }
+
+ protected Criterion(String condition, Object value, SqlLike sqlLike) {
+ this(condition, value, null, sqlLike);
+ }
+
+ protected Criterion(String condition, Object value, Object secondValue, String typeHandler) {
+ super();
+ this.condition = condition;
+ this.value = value;
+ this.secondValue = secondValue;
+ this.typeHandler = typeHandler;
+ this.betweenValue = true;
+ }
+
+ protected Criterion(String condition, Object value, Object secondValue) {
+ this(condition, value, secondValue, null);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Criterion criterion = (Criterion) o;
+ return noValue == criterion.noValue &&
+ singleValue == criterion.singleValue &&
+ likeValue == criterion.likeValue &&
+ betweenValue == criterion.betweenValue &&
+ listValue == criterion.listValue &&
+ Objects.equals(condition, criterion.condition) &&
+ Objects.equals(value, criterion.value) &&
+ Objects.equals(secondValue, criterion.secondValue) &&
+ Objects.equals(typeHandler, criterion.typeHandler) &&
+ sqlLike == criterion.sqlLike;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(condition, value, secondValue, noValue, singleValue, likeValue, betweenValue, listValue, typeHandler, sqlLike);
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Example.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Example.java
new file mode 100644
index 0000000000000000000000000000000000000000..ca11beba8c6292f4103cdd3899241f1a5cabe587
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Example.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+/**
+ * 查询示例
+ *
+ * 用于Mapper层面
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public interface Example extends Conditional {
+
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Func.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Func.java
new file mode 100644
index 0000000000000000000000000000000000000000..e87789783c5d6433fbc063802b2d0bbcd6163d47
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Func.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * 函数
+ *
+ * @author Clive Yuan
+ * @date 2020/10/28
+ */
+public interface Func extends Serializable {
+
+
+ /**
+ * 字段 IS NULL
+ * 例: isNull("name")
+ *
+ * @param column 字段
+ * @return children
+ */
+ Children isNull(R column);
+
+ /**
+ * 字段 IS NOT NULL
+ * 例: isNotNull("name")
+ *
+ * @param column 字段
+ * @return children
+ */
+ Children isNotNull(R column);
+
+ /**
+ * 字段 IN (value.get(0), value.get(1), ...)
+ * 例: in("id", Arrays.asList(1, 2, 3, 4, 5))
+ *
+ * 如果集合为 empty 则不会进行 sql 拼接
+ *
+ * @param column 字段
+ * @param coll 数据集合
+ * @return children
+ */
+ Children in(R column, Collection> coll);
+
+ /**
+ * 字段 IN (v0, v1, ...)
+ * 例: in("id", 1, 2, 3, 4, 5)
+ *
+ * 如果动态数组为 empty 则不会进行 sql 拼接
+ *
+ * @param column 字段
+ * @param values 数据数组
+ * @return children
+ */
+ default Children in(R column, Object... values) {
+ return in(column, Arrays.stream(Optional.ofNullable(values).orElseGet(() -> new Object[]{}))
+ .collect(toList()));
+ }
+
+ /**
+ * 字段 NOT IN (value.get(0), value.get(1), ...)
+ * 例: notIn("id", Arrays.asList(1, 2, 3, 4, 5))
+ *
+ * @param column 字段
+ * @param coll 数据集合
+ * @return children
+ */
+ Children notIn(R column, Collection> coll);
+
+ /**
+ * 字段 NOT IN (v0, v1, ...)
+ * 例: notIn("id", 1, 2, 3, 4, 5)
+ *
+ * @param column 字段
+ * @param values 数据数组
+ * @return children
+ */
+ default Children notIn(R column, Object... values) {
+ return notIn(column, Arrays.stream(Optional.ofNullable(values).orElseGet(() -> new Object[]{}))
+ .collect(toList()));
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/GeneratedCriteria.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/GeneratedCriteria.java
new file mode 100644
index 0000000000000000000000000000000000000000..71242599e336db81dd36c70f22a0c0237b79079b
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/GeneratedCriteria.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+import cn.cliveyuan.robin.base.util.SqlUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 生成的条件集合
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+@SuppressWarnings({"serial", "unchecked"})
+public abstract class GeneratedCriteria implements Compare, Func {
+
+ /**
+ * 占位符
+ */
+ protected final Children typedThis = (Children) this;
+
+ private List criteria;
+
+ public GeneratedCriteria() {
+ this.criteria = new ArrayList<>();
+ }
+
+ public boolean isValid() {
+ return criteria.size() > 0;
+ }
+
+ public List getCriteria() {
+ return criteria;
+ }
+
+ public void setCriteria(List criteria) {
+ this.criteria = criteria;
+ }
+
+ protected void addCriterion(String condition) {
+ if (condition == null) {
+ throw new RuntimeException("Value for condition cannot be null");
+ }
+ criteria.add(new Criterion(condition));
+ }
+
+ protected void addCriterion(R column, SqlKeyword sqlKeyword) {
+ this.addCriterion(this.contactCondition(this.columnToString(column), sqlKeyword));
+ }
+
+ protected void addCriterion(R column, SqlKeyword sqlKeyword, Object value) {
+ String columnName = this.columnToString(column);
+ this.addCriterion(this.contactCondition(columnName, sqlKeyword), value, columnName);
+ }
+
+ protected void addCriterion(R column, SqlKeyword sqlKeyword, Object value1, Object value2) {
+ String columnName = this.columnToString(column);
+ this.addCriterion(this.contactCondition(columnName, sqlKeyword), value1, value2, columnName);
+ }
+
+ protected void addCriterion(String condition, Object value, String property) {
+ if (value == null) {
+ throw new RuntimeException("Value for " + property + " cannot be null");
+ }
+ criteria.removeIf(x -> Objects.equals(x.getCondition(), condition));
+ criteria.add(new Criterion(condition, value));
+ }
+
+ protected void addLikeCriterion(SqlKeyword sqlKeyword, SqlLike sqlLike, Object value, R column) {
+ String columnName = this.columnToString(column);
+ if (value == null) {
+ throw new RuntimeException("Value for " + columnName + " cannot be null");
+ }
+ criteria.add(new Criterion(this.contactCondition(columnName, sqlKeyword), value, sqlLike));
+ }
+
+ protected void addCriterion(String condition, Object value1, Object value2, String property) {
+ if (value1 == null || value2 == null) {
+ throw new RuntimeException("Between values for " + property + " cannot be null");
+ }
+ criteria.add(new Criterion(condition, value1, value2));
+ }
+
+ public String contactCondition(String columnName, SqlKeyword sqlKeyword) {
+ SqlUtils.checkColumnName(columnName);
+ return String.format("`%s` %s", columnName, sqlKeyword.getKeyword());
+ }
+
+ protected abstract String columnToString(R column);
+
+ @Override
+ public Children eq(R column, Object val) {
+ addCriterion(column, SqlKeyword.EQ, val);
+ return typedThis;
+ }
+
+ @Override
+ public Children ne(R column, Object val) {
+ addCriterion(column, SqlKeyword.NE, val);
+ return typedThis;
+ }
+
+ @Override
+ public Children gt(R column, Object val) {
+ addCriterion(column, SqlKeyword.GT, val);
+ return typedThis;
+ }
+
+ @Override
+ public Children ge(R column, Object val) {
+ addCriterion(column, SqlKeyword.GE, val);
+ return typedThis;
+ }
+
+ @Override
+ public Children lt(R column, Object val) {
+ addCriterion(column, SqlKeyword.LT, val);
+ return typedThis;
+ }
+
+ @Override
+ public Children le(R column, Object val) {
+ addCriterion(column, SqlKeyword.LE, val);
+ return typedThis;
+ }
+
+ @Override
+ public Children between(R column, Object val1, Object val2) {
+ addCriterion(column, SqlKeyword.BETWEEN, val1, val2);
+ return typedThis;
+ }
+
+ @Override
+ public Children notBetween(R column, Object val1, Object val2) {
+ addCriterion(column, SqlKeyword.NOT_BETWEEN, val1, val2);
+ return typedThis;
+ }
+
+ @Override
+ public Children like(R column, Object val) {
+ addLikeCriterion(SqlKeyword.LIKE, SqlLike.DEFAULT, val, column);
+ return typedThis;
+ }
+
+ @Override
+ public Children notLike(R column, Object val) {
+ addLikeCriterion(SqlKeyword.NOT_LIKE, SqlLike.DEFAULT, val, column);
+ return typedThis;
+ }
+
+ @Override
+ public Children likeLeft(R column, Object val) {
+ addLikeCriterion(SqlKeyword.LIKE, SqlLike.LEFT, val, column);
+ return typedThis;
+ }
+
+ @Override
+ public Children likeRight(R column, Object val) {
+ addLikeCriterion(SqlKeyword.LIKE, SqlLike.RIGHT, val, column);
+ return typedThis;
+ }
+
+ @Override
+ public Children isNull(R column) {
+ addCriterion(column, SqlKeyword.IS_NULL);
+ return typedThis;
+ }
+
+ @Override
+ public Children isNotNull(R column) {
+ addCriterion(column, SqlKeyword.IS_NOT_NULL);
+ return typedThis;
+ }
+
+ @Override
+ public Children in(R column, Collection> coll) {
+ addCriterion(column, SqlKeyword.IN, coll);
+ return typedThis;
+ }
+
+ @Override
+ public Children notIn(R column, Collection> coll) {
+ addCriterion(column, SqlKeyword.NOT_IN, coll);
+ return typedThis;
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/LambdaCriteria.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/LambdaCriteria.java
new file mode 100644
index 0000000000000000000000000000000000000000..62df215db262c1a8788bc1aad8703074f52b110e
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/LambdaCriteria.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+import cn.cliveyuan.robin.base.support.SFunction;
+import cn.cliveyuan.robin.base.util.LambdaUtils;
+
+/**
+ * Lambda条件
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public class LambdaCriteria extends GeneratedCriteria, LambdaCriteria> {
+ @Override
+ protected String columnToString(SFunction column) {
+ return LambdaUtils.getFieldName(column);
+ }
+
+ public LambdaCriteria eq(String column, String val) {
+ this.addCriterion(this.contactCondition(column, SqlKeyword.EQ), val, column);
+ return this;
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/PageQueryExample.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/PageQueryExample.java
new file mode 100644
index 0000000000000000000000000000000000000000..ce60037fc2842530aea01773e552111c6b45d5f3
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/PageQueryExample.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+/**
+ * 分页查询示例
+ *
+ * @author Clive Yuan
+ * @date 2020/10/30
+ */
+public class PageQueryExample extends QueryExample {
+ /**
+ * 默认页码
+ */
+ public static final int DEFAULT_PAGE_NO = 1;
+
+ /**
+ * 默认分页大小
+ */
+ public static final int DEFAULT_PAGE_SIZE = 10;
+
+ /**
+ * 页号 (默认1)
+ */
+ private Integer pageNo = DEFAULT_PAGE_NO;
+
+ /**
+ * 每页条数 (默认10)
+ */
+ private Integer pageSize = DEFAULT_PAGE_SIZE;
+
+ public Integer getPageNo() {
+ return pageNo;
+ }
+
+ public void setPageNo(Integer pageNo) {
+ this.pageNo = pageNo;
+ }
+
+ public Integer getPageSize() {
+ return pageSize;
+ }
+
+ public void setPageSize(Integer pageSize) {
+ this.pageSize = pageSize;
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Query.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Query.java
new file mode 100644
index 0000000000000000000000000000000000000000..25417306db83a66254277447c9cd0145cb977e84
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/Query.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+/**
+ * 查询
+ *
+ * 用于Service层面
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public interface Query extends Conditional {
+
+ /**
+ * 设置实体
+ *
+ * @param entity 实体
+ */
+ void setEntity(T entity);
+
+ /**
+ * 获取实体
+ */
+ T getEntity();
+
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/QueryExample.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/QueryExample.java
new file mode 100644
index 0000000000000000000000000000000000000000..c6192fa818b885b13cdb576ea87a7cfdc1afbf55
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/QueryExample.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+/**
+ * 查询示例
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public class QueryExample extends AbstractQuery {
+
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/SqlKeyword.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/SqlKeyword.java
new file mode 100644
index 0000000000000000000000000000000000000000..8876e69b2345f3c74736ce281b2ffa1a33b456c3
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/SqlKeyword.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+
+/**
+ * SQL 保留关键字枚举
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public enum SqlKeyword {
+ IN("IN"),
+ NOT_IN("NOT IN"),
+ LIKE("LIKE"),
+ NOT_LIKE("NOT LIKE"),
+ EQ("="),
+ NE("<>"),
+ GT(">"),
+ GE(">="),
+ LT("<"),
+ LE("<="),
+ IS_NULL("IS NULL"),
+ IS_NOT_NULL("IS NOT NULL"),
+ BETWEEN("BETWEEN"),
+ NOT_BETWEEN("NOT BETWEEN"),
+ ASC("ASC"),
+ DESC("DESC");
+
+ private final String keyword;
+
+ SqlKeyword(String keyword) {
+ this.keyword = keyword;
+ }
+
+ public String getKeyword() {
+ return keyword;
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/SqlLike.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/SqlLike.java
new file mode 100644
index 0000000000000000000000000000000000000000..75aa669fbfb2b24016f857661169e8c67ee7fb7c
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/condition/SqlLike.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.condition;
+
+/**
+ * SQL like 枚举
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public enum SqlLike {
+ /**
+ * %值
+ */
+ LEFT,
+ /**
+ * 值%
+ */
+ RIGHT,
+ /**
+ * %值%
+ */
+ DEFAULT
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/provider/SqlProvider.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/provider/SqlProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..58f06e33889236805a38cc6c9f8d11eb8771e503
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/provider/SqlProvider.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.provider;
+
+import cn.cliveyuan.robin.base.support.SqlProviderSupport;
+import org.apache.ibatis.builder.annotation.ProviderContext;
+import org.apache.ibatis.builder.annotation.ProviderMethodResolver;
+
+import java.util.Map;
+
+/**
+ * SQL提供者
+ *
+ * @author Clive Yuan
+ * @date 2020/10/28
+ */
+public class SqlProvider implements ProviderMethodResolver {
+
+ public String insert(ProviderContext providerContext, T entity) {
+ SqlProviderSupport.checkEntity(entity);
+ return SqlProviderSupport.getSqlScript("insert", providerContext);
+ }
+
+ public String insertAll(ProviderContext providerContext, T entity) {
+ SqlProviderSupport.checkEntity(entity);
+ return SqlProviderSupport.getSqlScript("insertAll", providerContext);
+ }
+
+ public String batchInsert(ProviderContext providerContext, Map paramMap) {
+ SqlProviderSupport.checkEntityList(paramMap);
+ return SqlProviderSupport.getSqlScript("batchInsert", providerContext);
+ }
+
+ public String delete(ProviderContext providerContext, Map paramMap) {
+ SqlProviderSupport.checkId(paramMap);
+ return SqlProviderSupport.getSqlScript("delete", providerContext);
+ }
+
+ public String batchDelete(ProviderContext providerContext, Map paramMap) {
+ SqlProviderSupport.checkIds(paramMap);
+ return SqlProviderSupport.getSqlScript("batchDelete", providerContext);
+ }
+
+ public String deleteByExample(ProviderContext providerContext, Map paramMap) {
+ SqlProviderSupport.checkCondition(paramMap);
+ return SqlProviderSupport.getSqlScript("deleteByExample", providerContext);
+ }
+
+ public String update(ProviderContext providerContext, Map paramMap) {
+ SqlProviderSupport.checkEntity(paramMap);
+ return SqlProviderSupport.getSqlScript("update", providerContext);
+ }
+
+ public String updateAll(ProviderContext providerContext, Map paramMap) {
+ SqlProviderSupport.checkEntity(paramMap);
+ return SqlProviderSupport.getSqlScript("updateAll", providerContext);
+ }
+
+ public String updateByExample(ProviderContext providerContext, Map paramMap) {
+ SqlProviderSupport.checkCondition(paramMap);
+ return SqlProviderSupport.getSqlScript("updateByExample", providerContext);
+ }
+
+ public String updateByExampleAll(ProviderContext providerContext, Map paramMap) {
+ SqlProviderSupport.checkCondition(paramMap);
+ return SqlProviderSupport.getSqlScript("updateByExampleAll", providerContext);
+ }
+
+ public String get(ProviderContext providerContext, Map paramMap) {
+ SqlProviderSupport.checkId(paramMap);
+ return SqlProviderSupport.getSqlScript("get", providerContext);
+ }
+
+ public String batchGet(ProviderContext providerContext, Map paramMap) {
+ SqlProviderSupport.checkIds(paramMap);
+ return SqlProviderSupport.getSqlScript("batchGet", providerContext);
+ }
+
+ public String getByExample(ProviderContext providerContext) {
+ return SqlProviderSupport.getSqlScript("getByExample", providerContext);
+ }
+
+ public String list(ProviderContext providerContext) {
+ return SqlProviderSupport.getSqlScript("list", providerContext);
+ }
+
+ public String count(ProviderContext providerContext) {
+ return SqlProviderSupport.getSqlScript("count", providerContext);
+ }
+
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/support/ReflectEntity.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/support/ReflectEntity.java
new file mode 100644
index 0000000000000000000000000000000000000000..c434448f0ca771bc3e00fdfd49bd625e2978a51a
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/support/ReflectEntity.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.support;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 反射实体对象
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public class ReflectEntity implements Serializable {
+
+ private String simpleName;
+ private String tableName;
+ private List fields;
+ private ReflectEntityHelper reflectEntityHelper;
+
+ public String getTableName() {
+ return tableName;
+ }
+
+ public void setTableName(String tableName) {
+ this.tableName = tableName;
+ }
+
+ public List getFields() {
+ return fields;
+ }
+
+ public void setFields(List fields) {
+ this.fields = fields;
+ }
+
+ public String getSimpleName() {
+ return simpleName;
+ }
+
+ public void setSimpleName(String simpleName) {
+ this.simpleName = simpleName;
+ }
+
+ public ReflectEntityHelper getReflectEntityHelper() {
+ return reflectEntityHelper;
+ }
+
+ public void setReflectEntityHelper(ReflectEntityHelper reflectEntityHelper) {
+ this.reflectEntityHelper = reflectEntityHelper;
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/support/ReflectEntityHelper.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/support/ReflectEntityHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..a384d14998ba65bc1008f09cc698ae8761618418
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/support/ReflectEntityHelper.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.support;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * 反射实体帮助类
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public class ReflectEntityHelper {
+
+ private final Map variableMap = new ConcurrentHashMap<>();
+ private final ReflectEntity reflectEntity;
+
+ private ReflectEntityHelper(ReflectEntity reflectEntity) {
+ this.reflectEntity = reflectEntity;
+ }
+
+ public static ReflectEntityHelper build(ReflectEntity reflectEntity) {
+ Objects.requireNonNull(reflectEntity);
+ ReflectEntityHelper reflectEntityHelper = new ReflectEntityHelper(reflectEntity);
+ reflectEntityHelper.initVariableMap();
+ return reflectEntityHelper;
+ }
+
+ private void initVariableMap() {
+ variableMap.put("tableName", reflectEntity.getTableName());
+ variableMap.put("allFieldsString", allFieldsString());
+ variableMap.put("fieldsString", fieldsString());
+ variableMap.put("fieldsParamString", fieldsParamString());
+ variableMap.put("itemFieldsParamString", itemFieldsParamString());
+ variableMap.put("conditionalFieldsString", conditionalFieldsString());
+ variableMap.put("conditionalFieldsParamString", conditionalFieldsParamString());
+ variableMap.put("conditionalSetFieldsString", conditionalSetFieldsString());
+ variableMap.put("setFieldsString", setFieldsString());
+ }
+
+ public Map getVariableMap() {
+ return variableMap;
+ }
+
+ /**
+ * 获取非保存忽略字段
+ *
+ * @return
+ */
+ public List getNotIgnoreSavingFields() {
+ return reflectEntity.getFields().stream().filter(x -> !x.getIgnoreSaving()).collect(Collectors.toList());
+ }
+
+ /**
+ * 获取全部字段, 以逗号分隔
+ *
+ * @return
+ */
+ private String allFieldsString() {
+ return reflectEntity.getFields().stream().map(x -> String.format("`%s`", x.getFieldName()))
+ .collect(Collectors.joining(","));
+ }
+
+ private String fieldsString() {
+ return this.getNotIgnoreSavingFields().stream().map(x -> String.format("`%s`", x.getFieldName()))
+ .collect(Collectors.joining(","));
+ }
+
+ private String fieldsParamString() {
+ return this.getNotIgnoreSavingFields().stream().map(x -> String.format("#{%s}", x.getName()))
+ .collect(Collectors.joining(","));
+ }
+
+ private String itemFieldsParamString() {
+ return this.getNotIgnoreSavingFields().stream().map(x -> String.format("#{item.%s}", x.getName()))
+ .collect(Collectors.joining(","));
+ }
+
+ private String conditionalFieldsString() {
+ return this.getNotIgnoreSavingFields().stream().map(x -> String.format("`%s`,", x.getName(), x.getFieldName()))
+ .collect(Collectors.joining(""));
+ }
+
+ private String conditionalFieldsParamString() {
+ return this.getNotIgnoreSavingFields().stream().map(x -> String.format("#{%s},", x.getName(), x.getName()))
+ .collect(Collectors.joining(""));
+ }
+
+ private String conditionalSetFieldsString() {
+ return this.getNotIgnoreSavingFields().stream().map(x -> String.format("`%s` = #{entity.%s},", x.getName(), x.getFieldName(), x.getName()))
+ .collect(Collectors.joining(""));
+ }
+
+ private String setFieldsString() {
+ return this.getNotIgnoreSavingFields().stream().map(x -> String.format("`%s` = #{entity.%s}", x.getFieldName(), x.getName()))
+ .collect(Collectors.joining(","));
+ }
+
+
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/support/ReflectField.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/support/ReflectField.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a8398c1aee70fe656fbed96bd0c5574f365e63d
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/support/ReflectField.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.support;
+
+import java.io.Serializable;
+
+/**
+ * 反射字段
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public class ReflectField implements Serializable {
+
+ private String name;
+ private String fieldName;
+ private Boolean isPrimaryKey;
+ private Boolean ignoreSaving;
+
+ public String getFieldName() {
+ return fieldName;
+ }
+
+ public void setFieldName(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ public Boolean getPrimaryKey() {
+ return isPrimaryKey;
+ }
+
+ public void setPrimaryKey(Boolean primaryKey) {
+ isPrimaryKey = primaryKey;
+ }
+
+ public Boolean getIgnoreSaving() {
+ return ignoreSaving;
+ }
+
+ public void setIgnoreSaving(Boolean ignoreSaving) {
+ this.ignoreSaving = ignoreSaving;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/support/SFunction.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/support/SFunction.java
new file mode 100644
index 0000000000000000000000000000000000000000..470a3a1253baa69d02dce47ba2b4b887307ad713
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/support/SFunction.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.support;
+
+import java.io.Serializable;
+import java.util.function.Function;
+
+/**
+ * 支持序列化的 Function
+ */
+@FunctionalInterface
+public interface SFunction extends Function, Serializable {
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/support/SqlProviderSupport.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/support/SqlProviderSupport.java
new file mode 100644
index 0000000000000000000000000000000000000000..4705079d6b8295a59b4b36c85ef81e2c127e0cf3
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/support/SqlProviderSupport.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.support;
+
+import cn.cliveyuan.robin.base.condition.ConditionExample;
+import cn.cliveyuan.robin.base.util.AssertUtils;
+import cn.cliveyuan.robin.base.util.ReflectUtils;
+import cn.cliveyuan.robin.base.util.SqlUtils;
+import org.apache.ibatis.builder.annotation.ProviderContext;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+/**
+ * SQL提供者支持类
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+@SuppressWarnings("unchecked")
+public class SqlProviderSupport {
+ private static final Map SQL_CACHE = new ConcurrentHashMap<>();
+
+ public static String getSqlScript(String method, ProviderContext providerContext) {
+ return getSqlScript(method, getEntityClassFromProviderContext(providerContext));
+ }
+
+ public static String getSqlScript(String method, Class> entityClass) {
+ return SqlProviderSupport.getSqlScript(method, entityClass, () -> {
+ ReflectEntity reflectEntity = ReflectUtils.resolveEntity(entityClass);
+ ReflectEntityHelper reflectEntityHelper = reflectEntity.getReflectEntityHelper();
+ Map paramMap = reflectEntityHelper.getVariableMap();
+ String sqlSegment = SqlUtils.getSqlSegmentMap().get(method);
+ Objects.requireNonNull(sqlSegment, String.format("sqlSegment named '%s' is not exist", method));
+ return SqlUtils.resolveMapperScript(sqlSegment, paramMap);
+ });
+ }
+
+ public static String getSqlScript(String method, Class> clazz, Supplier supplier) {
+ ReflectEntity reflectEntity = ReflectUtils.resolveEntity(clazz);
+ String tableName = reflectEntity.getTableName();
+ String key = tableName.concat(".").concat(method);
+ return Optional.ofNullable(SQL_CACHE.get(key)).orElseGet(() -> {
+ String sqlScript = supplier.get();
+ SQL_CACHE.put(key, sqlScript);
+ return sqlScript;
+ });
+ }
+
+ public static Class> getEntityClassFromEntity(Map paramMap) {
+ T entity = (T) paramMap.get("entity");
+ AssertUtils.notNull(entity, "entity cant be null");
+ return entity.getClass();
+ }
+
+ public static Class> getEntityClassFromProviderContext(ProviderContext providerContext) {
+ return ReflectUtils.getClassGenericType(true, providerContext.getMapperType(), 0);
+ }
+
+ public static Class> getEntityClassFromMap(Map paramMap) {
+ Class> entityClass = (Class>) paramMap.get("entityClass");
+ AssertUtils.notNull(entityClass, "entityClass cant be null");
+ return entityClass;
+ }
+
+ public static Class> getEntityClassFromList(Map paramMap) {
+ List list = (List) paramMap.get("list");
+ AssertUtils.isTrue(list.size() > 0, "list is empty");
+ T entity = list.get(0);
+ AssertUtils.notNull(entity, "entity in list cant be null");
+ return entity.getClass();
+ }
+
+ /**
+ * 检查条件, 避免误操作
+ *
+ * @param paramMap 参数map
+ */
+ public static void checkCondition(Map paramMap) {
+ ConditionExample example = (ConditionExample) paramMap.get("example");
+ AssertUtils.notNull(example, "example cant be null");
+ AssertUtils.isTrue(example.getOredCriteria().size() > 0, "Condition can't be empty");
+ }
+
+ /**
+ * 检查id集合
+ *
+ * @param paramMap 参数map
+ */
+ public static void checkIds(Map paramMap) {
+ List ids = (List) paramMap.get("ids");
+ AssertUtils.isTrue(Objects.nonNull(ids) && ids.size() > 0, "ids can't be empty");
+ }
+
+ /**
+ * 检查实体集合
+ *
+ * @param paramMap 参数map
+ */
+ public static void checkEntityList(Map paramMap) {
+ List list = (List) paramMap.get("list");
+ AssertUtils.isTrue(Objects.nonNull(list) && list.size() > 0, "list can't be empty");
+ }
+
+ public static void checkEntity(Map paramMap) {
+ checkEntity((T) paramMap.get("entity"));
+ }
+
+ public static void checkEntity(T entity) {
+ AssertUtils.notNull(entity, "entity can't be empty");
+ }
+
+ public static void checkId(Map paramMap) {
+ Long id = (Long) paramMap.get("id");
+ AssertUtils.notNull(id, "id can't be empty");
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/util/AssertUtils.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/util/AssertUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..bc640d880e5b20c5f021e78ace1d4806a8cb0cdf
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/util/AssertUtils.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.util;
+
+/**
+ * @author Clive Yuan
+ * @date 2020/12/23
+ */
+public class AssertUtils {
+
+ public static void isTrue(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ public static void notNull(Object object, String message) {
+ if (object == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/util/BeanCopyUtils.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/util/BeanCopyUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..77d988f0c01446da4303fb7836ba52097ff802e3
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/util/BeanCopyUtils.java
@@ -0,0 +1,78 @@
+package cn.cliveyuan.robin.base.util;
+
+import cn.cliveyuan.robin.base.common.Pagination;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * @author Clive Yuan
+ * @date 2020/12/24
+ */
+public class BeanCopyUtils {
+
+ private static final Logger logger = LoggerFactory.getLogger(BeanCopyUtils.class);
+
+ /**
+ * 对象浅拷贝
+ *
+ * @param source 源对象
+ * @param targetClass 目标对象类
+ * @param 类
+ * @return
+ */
+ public static T copy(Object source, Class targetClass) {
+ AssertUtils.notNull(source, "source is required");
+ AssertUtils.notNull(targetClass, "targetClass is required");
+ try {
+ T targetObject = targetClass.getDeclaredConstructor().newInstance();
+ BeanUtils.copyProperties(source, targetObject);
+ return targetObject;
+ } catch (Exception e) {
+ logger.error("copy error", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * @param sourceList 源对象列表
+ * @param targetClass 目标对象类
+ * @param 源类
+ * @param 目标类
+ * @return
+ */
+ public static List copyList(List sourceList, Class targetClass) {
+ AssertUtils.notNull(sourceList, "sourceList is required");
+ AssertUtils.notNull(targetClass, "targetClass is required");
+ if (sourceList.size() == 0) {
+ return new ArrayList<>();
+ }
+ return sourceList.stream().map(x -> copy(x, targetClass)).collect(Collectors.toList());
+ }
+
+ /**
+ * @param sourcePagination 源对象分页
+ * @param targetClass 目标对象类
+ * @param 源类
+ * @param 目标类
+ * @return
+ */
+ public static Pagination copyPagination(Pagination sourcePagination, Class targetClass) {
+ AssertUtils.notNull(sourcePagination, "sourcePagination is required");
+ AssertUtils.notNull(targetClass, "targetClass is required");
+ if (Objects.isNull(sourcePagination.getDataList())) {
+ return Pagination.buildEmpty();
+ }
+ List list = copyList(sourcePagination.getDataList(), targetClass);
+ Pagination pagination = new Pagination<>();
+ pagination.setDataList(list);
+ pagination.setTotalCount(sourcePagination.getTotalCount());
+ pagination.setHasNext(sourcePagination.isHasNext());
+ return pagination;
+ }
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/util/LambdaUtils.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/util/LambdaUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0fe97034768e1c2256effdc2731b8184d04f80f
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/util/LambdaUtils.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.util;
+
+import cn.cliveyuan.robin.base.support.SFunction;
+
+import java.lang.invoke.SerializedLambda;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Lambda工具
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public class LambdaUtils {
+
+ /**
+ * SerializedLambda 反序列化缓存
+ */
+ private static final Map FUNC_CACHE = new ConcurrentHashMap<>();
+
+ public static String getFieldName(SFunction func) {
+ SerializedLambda serializedLambda = resolveSerializedLambdaCache(func);
+ String getter = serializedLambda.getImplMethodName();
+ return resolveFieldName(getter);
+ }
+
+ private static SerializedLambda resolveSerializedLambdaCache(SFunction func) {
+ Class> clazz = func.getClass();
+ String name = clazz.getName();
+ return Optional.ofNullable(FUNC_CACHE.get(name))
+ .orElseGet(() -> {
+ SerializedLambda lambda = resolveSerializedLambda(func);
+ FUNC_CACHE.put(name, lambda);
+ return lambda;
+ });
+ }
+
+ private static SerializedLambda resolveSerializedLambda(SFunction func) {
+ try {
+ // 通过获取对象方法,判断是否存在该方法
+ Method method = func.getClass().getDeclaredMethod("writeReplace");
+ method.setAccessible(Boolean.TRUE);
+ // 利用jdk的SerializedLambda 解析方法引用
+ return (SerializedLambda) method.invoke(func);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static String resolveFieldName(String getMethodName) {
+ if (getMethodName.startsWith("get")) {
+ getMethodName = getMethodName.substring(3);
+ } else if (getMethodName.startsWith("is")) {
+ getMethodName = getMethodName.substring(2);
+ }
+ // 小写第一个字母
+ return SqlUtils.firstToLowerCase(getMethodName);
+ }
+
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/util/ReflectUtils.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/util/ReflectUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..d9ff4aa8fa98d8cbf8401c4de0c1a7ee15e56479
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/util/ReflectUtils.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.util;
+
+import cn.cliveyuan.robin.base.annotation.TableField;
+import cn.cliveyuan.robin.base.annotation.TableId;
+import cn.cliveyuan.robin.base.annotation.TableName;
+import cn.cliveyuan.robin.base.support.ReflectEntity;
+import cn.cliveyuan.robin.base.support.ReflectEntityHelper;
+import cn.cliveyuan.robin.base.support.ReflectField;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 反射工具
+ *
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public class ReflectUtils {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ReflectUtils.class);
+ private static final Map REFLECT_ENTITY_CACHE = new ConcurrentHashMap<>();
+ private static final String GET_METHOD_PREFIX = "get";
+ private static final String PRIMARY_KEY_NAME = "id";
+
+ public static Class> getSuperClassGenericType(final Class> clazz, final int index) {
+ return getClassGenericType(false, clazz, index);
+ }
+
+ public static Class> getClassGenericType(final boolean isInterface, final Class> clazz, final int index) {
+ Type genType = clazz.getGenericSuperclass();
+ if (isInterface) {
+ Type[] genericInterfaces = clazz.getGenericInterfaces();
+ if (genericInterfaces.length > 0) {
+ genType = genericInterfaces[0];
+ }
+ }
+ if (!(genType instanceof ParameterizedType)) {
+ LOGGER.warn(String.format("Warn: %s's superclass not ParameterizedType", clazz.getSimpleName()));
+ return Object.class;
+ }
+ Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
+ if (index >= params.length || index < 0) {
+ LOGGER.warn(String.format("Warn: Index: %s, Size of %s's Parameterized Type: %s .", index,
+ clazz.getSimpleName(), params.length));
+ return Object.class;
+ }
+ if (!(params[index] instanceof Class)) {
+ LOGGER.warn(String.format("Warn: %s not set the actual class on superclass generic parameter",
+ clazz.getSimpleName()));
+ return Object.class;
+ }
+ return (Class>) params[index];
+ }
+
+ /**
+ * 解析反射实体
+ *
+ * @param clazz 实体类对象
+ * @return
+ */
+ public static ReflectEntity resolveEntity(Class> clazz) {
+ String name = clazz.getName();
+ return Optional.ofNullable(REFLECT_ENTITY_CACHE.get(name))
+ .orElseGet(() -> {
+ ReflectEntity reflectEntity = doResolveEntity(clazz);
+ REFLECT_ENTITY_CACHE.put(name, reflectEntity);
+ return reflectEntity;
+ });
+ }
+
+ private static ReflectEntity doResolveEntity(Class> clazz) {
+ Objects.requireNonNull(clazz);
+ ReflectEntity reflectEntity = new ReflectEntity();
+ String simpleName = clazz.getSimpleName();
+ String tableName = simpleName;
+ // 读取类
+ TableName tableNameAnnotation = clazz.getAnnotation(TableName.class);
+ if (Objects.nonNull(tableNameAnnotation)) {
+ tableName = tableNameAnnotation.value();
+ }
+ reflectEntity.setTableName(tableName);
+ reflectEntity.setSimpleName(simpleName);
+
+ // 读取字段
+ List fields = new ArrayList<>();
+ reflectEntity.setFields(fields);
+ List declaredFields = getDeclaredFields(clazz);
+ for (Field declaredField : declaredFields) {
+ String name = declaredField.getName();
+ boolean primaryKey = false;
+ boolean ignoreSaving = false;
+ String fieldName = name;
+ TableId tableIdAnnotation = declaredField.getAnnotation(TableId.class);
+ if (Objects.nonNull(tableIdAnnotation)) {
+ if (RobinStrUtils.isNotBlank(tableIdAnnotation.value())) {
+ fieldName = tableIdAnnotation.value();
+ }
+ primaryKey = true;
+ ignoreSaving = true;
+ }
+
+ TableField tableFieldAnnotation = declaredField.getAnnotation(TableField.class);
+ if (Objects.nonNull(tableFieldAnnotation)) {
+ if (RobinStrUtils.isNotBlank(tableFieldAnnotation.value())) {
+ fieldName = tableFieldAnnotation.value();
+ }
+ ignoreSaving = tableFieldAnnotation.ignoreSaving();
+ }
+
+ ReflectField field = new ReflectField();
+ field.setName(name);
+ field.setFieldName(fieldName);
+ field.setIgnoreSaving(ignoreSaving);
+ field.setPrimaryKey(primaryKey);
+ fields.add(field);
+ }
+ reflectEntity.setReflectEntityHelper(ReflectEntityHelper.build(reflectEntity));
+ return reflectEntity;
+ }
+
+
+ public static boolean isIdNull(T entity) {
+ return isFieldNull(entity, PRIMARY_KEY_NAME);
+ }
+
+ public static boolean isFieldNull(T entity, String fieldName) {
+ Object value = methodInvoke(getMethod(entity.getClass(), fieldName), entity);
+ return Objects.isNull(value);
+ }
+
+ /**
+ * 获取实体字段和值
+ *
+ * @param entity 实体对象
+ * @param 实体对象类
+ * @return
+ */
+ public static Map resolveEntityFieldAndValue(T entity) {
+ Map fieldValueMap = new HashMap<>();
+ Class> entityClass = entity.getClass();
+ List declaredFields = getDeclaredFields(entityClass);
+ List fieldNames = declaredFields.stream()
+ .map(Field::getName)
+ .collect(Collectors.toList());
+
+ Method[] declaredMethods = entityClass.getDeclaredMethods();
+ Map getMethodMap = Arrays.stream(declaredMethods).filter(x -> x.getName().startsWith(GET_METHOD_PREFIX))
+ .collect(Collectors.toMap(Method::getName, Function.identity()));
+
+ fieldNames.forEach(fieldName -> {
+ String methodName = GET_METHOD_PREFIX.concat(SqlUtils.firstToUpperCase(fieldName));
+ Method method = getMethodMap.get(methodName);
+ Object value = methodInvoke(method, entity);
+ if (Objects.nonNull(value)) {
+ fieldValueMap.put(fieldName, value.toString());
+ }
+ });
+ return fieldValueMap;
+ }
+
+ public static Object methodInvoke(Method method, Object obj, Object... args) {
+ try {
+ method.setAccessible(true);
+ return method.invoke(obj, args);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ LOGGER.error("methodInvoke error", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Method getMethod(Class> entityClass, String fieldName) {
+ try {
+ return entityClass.getDeclaredMethod(GET_METHOD_PREFIX.concat(SqlUtils.firstToUpperCase(fieldName)));
+ } catch (NoSuchMethodException e) {
+ LOGGER.error("getMethod error", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 获取所有声明字段,且非static修饰
+ *
+ * @param clazz 类对象
+ * @return
+ */
+ private static List getDeclaredFields(Class> clazz) {
+ Field[] declaredFields = clazz.getDeclaredFields();
+ return Arrays.stream(declaredFields).filter(x -> !Modifier.isStatic(x.getModifiers()))
+ .collect(Collectors.toList());
+
+ }
+
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/util/RobinStrUtils.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/util/RobinStrUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc1e1c14d925464c2ecfdaec77c0bdc97f54df65
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/util/RobinStrUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.util;
+
+/**
+ * @author Clive Yuan
+ * @date 2020/12/23
+ */
+public class RobinStrUtils {
+
+ public static final String EMPTY = "";
+
+ public static boolean isBlank(final CharSequence cs) {
+ final int strLen = length(cs);
+ if (strLen == 0) {
+ return true;
+ }
+ for (int i = 0; i < strLen; i++) {
+ if (!Character.isWhitespace(cs.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static int length(final CharSequence cs) {
+ return cs == null ? 0 : cs.length();
+ }
+
+ public static boolean isNotBlank(final CharSequence cs) {
+ return !isBlank(cs);
+ }
+
+}
diff --git a/robin-base/src/main/java/cn/cliveyuan/robin/base/util/SqlUtils.java b/robin-base/src/main/java/cn/cliveyuan/robin/base/util/SqlUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..5feaba0aded716a8300a9036565e2db9131a433c
--- /dev/null
+++ b/robin-base/src/main/java/cn/cliveyuan/robin/base/util/SqlUtils.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.base.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Clive Yuan
+ * @date 2020/10/29
+ */
+public class SqlUtils {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SqlUtils.class);
+ private static final Pattern SQL_PARAM_PATTERN = Pattern.compile("(@\\{)([\\w]+)(\\})");
+ private static final Pattern TPL_PARAM_PATTERN = Pattern.compile("(@sql\\{)([\\w]+)(\\})");
+ private static final String SQL_SEGMENT_PATH = "/sql-segment.xml";
+ private static final Map SQL_SEGMENT = new ConcurrentHashMap<>();
+ private static final String INJECTION_REGEX = "[A-Za-z0-9\\_\\-\\+\\.]+";
+
+
+ /**
+ * 检查字段名是否合法
+ *
+ * @param columnName 字段名
+ */
+ public static void checkColumnName(String columnName) {
+ AssertUtils.isTrue(!SqlUtils.isSQLInjection(columnName), "columnName is illegal: " + columnName);
+ }
+
+ public static boolean isSQLInjection(String str) {
+ return !Pattern.matches(INJECTION_REGEX, str);
+ }
+
+ /**
+ * get sql segment
+ *
+ * @return
+ */
+ public static Map getSqlSegmentMap() {
+ synchronized (SQL_SEGMENT) {
+ if (SQL_SEGMENT.isEmpty()) {
+ SQL_SEGMENT.putAll(doResolveSqlSegment());
+ }
+ }
+ return SQL_SEGMENT;
+ }
+
+ private static Map doResolveSqlSegment() {
+ Map map = new HashMap<>();
+ try {
+ DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); //NOSONAR
+ DocumentBuilder builder = builderFactory.newDocumentBuilder();
+ InputStream inputStream = SqlUtils.class.getResourceAsStream(SQL_SEGMENT_PATH);
+ Document document = builder.parse(inputStream);
+ Element rootElement = document.getDocumentElement();
+ NodeList childNodes = rootElement.getElementsByTagName("sql");
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Element element = (Element) childNodes.item(i);
+ String id = element.getAttribute("id");
+ String textContent = element.getTextContent();
+ if (textContent.contains("@sql")) {
+ textContent = parseTplVariable(textContent, map);
+ }
+ map.put(id, textContent);
+ }
+ } catch (Exception e) {
+ LOGGER.error("resolveSqlSegment", e);
+ }
+ return map;
+ }
+
+ public static String parseSqlVariable(String template, Map paramMap) {
+ return parseVariable(SQL_PARAM_PATTERN, template, paramMap);
+ }
+
+ public static String parseTplVariable(String template, Map paramMap) {
+ List matches = getMatches(TPL_PARAM_PATTERN, template);
+ if (matches.isEmpty()) {
+ return template;
+ }
+ Map newParamMap = new HashMap<>();
+ matches.forEach(x -> newParamMap.put(x, replaceDollar(paramMap.get(x))));
+ return restoreDollar(parseVariable(TPL_PARAM_PATTERN, template, newParamMap));
+ }
+
+ private static String replaceDollar(String content) {
+ return content.replace("$", "$");
+ }
+
+ private static String restoreDollar(String content) {
+ return content.replace("$", "$");
+ }
+
+ /**
+ * 解析变量
+ *
+ * @param pattern 模式
+ * @param template 模板 变量格式为 @{name}
+ * @param paramMap 参数
+ * @return
+ */
+ private static String parseVariable(Pattern pattern, String template, Map paramMap) {
+ Objects.requireNonNull(template);
+ Objects.requireNonNull(paramMap);
+ Matcher m = pattern.matcher(template);
+ StringBuffer sb = new StringBuffer();
+ while (m.find()) {
+ String group = m.group(2);
+ String value = paramMap.get(group);
+ m.appendReplacement(sb, value);
+ }
+ m.appendTail(sb);
+ return sb.toString();
+ }
+
+ private static List getMatches(Pattern pattern, String template) {
+ List list = new ArrayList<>();
+ Matcher m = pattern.matcher(template);
+ while (m.find()) {
+ list.add(m.group(2));
+ }
+ return list;
+ }
+
+ public static String wrapperScript(String sqlScript) {
+ return String.format("", sqlScript);
+ }
+
+ public static String resolveMapperScript(String template, Map paramMap) {
+ return wrapperScript(parseSqlVariable(template, paramMap));
+ }
+
+ public static String firstToLowerCase(String param) {
+ if (RobinStrUtils.isBlank(param)) {
+ return RobinStrUtils.EMPTY;
+ }
+ return param.substring(0, 1).toLowerCase() + param.substring(1);
+ }
+
+ public static String firstToUpperCase(String param) {
+ if (RobinStrUtils.isBlank(param)) {
+ return RobinStrUtils.EMPTY;
+ }
+ return param.substring(0, 1).toUpperCase() + param.substring(1);
+ }
+}
diff --git a/robin-base/src/main/resources/sql-segment.xml b/robin-base/src/main/resources/sql-segment.xml
index 30f6cda27c3c5da96fef84d35207e351b39dcf99..422d18f99bc11eed00be133da58ed4aaecbcee75 100644
--- a/robin-base/src/main/resources/sql-segment.xml
+++ b/robin-base/src/main/resources/sql-segment.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/robin-base/src/test/java/cn/cliveyuan/robin/base/test/BeanCopyUtilsTest.java b/robin-base/src/test/java/cn/cliveyuan/robin/base/test/BeanCopyUtilsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f61201ede3968d18429d9ac1b449ae10ee32af6
--- /dev/null
+++ b/robin-base/src/test/java/cn/cliveyuan/robin/base/test/BeanCopyUtilsTest.java
@@ -0,0 +1,79 @@
+package cn.cliveyuan.robin.base.test;
+
+import cn.cliveyuan.robin.base.common.Pagination;
+import cn.cliveyuan.robin.base.util.BeanCopyUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author Clive Yuan
+ * @date 2020/12/24
+ */
+public class BeanCopyUtilsTest {
+
+ @Test
+ public void copy() {
+ User user = new User();
+ user.setId(1);
+ user.setUsername("Clive");
+ user.setAge(18);
+ user.setBirthday(new Date());
+ UserDTO userDTO = BeanCopyUtils.copy(user, UserDTO.class);
+ verify(user, userDTO);
+ }
+
+ @Test
+ public void copyList() {
+ List list = new ArrayList<>();
+ for (int i = 0; i < 100; i++) {
+ User user = new User();
+ user.setId(1);
+ user.setUsername("Clive");
+ user.setAge(18);
+ user.setBirthday(new Date());
+ list.add(user);
+ }
+
+ List targetList = BeanCopyUtils.copyList(list, UserDTO.class);
+ for (int i = 0; i < targetList.size(); i++) {
+ User user = list.get(i);
+ UserDTO userDTO = targetList.get(i);
+ verify(user, userDTO);
+ }
+ }
+
+ @Test
+ public void copyPagination() {
+ List list = new ArrayList<>();
+ for (int i = 0; i < 100; i++) {
+ User user = new User();
+ user.setId(1);
+ user.setUsername("Clive");
+ user.setAge(18);
+ user.setBirthday(new Date());
+ list.add(user);
+ }
+ Pagination pagination = new Pagination<>(list.size(), list, 1, list.size());
+ Pagination targetPagination = BeanCopyUtils.copyPagination(pagination, UserDTO.class);
+ Assert.assertNotNull(targetPagination);
+ Assert.assertEquals(pagination.getTotalCount(), targetPagination.getTotalCount());
+ Assert.assertEquals(pagination.isHasNext(), targetPagination.isHasNext());
+ for (int i = 0; i < targetPagination.getDataList().size(); i++) {
+ User user = pagination.getDataList().get(i);
+ UserDTO userDTO = targetPagination.getDataList().get(i);
+ verify(user, userDTO);
+ }
+ }
+
+ private void verify(User user, UserDTO userDTO) {
+ Assert.assertNotNull(userDTO);
+ Assert.assertEquals(user.getId(), userDTO.getId());
+ Assert.assertEquals(user.getUsername(), userDTO.getUsername());
+ Assert.assertEquals(user.getAge(), userDTO.getAge());
+ Assert.assertEquals(user.getBirthday(), userDTO.getBirthday());
+ }
+}
diff --git a/robin-base/src/test/java/cn/cliveyuan/robin/base/test/User.java b/robin-base/src/test/java/cn/cliveyuan/robin/base/test/User.java
new file mode 100644
index 0000000000000000000000000000000000000000..c13ac56250108ef345514814b0a183240f7c4f21
--- /dev/null
+++ b/robin-base/src/test/java/cn/cliveyuan/robin/base/test/User.java
@@ -0,0 +1,57 @@
+package cn.cliveyuan.robin.base.test;
+
+import java.util.Date;
+
+/**
+ * @author Clive Yuan
+ * @date 2020/12/24
+ */
+public class User {
+ private Integer id;
+ private String username;
+ private Integer age;
+ private Date birthday;
+
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public Date getBirthday() {
+ return birthday;
+ }
+
+ public void setBirthday(Date birthday) {
+ this.birthday = birthday;
+ }
+
+ @Override
+ public String toString() {
+ return "User{" +
+ "id=" + id +
+ ", username='" + username + '\'' +
+ ", age=" + age +
+ ", birthday=" + birthday +
+ '}';
+ }
+}
diff --git a/robin-base/src/test/java/cn/cliveyuan/robin/base/test/UserDTO.java b/robin-base/src/test/java/cn/cliveyuan/robin/base/test/UserDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..f91a03d8735280b2216636a7bfd0a7579a306504
--- /dev/null
+++ b/robin-base/src/test/java/cn/cliveyuan/robin/base/test/UserDTO.java
@@ -0,0 +1,56 @@
+package cn.cliveyuan.robin.base.test;
+
+import java.util.Date;
+
+/**
+ * @author Clive Yuan
+ * @date 2020/12/24
+ */
+public class UserDTO {
+ private Integer id;
+ private String username;
+ private Integer age;
+ private Date birthday;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public Date getBirthday() {
+ return birthday;
+ }
+
+ public void setBirthday(Date birthday) {
+ this.birthday = birthday;
+ }
+
+ @Override
+ public String toString() {
+ return "UserDTO{" +
+ "id=" + id +
+ ", username='" + username + '\'' +
+ ", age=" + age +
+ ", birthday=" + birthday +
+ '}';
+ }
+}
diff --git a/robin-generator/dtd/robin-generator.dtd b/robin-generator/dtd/robin-generator.dtd
new file mode 100644
index 0000000000000000000000000000000000000000..3ca713b0723f94f4da6ba4154bb97dcf2fe9727c
--- /dev/null
+++ b/robin-generator/dtd/robin-generator.dtd
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/robin-generator/pom.xml b/robin-generator/pom.xml
index f5fc82f0ddfcd75c224e083ce598ad707feae775..ab1c007b6ebdf8c5ac7d8535546a5ea75583f7fb 100644
--- a/robin-generator/pom.xml
+++ b/robin-generator/pom.xml
@@ -2,20 +2,93 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
robin-generator
+ 1.1.0
jar
com.gitee.opensource4clive
robin
- ${revision}
- ../
+ 1.1.0
+
+ mysql
+ mysql-connector-java
+ runtime
+
+
+ dom4j
+ dom4j
+
+
+ jaxen
+ jaxen
+
+
+ org.freemarker
+ freemarker
+
+
+ org.projectlombok
+ lombok
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ commons-io
+ commons-io
+
+
+ com.google.guava
+ guava
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+ test
+
junit
junit
test
+
+ org.springframework
+ spring-webmvc
+ 5.2.9.RELEASE
+ test
+
+
+ javax.annotation
+ javax.annotation-api
+ test
+
+
+ io.swagger
+ swagger-annotations
+ test
+
+
+ javax.validation
+ validation-api
+ test
+
+
+ org.hibernate.validator
+ hibernate-validator
+ test
+
+
+ com.gitee.opensource4clive
+ robin-base
+ test
+
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/CodeGeneratorXmlConfig.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/CodeGeneratorXmlConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..2fc27013864620e492d577ca0293104a9e2c4cfd
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/CodeGeneratorXmlConfig.java
@@ -0,0 +1,92 @@
+package cn.cliveyuan.robin.generator.core;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 代码生成配置
+ *
+ * @author Clive Yuan
+ * @date 2020/09/07
+ */
+@Data
+public class CodeGeneratorXmlConfig implements Serializable {
+
+ private BaseConfig baseConfig;
+ private JdbcConnection jdbcConnection;
+ private MapperGeneratorConfig javaModelGenerator;
+ private MapperGeneratorConfig sqlMapGenerator;
+ private MapperGeneratorConfig javaClientGenerator;
+ private MapperGeneratorConfig serviceGenerator;
+ private MapperGeneratorConfig controllerGenerator;
+ private MapperGeneratorConfig dtoGenerator;
+ private List tables;
+ private List fixedColumns;
+ private List fixedField;
+
+ @Data
+ @Builder
+ public static class BaseConfig {
+ private boolean enableLombok;
+ private boolean enableSwagger;
+ private boolean enableValidation;
+ private boolean enableReadWriteSeparation;
+ private boolean disableUpdatingMapperXml;
+ }
+
+ @Data
+ @Builder
+ public static class JdbcConnection {
+ private String driverClass;
+ private String connectionURL;
+ private String username;
+ private String password;
+ }
+
+ @Data
+ @Builder
+ public static class MapperGeneratorConfig {
+ /**
+ * 是否禁用
+ */
+ private boolean disabled;
+ /**
+ * 模板路径(相对resource路径)
+ */
+ private String templatePath;
+ /**
+ * 代码路径(相对root路径)
+ */
+ private String codePath;
+ /**
+ * 目标包名
+ */
+ private String targetPackage;
+ /**
+ * 后缀
+ */
+ private String suffix;
+
+ // 以下为内部字段,不从xml中读取
+ /**
+ * 模块名
+ */
+ private String moduleName;
+ }
+
+ @Data
+ @Builder
+ public static class Table {
+ private String tableName;
+ private String entityObjectName;
+ private Set ignoreColumns;
+ private String entityObjectSuffix;
+ }
+
+}
+
+
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/Entity.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/Entity.java
new file mode 100644
index 0000000000000000000000000000000000000000..ebd02b7abae552ab09da6cd97b8a40ec076095f3
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/Entity.java
@@ -0,0 +1,50 @@
+package cn.cliveyuan.robin.generator.core;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 实体
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+@Data
+public class Entity implements Serializable {
+ /**
+ * 是否有BigDecimal类型的字段
+ */
+ private boolean hasBigDecimalField;
+ /**
+ * 小写驼峰名
+ */
+ private String lowerCamelName;
+ /**
+ * 实体名 (用户自定义, 若无则为首字母大写的驼峰)
+ */
+ private String entityName;
+ /**
+ * 表名
+ */
+ private String tableName;
+ /**
+ * 大写驼峰名
+ */
+ private String upperCamelName;
+ /**
+ * 备注
+ */
+ private String comment;
+ /**
+ * 字段
+ */
+ private List fields;
+
+ // 内部使用字段
+ /**
+ * 文件名(生成文件用)
+ */
+ private String fileName;
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/ExtensionParam.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/ExtensionParam.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c7e02a621386f0bbbb876aa14adacb12c0e234a
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/ExtensionParam.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.generator.core;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * @author Clive Yuan
+ * @date 2021/02/02
+ */
+@Data
+@Builder
+public class ExtensionParam {
+ /**
+ * 生成上下文
+ */
+ private GeneratorContext context;
+ /**
+ * 模板参数
+ */
+ private Map paramMap;
+ /**
+ * 代码文件保存路径
+ */
+ private String codeFilePath;
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/Field.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/Field.java
new file mode 100644
index 0000000000000000000000000000000000000000..092bedbc8c8535982cea842c4be52a790faa2e98
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/Field.java
@@ -0,0 +1,61 @@
+package cn.cliveyuan.robin.generator.core;
+
+import cn.cliveyuan.robin.generator.db.JdbcType;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 字段
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+@Data
+public class Field implements Serializable {
+ /**
+ * JDBC类型
+ */
+ private JdbcType jdbcType;
+ /**
+ * 小写驼峰名
+ */
+ private String lowerCamelName;
+ /**
+ * 大写驼峰名
+ */
+ private String upperCamelName;
+ /**
+ * 列名
+ */
+ private String columnName;
+ /**
+ * 长度
+ */
+ private Integer length;
+ /**
+ * 是否可空
+ */
+ private Boolean nullable;
+ /**
+ * 备注
+ */
+ private String comment;
+ /**
+ * 是否标注字段名
+ * 当lowerCamelName与columnName不一致时需要标注
+ */
+ private boolean markColumnName;
+ /**
+ * 是否为主键
+ */
+ private boolean primaryKey;
+ /**
+ * 保存忽略
+ */
+ private boolean ignoreSaving;
+ /**
+ * swagger 字段隐藏
+ */
+ private boolean swaggerHidden;
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/FileType.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/FileType.java
new file mode 100644
index 0000000000000000000000000000000000000000..760bd2456b819938320f07c37ffbc1d3fe19c9de
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/FileType.java
@@ -0,0 +1,23 @@
+package cn.cliveyuan.robin.generator.core;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 文件类型
+ *
+ * @author Clive Yuan
+ * @date 2020/11/06
+ */
+@Getter
+@AllArgsConstructor
+public enum FileType {
+
+ JAVA(".java"),
+ XML(".xml");
+
+ /**
+ * 扩展名
+ */
+ private final String extension;
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GenerateParam.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GenerateParam.java
new file mode 100644
index 0000000000000000000000000000000000000000..cde93cd2d63fe65fa518e6f03f8d85ecf528519f
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GenerateParam.java
@@ -0,0 +1,40 @@
+package cn.cliveyuan.robin.generator.core;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * 生成参数
+ *
+ * @author Clive Yuan
+ * @date 2020/11/06
+ */
+@Data
+@Builder
+public class GenerateParam implements Serializable {
+ /**
+ * 上下文
+ */
+ private GeneratorContext context;
+ /**
+ * 文件名方法
+ */
+ private Function fileNameFunction;
+ /**
+ * 生成器
+ */
+ private Generator generator;
+ /**
+ * 生成配置
+ */
+ private CodeGeneratorXmlConfig.MapperGeneratorConfig generatorConfig;
+ /**
+ * 生成前 扩展处理
+ */
+ private Consumer beforeExtensionHandle;
+
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/Generator.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/Generator.java
new file mode 100644
index 0000000000000000000000000000000000000000..a8799cdc0b4dc0cee06e914aa7a7fe40757dc483
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/Generator.java
@@ -0,0 +1,18 @@
+package cn.cliveyuan.robin.generator.core;
+
+/**
+ * 代码生成器接口
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+public interface Generator {
+
+ /**
+ * 生成
+ *
+ * @param context 上下文
+ * @param generatorChain 生成器链
+ */
+ void generate(GeneratorContext context, GeneratorChain generatorChain);
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorChain.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorChain.java
new file mode 100644
index 0000000000000000000000000000000000000000..e819f9b5416bf35de04084cb2dba9b1efb61e9f5
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorChain.java
@@ -0,0 +1,189 @@
+package cn.cliveyuan.robin.generator.core;
+
+import cn.cliveyuan.robin.generator.util.FreemarkerUtils;
+import cn.cliveyuan.robin.generator.util.ReflectUtils;
+import com.google.common.collect.Maps;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * 生成器链
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+@Slf4j
+public class GeneratorChain implements Generator {
+
+ /**
+ * 生成器集合
+ */
+ private final List generators = new ArrayList<>();
+
+ /**
+ * 执行下标
+ */
+ private int index = 0;
+
+ public GeneratorChain() {
+ this.init();
+ }
+
+ @Override
+ public void generate(GeneratorContext context, GeneratorChain generatorChain) {
+ if (generators.size() == index) {
+ return;
+ }
+ Generator generator = generators.get(index);
+ log.info("GeneratorChain generate: generator={}, index={}", generator.getClass().getSimpleName(), index);
+ index++;
+ generator.generate(context, generatorChain);
+ }
+
+ /**
+ * 初始化: 从生成器注册枚举中读取生成器并将其初始化然后加入列表
+ */
+ private void init() {
+ log.info("GeneratorChain init START");
+ for (GeneratorEnum value : GeneratorEnum.values()) {
+ generators.add(ReflectUtils.newInstance(value.getClazz()));
+ }
+ log.info("GeneratorChain init END, generators.size={}", generators.size());
+ }
+
+ /**
+ * 生成文件
+ *
+ * @param generateParam 生成参数
+ */
+ public void doGenerate(GenerateParam generateParam) {
+ try {
+ CodeGeneratorXmlConfig.MapperGeneratorConfig generatorConfig = generateParam.getGeneratorConfig();
+ if (generatorConfig.isDisabled()) {
+ log.info("doGenerate skip the module: {}", generatorConfig.getModuleName());
+ return;
+ }
+ GeneratorEnum generatorEnum = GeneratorEnum.valueOf(generateParam.getGenerator());
+ GeneratorContext context = generateParam.getContext();
+ // 设置文件名
+ this.setFileName(generateParam);
+ // 文件保存路径
+ String codeFilePath = this.getCodeFilePath(generatorConfig);
+ // 模板变量
+ Map paramMap = this.getParamMap(context);
+ String customizedTemplatePath = generatorConfig.getTemplatePath();
+ // 代码模板: 如果自定义为空 则使用默认
+ String templatePath = StringUtils.isNotBlank(customizedTemplatePath) ?
+ customizedTemplatePath : generatorEnum.getTemplatePath();
+
+ if (Objects.nonNull(generateParam.getBeforeExtensionHandle())) {
+ generateParam.getBeforeExtensionHandle().accept(ExtensionParam.builder()
+ .context(generateParam.getContext())
+ .paramMap(paramMap)
+ .codeFilePath(codeFilePath)
+ .build());
+ }
+ // 遍历实体列表,生成文件
+ context.getEntityList().forEach(entity -> this.generateFile(paramMap, templatePath, codeFilePath, entity, generatorEnum));
+
+ } finally {
+ // 继续责任链
+ this.generate(generateParam.getContext(), this);
+ }
+ }
+
+ public String getSuffix(CodeGeneratorXmlConfig.MapperGeneratorConfig generatorConfig, Generator generator) {
+ return getSuffix(generatorConfig, GeneratorEnum.valueOf(generator));
+ }
+
+ public String getSuffix(CodeGeneratorXmlConfig.MapperGeneratorConfig generatorConfig, GeneratorEnum generatorEnum) {
+ String suffix = generatorEnum.getDefaultSuffix();
+ if (StringUtils.isNotBlank(generatorConfig.getSuffix())) {
+ suffix = generatorConfig.getSuffix();
+ }
+ return suffix;
+ }
+
+ private void setFileName(GenerateParam generateParam) {
+ Function function = generateParam.getFileNameFunction();
+ List entities = generateParam.getContext().getEntityList();
+
+ if (Objects.nonNull(function)) {
+ // 处理自定义文件名
+ entities.forEach(x -> x.setFileName(function.apply(x)));
+ } else {
+ // 默认为 大小驼峰+后缀
+ String suffix = this.getSuffix(generateParam.getGeneratorConfig(), generateParam.getGenerator());
+ entities.forEach(x -> x.setFileName(x.getUpperCamelName() + suffix));
+ }
+ }
+
+ private String getCodeFilePath(CodeGeneratorXmlConfig.MapperGeneratorConfig generatorConfig) {
+ String targetPackage = generatorConfig.getTargetPackage();
+ String projectPath = System.getProperty("user.dir");
+ String codePath = generatorConfig.getCodePath();
+ String packagePath = targetPackage.replace(".", "/");
+ return String.format("%s/%s/%s", projectPath, codePath, packagePath);
+ }
+
+ private Map getParamMap(GeneratorContext context) {
+ CodeGeneratorXmlConfig xmlConfig = context.getXmlConfig();
+ CodeGeneratorXmlConfig.MapperGeneratorConfig javaModelGenerator = xmlConfig.getJavaModelGenerator();
+ CodeGeneratorXmlConfig.MapperGeneratorConfig javaClientGenerator = xmlConfig.getJavaClientGenerator();
+ CodeGeneratorXmlConfig.MapperGeneratorConfig serviceGenerator = xmlConfig.getServiceGenerator();
+ CodeGeneratorXmlConfig.MapperGeneratorConfig controllerGenerator = xmlConfig.getControllerGenerator();
+ CodeGeneratorXmlConfig.MapperGeneratorConfig dtoGenerator = xmlConfig.getDtoGenerator();
+ String dtoSuffix = this.getSuffix(dtoGenerator, GeneratorEnum.DTO);
+ String mapperSuffix = this.getSuffix(javaClientGenerator, GeneratorEnum.MAPPER_JAVA);
+ String serviceSuffix = this.getSuffix(serviceGenerator, GeneratorEnum.SERVICE);
+ // 设置变量
+ Map paramMap = Maps.newHashMap();
+ paramMap.put("entityPackage", javaModelGenerator.getTargetPackage());
+ paramMap.put("mapperPackage", javaClientGenerator.getTargetPackage());
+ paramMap.put("mapperImplPackage", javaClientGenerator.getTargetPackage() + GeneratorConst.IMPL_PKG_SUFFIX);
+ paramMap.put("servicePackage", serviceGenerator.getTargetPackage());
+ paramMap.put("serviceImplPackage", serviceGenerator.getTargetPackage() + GeneratorConst.IMPL_PKG_SUFFIX);
+ paramMap.put("controllerPackage", controllerGenerator.getTargetPackage());
+ paramMap.put("dtoPackage", dtoGenerator.getTargetPackage());
+ paramMap.put("baseConfig", xmlConfig.getBaseConfig());
+ paramMap.put("dtoSuffix", dtoSuffix);
+ paramMap.put("mapperSuffix", mapperSuffix);
+ paramMap.put("serviceSuffix", serviceSuffix);
+ return paramMap;
+ }
+
+ private void generateFile(Map paramMap, String ftlPath, String codeFilePath,
+ Entity entity, GeneratorEnum generatorEnum) {
+ String fileName = entity.getFileName() + generatorEnum.getFileType().getExtension();
+ paramMap.put("entity", entity);
+ // 通过模板生成代码内容
+ String codeContent = FreemarkerUtils.parseTemplate(ftlPath, paramMap);
+ String filePath = String.format("%s/%s", codeFilePath, fileName);
+ // 写入指定目录
+ this.writeStringToFile(filePath, codeContent, generatorEnum.isOverwrite());
+ }
+
+ private void writeStringToFile(String filePath, String data, boolean overwrite) {
+ try {
+ File file = new File(filePath);
+ if (file.exists() && !overwrite) {
+ log.debug("The file is exist, skip creating it: {}", file.getName());
+ return;
+ }
+ FileUtils.writeStringToFile(file, data, StandardCharsets.UTF_8);
+ log.debug("The file was created successfully: {}", file.getName());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorConst.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorConst.java
new file mode 100644
index 0000000000000000000000000000000000000000..f63a7e60c1e779447488ae8fe5629771cd61f026
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorConst.java
@@ -0,0 +1,15 @@
+package cn.cliveyuan.robin.generator.core;
+
+/**
+ * 常量
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+public class GeneratorConst {
+ public final static String PRIMARY_KEY_COL = "id";
+ public final static String CREATED_TIME_COL = "created_at";
+ public final static String UPDATED_TIME_COL = "updated_at";
+ public final static String KEY_WORD_PREFIX = "$";
+ public final static String IMPL_PKG_SUFFIX = ".impl";
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorContext.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..72447acf4f0b32bdb251e49801b8b890ec443b0f
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorContext.java
@@ -0,0 +1,23 @@
+package cn.cliveyuan.robin.generator.core;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 上下文
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+@Data
+public class GeneratorContext {
+ /**
+ * 实体列表
+ */
+ private List entityList;
+ /**
+ * 配置
+ */
+ private CodeGeneratorXmlConfig xmlConfig;
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorContextResolver.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorContextResolver.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff614bd951c3c27f6dc6bee7e076480ab674e829
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorContextResolver.java
@@ -0,0 +1,384 @@
+package cn.cliveyuan.robin.generator.core;
+
+import cn.cliveyuan.robin.generator.db.ColumnInfo;
+import cn.cliveyuan.robin.generator.db.ConnectionFactory;
+import cn.cliveyuan.robin.generator.db.JdbcType;
+import cn.cliveyuan.robin.generator.db.JdbcTypeResolver;
+import cn.cliveyuan.robin.generator.db.MysqlTableIntrospect;
+import cn.cliveyuan.robin.generator.db.TableInfo;
+import cn.cliveyuan.robin.generator.db.TableIntrospect;
+import cn.cliveyuan.robin.generator.util.CollectionUtils;
+import cn.cliveyuan.robin.generator.util.GeneratorUtils;
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.dom4j.Attribute;
+import org.dom4j.Document;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.springframework.util.Assert;
+
+import javax.lang.model.SourceVersion;
+import java.io.File;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 配置上下文解析器
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+@Slf4j
+public class GeneratorContextResolver {
+
+ private static final String CONFIG_XML_NAME = "code-generator.xml";
+ private static final String DEFAULT_CONFIG_FILE_PATH = "/" + CONFIG_XML_NAME;
+
+ private CodeGeneratorXmlConfig xmlConfig;
+ private TableIntrospect tableIntrospect;
+
+ /**
+ * 解析上下文
+ *
+ * @param configFilePath 配置文件
+ * @return
+ */
+ public GeneratorContext resolve(String configFilePath) {
+ log.info("GeneratorContextResolver resolving START, configFilePath={}", configFilePath);
+ CodeGeneratorXmlConfig xmlConfig = this.parseXmlConfig(configFilePath);
+ List entityList = this.parseEntityList(xmlConfig);
+ GeneratorContext generatorContext = new GeneratorContext();
+ generatorContext.setEntityList(entityList);
+ generatorContext.setXmlConfig(xmlConfig);
+ this.closeTableIntrospect();
+ log.info("GeneratorContextResolver resolving END, entityList.size={}", entityList.size());
+ return generatorContext;
+ }
+
+ private void initTableIntrospect() {
+ CodeGeneratorXmlConfig.JdbcConnection jdbcConnection = xmlConfig.getJdbcConnection();
+ Connection connection = ConnectionFactory.createConnection(jdbcConnection.getDriverClass(),
+ jdbcConnection.getConnectionURL(), jdbcConnection.getUsername(), jdbcConnection.getPassword());
+ this.tableIntrospect = new MysqlTableIntrospect(connection);
+ }
+
+ private void closeTableIntrospect() {
+ this.tableIntrospect.closeConnection();
+ }
+
+ private List parseEntityList(CodeGeneratorXmlConfig xmlConfig) {
+ List tables = xmlConfig.getTables();
+ return tables.stream().map(this::table2Entity).collect(Collectors.toList());
+ }
+
+ private Entity table2Entity(CodeGeneratorXmlConfig.Table table) {
+ TableInfo tableInfo = tableIntrospect.introspect(table.getTableName());
+ Entity entity = new Entity();
+ String lowerCamelName = GeneratorUtils.getLowerCamelName(tableInfo.getName());
+ String upperCamelName = StringUtils.capitalize(lowerCamelName);
+ String entityName = table.getEntityObjectName();
+ String suffix = StringUtils.isNotBlank(table.getEntityObjectSuffix()) ? table.getEntityObjectSuffix() : "";
+ if (StringUtils.isNotBlank(entityName)) {
+ lowerCamelName = GeneratorUtils.getLowerCamelName(entityName);
+ upperCamelName = StringUtils.capitalize(entityName);
+ }
+ entityName = upperCamelName + suffix;
+ entity.setLowerCamelName(lowerCamelName);
+ entity.setEntityName(entityName);
+ entity.setTableName(tableInfo.getName());
+ entity.setComment(tableInfo.getComment());
+ entity.setUpperCamelName(upperCamelName);
+
+ List fields = new ArrayList<>();
+ fields.addAll(xmlConfig.getFixedField());
+ fields.addAll(this.column2Field(table, tableInfo.getColumns()));
+ this.matchIdType(fields, tableInfo.getColumns());
+ entity.setFields(fields);
+ entity.setHasBigDecimalField(this.hasBigDecimalField(entity.getFields()));
+ return entity;
+ }
+
+ // 匹配id类型
+ private void matchIdType(List fields, List columns) {
+ columns.stream().filter(x -> Objects.equals(GeneratorConst.PRIMARY_KEY_COL, x.getName()))
+ .findAny().ifPresent(idColumn -> {
+ fields.removeIf(x -> Objects.equals(GeneratorConst.PRIMARY_KEY_COL, x.getColumnName()));
+ Field idField = this.convertColumnInfo2Field(idColumn);
+ idField.setNullable(true);
+ idField.setComment("ID 主键");
+ idField.setMarkColumnName(false);
+ idField.setPrimaryKey(true);
+ fields.add(0, idField);
+ });
+ }
+
+ private List column2Field(CodeGeneratorXmlConfig.Table table, List columns) {
+ return columns.stream()
+ .filter(x -> !table.getIgnoreColumns().contains(x.getName())) // 排除忽略字段
+ .map(this::convertColumnInfo2Field).collect(Collectors.toList());
+ }
+
+ private Field convertColumnInfo2Field(ColumnInfo columnInfo) {
+ Field field = new Field();
+ field.setJdbcType(JdbcTypeResolver.resolve(columnInfo.getType(), columnInfo.getLength()));
+ String lowerCamelName = GeneratorUtils.getLowerCamelName(columnInfo.getName());
+ if (SourceVersion.isKeyword(lowerCamelName)) {
+ lowerCamelName += GeneratorConst.KEY_WORD_PREFIX;
+ }
+ field.setLowerCamelName(lowerCamelName);
+ field.setUpperCamelName(StringUtils.capitalize(lowerCamelName));
+ field.setColumnName(columnInfo.getName());
+ field.setLength(columnInfo.getLength());
+ field.setNullable(columnInfo.getNullable());
+ field.setComment(columnInfo.getComment());
+ field.setMarkColumnName(!Objects.equals(field.getLowerCamelName(), field.getColumnName()));
+ return field;
+ }
+
+ private boolean hasBigDecimalField(List fields) {
+ return fields.stream().anyMatch(x -> JdbcType.DECIMAL.equals(x.getJdbcType()));
+ }
+
+ private CodeGeneratorXmlConfig parseXmlConfig(String configFilePath) {
+ if (StringUtils.isBlank(configFilePath)) {
+ configFilePath = DEFAULT_CONFIG_FILE_PATH;
+ }
+ log.info("GeneratorContextResolver: configFilePath={}", configFilePath);
+ try {
+ CodeGeneratorXmlConfig config = new CodeGeneratorXmlConfig();
+ this.xmlConfig = config;
+ InputStream xmlInputStream = this.getClass().getResourceAsStream(configFilePath);
+ if (Objects.isNull(xmlInputStream)) {
+ log.info("can't find file in resource path, try to find in file system");
+ File file = new File(configFilePath);
+ xmlInputStream = FileUtils.openInputStream(file);
+ }
+ Assert.notNull(xmlInputStream, "code generator config file is not exist: " + configFilePath);
+ SAXReader reader = new SAXReader();
+ reader.setValidation(true);
+ Document document = reader.read(xmlInputStream);
+ Element rootElement = document.getRootElement();
+
+ config.setBaseConfig(this.getBaseConfig(rootElement));
+ config.setJdbcConnection(this.getJdbcConnection(rootElement));
+ this.initTableIntrospect();
+ config.setJavaModelGenerator(this.getMapperGeneratorConfig(rootElement, "javaModelGenerator"));
+ config.setSqlMapGenerator(this.getMapperGeneratorConfig(rootElement, "sqlMapGenerator"));
+ config.setJavaClientGenerator(this.getMapperGeneratorConfig(rootElement, "javaClientGenerator"));
+ config.setServiceGenerator(this.getMapperGeneratorConfig(rootElement, "serviceGenerator"));
+ config.setControllerGenerator(this.getMapperGeneratorConfig(rootElement, "controllerGenerator"));
+ config.setDtoGenerator(this.getMapperGeneratorConfig(rootElement, "dtoGenerator"));
+ this.verifyDtoGenerator(config);
+ config.setTables(this.getTables(rootElement));
+ return config;
+ } catch (Exception e) {
+ log.error("Fail to parse xml file: " + configFilePath, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void verifyDtoGenerator(CodeGeneratorXmlConfig config) {
+ CodeGeneratorXmlConfig.MapperGeneratorConfig controllerGenerator = config.getControllerGenerator();
+ CodeGeneratorXmlConfig.MapperGeneratorConfig dtoGenerator = config.getDtoGenerator();
+ if (!controllerGenerator.isDisabled()) {
+ Assert.isTrue(!dtoGenerator.isDisabled(), "DTO生成配置未生效, 请检查'dtoGenerator'标签(生成controller时DTO也需要开启)");
+ }
+ }
+
+ private CodeGeneratorXmlConfig.BaseConfig getBaseConfig(Element rootElement) {
+ CodeGeneratorXmlConfig.BaseConfig baseConfig = CodeGeneratorXmlConfig.BaseConfig.builder().build();
+ Element baseConfigEl = this.getNullableElement(rootElement, "baseConfig");
+ if (Objects.nonNull(baseConfigEl)) {
+ Iterator iterator = baseConfigEl.elementIterator("property");
+ while (iterator.hasNext()) {
+ Element property = (Element) iterator.next();
+ String name = this.getAttributeValue(property, "name");
+ if (StringUtils.isBlank(name)) {
+ continue;
+ }
+ switch (name) {
+ case "enableLombok": baseConfig.setEnableLombok("true".equalsIgnoreCase(property.getStringValue())); break;
+ case "enableSwagger": baseConfig.setEnableSwagger("true".equalsIgnoreCase(property.getStringValue())); break;
+ case "enableValidation": baseConfig.setEnableValidation("true".equalsIgnoreCase(property.getStringValue())); break;
+ case "enableReadWriteSeparation": baseConfig.setEnableReadWriteSeparation("true".equalsIgnoreCase(property.getStringValue())); break;
+ case "disableUpdatingMapperXml": baseConfig.setDisableUpdatingMapperXml("true".equalsIgnoreCase(property.getStringValue())); break;
+ }
+ }
+ }
+ return baseConfig;
+ }
+
+ private CodeGeneratorXmlConfig.JdbcConnection getJdbcConnection(Element rootElement) {
+ Element jdbcConnection = this.getElement(rootElement, "jdbcConnection");
+ String host = this.getAttributeValueNonBlank(jdbcConnection, "host");
+ String database = this.getAttributeValueNonBlank(jdbcConnection, "database");
+ String tinyInt1isBit = this.getAttributeValue(jdbcConnection, "tinyInt1isBit", true);
+ String queryString = StringUtils.EMPTY;
+ if ("false".equalsIgnoreCase(tinyInt1isBit)) {
+ queryString = "?tinyInt1isBit=false";
+ }
+ String connectionURL = String.format("jdbc:mysql://%s/%s%s", host, database, queryString);
+ return CodeGeneratorXmlConfig.JdbcConnection.builder()
+ .driverClass(this.getAttributeValueNonBlank(jdbcConnection, "driverClass"))
+ .connectionURL(connectionURL)
+ .username(this.getAttributeValue(jdbcConnection, "username"))
+ .password(this.getAttributeValue(jdbcConnection, "password"))
+ .build();
+ }
+
+ private List getTables(Element rootElement) {
+ List tables = Lists.newArrayList();
+ Element tableEls = this.getElement(rootElement, "tables");
+ String all = this.getAttributeValue(tableEls, "all", true);
+ boolean allTables = "true".equalsIgnoreCase(all);
+ String createTimeColumn = this.getAttributeValue(tableEls, "createTimeColumn", true);
+ String updateTimeColumn = this.getAttributeValue(tableEls, "updateTimeColumn", true);
+ String entityObjectSuffix = this.getAttributeValue(tableEls, "entityObjectSuffix", true);
+ Set globalIgnoreColumnSet = this.getIgnoreColumnSet(tableEls);
+
+ String primaryKeyColumn = GeneratorConst.PRIMARY_KEY_COL;
+ List fixedColumns = new ArrayList<>();
+ if (StringUtils.isBlank(createTimeColumn)) {
+ createTimeColumn = GeneratorConst.CREATED_TIME_COL;
+ }
+ if (StringUtils.isBlank(updateTimeColumn)) {
+ updateTimeColumn = GeneratorConst.UPDATED_TIME_COL;
+ }
+
+ fixedColumns.add(primaryKeyColumn);
+ fixedColumns.add(createTimeColumn);
+ fixedColumns.add(updateTimeColumn);
+
+ xmlConfig.setFixedColumns(fixedColumns);
+ List fixedFields = new ArrayList<>();
+ fixedFields.add(this.getPrimaryKeyField(primaryKeyColumn));
+ fixedFields.add(this.getDateField(createTimeColumn, "创建时间"));
+ fixedFields.add(this.getDateField(updateTimeColumn, "修改时间"));
+ xmlConfig.setFixedField(fixedFields);
+ globalIgnoreColumnSet.addAll(fixedColumns);
+
+ if (allTables) {
+ List tableNames = tableIntrospect.getAllTables();
+ tableNames.forEach(tableName -> {
+ tables.add(CodeGeneratorXmlConfig.Table.builder()
+ .tableName(tableName)
+ .ignoreColumns(globalIgnoreColumnSet)
+ .build());
+ });
+ } else {
+ Iterator iterator = tableEls.elementIterator("table");
+ while (iterator.hasNext()) {
+ Element tableEl = (Element) iterator.next();
+ Set tableIgnoreColumnSet = new HashSet<>(globalIgnoreColumnSet);
+ tableIgnoreColumnSet.addAll(this.getIgnoreColumnSet(tableEl));
+ tables.add(CodeGeneratorXmlConfig.Table.builder()
+ .tableName(this.getAttributeValue(tableEl, "tableName"))
+ .entityObjectName(this.getAttributeValue(tableEl, "entityObjectName", true))
+ .entityObjectSuffix(entityObjectSuffix)
+ .ignoreColumns(tableIgnoreColumnSet)
+ .build());
+ }
+ }
+
+ return tables;
+ }
+
+ private Field getPrimaryKeyField(String primaryKeyColumn) {
+ Field field = new Field();
+ field.setJdbcType(JdbcType.LONG);
+ String lowerCamelName = GeneratorUtils.getLowerCamelName(primaryKeyColumn);
+ if (SourceVersion.isKeyword(lowerCamelName)) {
+ lowerCamelName += GeneratorConst.KEY_WORD_PREFIX;
+ }
+ field.setLowerCamelName(lowerCamelName);
+ field.setUpperCamelName(StringUtils.capitalize(lowerCamelName));
+ field.setColumnName(primaryKeyColumn);
+ field.setLength(20);
+ field.setNullable(true);
+ field.setComment("ID 主键");
+ field.setMarkColumnName(false);
+ field.setPrimaryKey(true);
+ return field;
+ }
+
+ private Field getDateField(String column, String comment) {
+ Field field = new Field();
+ field.setJdbcType(JdbcType.DATE);
+ String lowerCamelName = GeneratorUtils.getLowerCamelName(column);
+ if (SourceVersion.isKeyword(lowerCamelName)) {
+ lowerCamelName += GeneratorConst.KEY_WORD_PREFIX;
+ }
+ field.setLowerCamelName(lowerCamelName);
+ field.setUpperCamelName(StringUtils.capitalize(lowerCamelName));
+ field.setColumnName(column);
+ field.setLength(19);
+ field.setNullable(true);
+ field.setComment(comment);
+ field.setMarkColumnName(!Objects.equals(field.getLowerCamelName(), field.getColumnName()));
+ field.setSwaggerHidden(true);
+ field.setIgnoreSaving(true);
+ return field;
+ }
+
+ private Set getIgnoreColumnSet(Element element) {
+ String ignoreColumns = this.getAttributeValue(element, "ignoreColumns", true);
+ Set ignoreColumnSet = new HashSet<>();
+ if (StringUtils.isNotBlank(ignoreColumns)) {
+ ignoreColumnSet.addAll(CollectionUtils.listOf(ignoreColumns.split(",")));
+ }
+ return ignoreColumnSet;
+ }
+
+ private CodeGeneratorXmlConfig.MapperGeneratorConfig getMapperGeneratorConfig(Element rootElement, String nodeName) {
+ Element element = rootElement.element(nodeName);
+ if (Objects.isNull(element)) {
+ return CodeGeneratorXmlConfig.MapperGeneratorConfig.builder().disabled(true).moduleName(nodeName).build();
+ }
+ String disabledValue = this.getAttributeValue(element, "disabled", true);
+ return CodeGeneratorXmlConfig.MapperGeneratorConfig.builder()
+ .disabled("true".equalsIgnoreCase(disabledValue))
+ .targetPackage(this.getAttributeValue(element, "targetPackage"))
+ .codePath(this.getAttributeValue(element, "codePath"))
+ .templatePath(this.getAttributeValue(element, "templatePath", true))
+ .suffix(this.getAttributeValue(element, "suffix", true))
+ .moduleName(nodeName)
+ .build();
+ }
+
+ private Element getElement(Element element, String name) {
+ Element childElement = this.getNullableElement(element, name);
+ Assert.notNull(childElement, "Element '" + name + "' is not exist");
+ return childElement;
+ }
+
+ private Element getNullableElement(Element element, String name) {
+ return element.element(name);
+ }
+
+ private String getAttributeValueNonBlank(Element element, String attributeName) {
+ String attributeValue = this.getAttributeValue(element, attributeName, false);
+ Assert.isTrue(StringUtils.isNotBlank(attributeValue), "Attribute '" + attributeName + "' is empty string");
+ return attributeValue;
+ }
+
+ private String getAttributeValue(Element element, String attributeName) {
+ return this.getAttributeValue(element, attributeName, false);
+ }
+
+ private String getAttributeValue(Element element, String attributeName, boolean nullable) {
+ Attribute attribute = element.attribute(attributeName);
+ if (!nullable) {
+ Assert.notNull(attribute, "Attribute '" + attributeName + "' is not exist");
+ }
+ return Objects.nonNull(attribute) ? attribute.getValue() : null;
+ }
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorEnum.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorEnum.java
new file mode 100644
index 0000000000000000000000000000000000000000..954775fb1acad9089eb7bff2e3acf578e12b5756
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/core/GeneratorEnum.java
@@ -0,0 +1,61 @@
+package cn.cliveyuan.robin.generator.core;
+
+import cn.cliveyuan.robin.generator.generator.ControllerGenerator;
+import cn.cliveyuan.robin.generator.generator.DTOGenerator;
+import cn.cliveyuan.robin.generator.generator.EntityGenerator;
+import cn.cliveyuan.robin.generator.generator.MapperJavaGenerator;
+import cn.cliveyuan.robin.generator.generator.MapperJavaImplGenerator;
+import cn.cliveyuan.robin.generator.generator.MapperXmlGenerator;
+import cn.cliveyuan.robin.generator.generator.ServiceGenerator;
+import cn.cliveyuan.robin.generator.generator.ServiceImplGenerator;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * 生成器注册枚举类
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+@AllArgsConstructor
+@Getter
+public enum GeneratorEnum {
+
+ ENTITY(EntityGenerator.class, FileType.JAVA, "templates/entity.java.ftl", true, ""),
+ MAPPER_XML(MapperXmlGenerator.class, FileType.XML, "templates/mapper.xml.ftl", false, "Mapper"),
+ MAPPER_JAVA(MapperJavaGenerator.class, FileType.JAVA, "templates/mapper.java.ftl", false, "Mapper"),
+ MAPPER_JAVA_IMPL(MapperJavaImplGenerator.class, FileType.JAVA, "templates/mapperimpl.java.ftl", false, "Mapper"),
+ SERVICE(ServiceGenerator.class, FileType.JAVA, "templates/service.java.ftl", false, "Service"),
+ SERVICE_IMPL(ServiceImplGenerator.class, FileType.JAVA, "templates/serviceimpl.java.ftl", false, "Service"),
+ DTO(DTOGenerator.class, FileType.JAVA, "templates/dto.java.ftl", false, "DTO"),
+ CONTROLLER(ControllerGenerator.class, FileType.JAVA, "templates/controller.java.ftl", false, "Controller");
+
+ /**
+ * 生成器类
+ */
+ private final Class> clazz;
+ /**
+ * 文件类型
+ */
+ private final FileType fileType;
+ /**
+ * 默认模板路径
+ */
+ private final String templatePath;
+ /**
+ * 是否覆盖写入
+ */
+ private final boolean overwrite;
+ /**
+ * 默认后缀
+ */
+ private final String defaultSuffix;
+
+ public static GeneratorEnum valueOf(Generator generator) {
+ return Arrays.stream(GeneratorEnum.values()).filter(x -> Objects.equals(generator.getClass(), x.getClazz()))
+ .findAny().orElseThrow(() -> new IllegalArgumentException("GeneratorEnum not fund: " + generator.getClass()));
+ }
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/ColumnInfo.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/ColumnInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..95b6fd5e32a9d28ef32e97001ce8b7acaeef4c0d
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/ColumnInfo.java
@@ -0,0 +1,33 @@
+package cn.cliveyuan.robin.generator.db;
+
+import lombok.Data;
+
+/**
+ * 列信息
+ *
+ * @author Clive Yuan
+ * @date 2020/10/28
+ */
+@Data
+public class ColumnInfo {
+ /**
+ * 列名
+ */
+ private String name;
+ /**
+ * 类型
+ */
+ private String type;
+ /**
+ * 长度
+ */
+ private Integer length;
+ /**
+ * 能否为空
+ */
+ private Boolean nullable;
+ /**
+ * 备注
+ */
+ private String comment;
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/ConnectionFactory.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/ConnectionFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..6af85ec84a542573af698f40fa68b8518cb218b8
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/ConnectionFactory.java
@@ -0,0 +1,45 @@
+package cn.cliveyuan.robin.generator.db;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+/**
+ * 连接工厂
+ *
+ * @author Clive Yuan
+ * @date 2020/10/28
+ */
+@Slf4j
+public class ConnectionFactory {
+ private static final String QUESTION_MARK = "?";
+ private static final String AND_MARK = "&";
+ private static final String URL_PARAMS = "useInformationSchema=true";
+
+ /**
+ * 创建数据库连接
+ *
+ * @param driver 驱动类
+ * @param url 连接
+ * @param username 用户名
+ * @param password 密码
+ * @return
+ */
+ public static Connection createConnection(String driver, String url, String username, String password) {
+ try {
+ Class.forName(driver);
+ if (url.contains(QUESTION_MARK)) {
+ url += AND_MARK.concat(URL_PARAMS);
+ } else {
+ url += QUESTION_MARK.concat(URL_PARAMS);
+ }
+ return DriverManager.getConnection(url, username, password);
+ } catch (ClassNotFoundException | SQLException e) {
+ log.error("createConnection", e);
+ throw new RuntimeException(String.format("Fail to create connection with driver=%s,url=%s,username=%s,password=%s", driver, url, username, password)); //NOSONAR
+ }
+ }
+
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/JdbcType.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/JdbcType.java
new file mode 100644
index 0000000000000000000000000000000000000000..75858cec7196fb10b1d9df7d612034e47c0f9fd8
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/JdbcType.java
@@ -0,0 +1,44 @@
+package cn.cliveyuan.robin.generator.db;
+
+import lombok.Getter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 类型映射
+ *
+ * doc
+ *
+ * @author Clive Yuan
+ * @date 2020/11/04
+ */
+@Getter
+public enum JdbcType {
+
+ BOOLEAN(Boolean.class, "BOOL", "BOOLEAN"),
+ BYTE_ARRAY(byte[].class, "BIT", "BINARY", "VARBINARY", "TINYBLOB", "BLOB", "MEDIUMBLOB", "LONGBLOB"),
+ INTEGER(Integer.class, "TINYINT", "SMALLINT", "MEDIUMINT", "INT", "INTEGER"),
+ LONG(Long.class, "BIGINT"),
+ DOUBLE(Double.class, "DOUBLE"),
+ FLOAT(Float.class, "FLOAT"),
+ DECIMAL(BigDecimal.class, "DECIMAL"),
+ DATE(Date.class, "DATE", "DATETIME", "TIMESTAMP", "TIME", "YEAR"),
+ STRING(String.class, "ENUM", "SET", "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT"),
+ OBJECT(Object.class, "");
+
+
+ JdbcType(Class> javaType, String... sqlTypes) {
+ this.javaType = javaType;
+ this.sqlTypes = sqlTypes;
+ }
+
+ /**
+ * Java类型
+ */
+ private final Class> javaType;
+ /**
+ * 数据库字段类型
+ */
+ private final String[] sqlTypes;
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/JdbcTypeResolver.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/JdbcTypeResolver.java
new file mode 100644
index 0000000000000000000000000000000000000000..46c29665cb421492f69911eeb2044e6cbf531574
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/JdbcTypeResolver.java
@@ -0,0 +1,39 @@
+package cn.cliveyuan.robin.generator.db;
+
+import cn.cliveyuan.robin.generator.util.CollectionUtils;
+
+import java.util.Arrays;
+
+/**
+ * 类型解析器
+ *
+ * @author Clive Yuan
+ * @date 2020/11/04
+ */
+public class JdbcTypeResolver {
+
+ private static final String UNSIGNED = " UNSIGNED";
+ private static final String BIT = "BIT";
+
+ /**
+ * 解析类型
+ *
+ * @param type jdbc类型
+ * @param length 长度
+ * @return
+ */
+ public static JdbcType resolve(String type, Integer length) {
+ if (type.contains(UNSIGNED)) {
+ type = type.replace(UNSIGNED, "");
+ }
+ String finalType = type;
+ JdbcType jdbcType = Arrays.stream(JdbcType.values())
+ .filter(x -> CollectionUtils.listOf(x.getSqlTypes()).contains(finalType))
+ .findAny()
+ .orElse(JdbcType.OBJECT);
+ if (BIT.equals(finalType) && length == 1) {
+ jdbcType = JdbcType.BOOLEAN;
+ }
+ return jdbcType;
+ }
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/MysqlTableIntrospect.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/MysqlTableIntrospect.java
new file mode 100644
index 0000000000000000000000000000000000000000..b75a3f61a79f29adcd80dc1e8bf3427a428f385a
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/MysqlTableIntrospect.java
@@ -0,0 +1,98 @@
+package cn.cliveyuan.robin.generator.db;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * MySQL 表信息探测器
+ *
+ * @author Clive Yuan
+ * @date 2020/10/28
+ */
+@Slf4j
+public class MysqlTableIntrospect implements TableIntrospect {
+
+ private final Connection connection;
+
+ public MysqlTableIntrospect(Connection connection) {
+ this.connection = connection;
+ }
+
+ @Override
+ public TableInfo introspect(String tableName) {
+ try {
+ TableInfo tableInfo = new TableInfo();
+ tableInfo.setName(tableName);
+ DatabaseMetaData metaData = connection.getMetaData();
+ // introspect table info
+ try (ResultSet tableRs = metaData.getTables(connection.getCatalog(), null, tableName, null)) {
+ if (tableRs.next()) {
+ String remarks = tableRs.getString("REMARKS");
+ if (StringUtils.isBlank(remarks)) {
+ remarks = tableName;
+ }
+ tableInfo.setComment(remarks);
+ }
+ }
+ List columns = new ArrayList();
+ tableInfo.setColumns(columns);
+
+ // introspect column info
+ try (ResultSet columnRs = metaData.getColumns(connection.getCatalog(), null, tableName, null)) {
+ while (columnRs.next()) {
+ String columnName = columnRs.getString("COLUMN_NAME");
+ String typeName = columnRs.getString("TYPE_NAME");
+ int columnSize = columnRs.getInt("COLUMN_SIZE");
+ int nullable = columnRs.getInt("NULLABLE");
+ String comment = columnRs.getString("REMARKS");
+ ColumnInfo column = new ColumnInfo();
+ column.setName(columnName);
+ column.setType(typeName);
+ column.setLength(columnSize);
+ column.setNullable(nullable == 1);
+ column.setComment(StringUtils.isNotBlank(comment) ? comment : columnName);
+ columns.add(column);
+ }
+ }
+ return tableInfo;
+ } catch (SQLException e) {
+ log.error("introspect", e);
+ throw new RuntimeException("Fail to introspect table: " + tableName);
+ }
+ }
+
+ @Override
+ public List getAllTables() {
+ List list = new ArrayList<>();
+ try {
+ DatabaseMetaData metaData = connection.getMetaData();
+ ResultSet tableRs = metaData.getTables(connection.getCatalog(), null, null, null);
+ while (tableRs.next()) {
+ list.add(tableRs.getString("TABLE_NAME"));
+ }
+ } catch (SQLException e) {
+ log.error("introspect", e);
+ throw new RuntimeException(e);
+ }
+ return list;
+ }
+
+ @Override
+ public void closeConnection() {
+ if (Objects.nonNull(connection)) {
+ try {
+ connection.close();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/TableInfo.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/TableInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..1c5726727a0b881187c84d9e0de5db56ef98a68c
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/TableInfo.java
@@ -0,0 +1,28 @@
+package cn.cliveyuan.robin.generator.db;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 表信息
+ *
+ * @author Clive Yuan
+ * @date 2020/10/28
+ */
+@Data
+public class TableInfo {
+ /**
+ * 名称
+ */
+ private String name;
+ /**
+ * 备注
+ */
+ private String comment;
+ /**
+ * 字段列表
+ */
+ private List columns;
+
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/TableIntrospect.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/TableIntrospect.java
new file mode 100644
index 0000000000000000000000000000000000000000..964fd16cca395452e983ba810d9544734707a118
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/db/TableIntrospect.java
@@ -0,0 +1,32 @@
+package cn.cliveyuan.robin.generator.db;
+
+import java.util.List;
+
+/**
+ * 表信息探测器
+ *
+ * @author Clive Yuan
+ * @date 2020/10/28
+ */
+public interface TableIntrospect {
+
+ /**
+ * 探测表信息
+ *
+ * @param tableName 表名
+ * @return
+ */
+ TableInfo introspect(String tableName);
+
+ /**
+ * 获取所有表名
+ *
+ * @return
+ */
+ List getAllTables();
+
+ /**
+ * 关闭数据库连接
+ */
+ void closeConnection();
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/ControllerGenerator.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/ControllerGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..903bf13d3d148edf5778a611df19b21850735671
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/ControllerGenerator.java
@@ -0,0 +1,23 @@
+package cn.cliveyuan.robin.generator.generator;
+
+import cn.cliveyuan.robin.generator.core.GenerateParam;
+import cn.cliveyuan.robin.generator.core.Generator;
+import cn.cliveyuan.robin.generator.core.GeneratorChain;
+import cn.cliveyuan.robin.generator.core.GeneratorContext;
+
+/**
+ * 控制器 生成器
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+public class ControllerGenerator implements Generator {
+ @Override
+ public void generate(GeneratorContext context, GeneratorChain generatorChain) {
+ generatorChain.doGenerate(GenerateParam.builder()
+ .generator(this)
+ .context(context)
+ .generatorConfig(context.getXmlConfig().getControllerGenerator())
+ .build());
+ }
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/DTOGenerator.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/DTOGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..fbc1b1ade739781f80a5ce3f848d41cbe0d5b5e1
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/DTOGenerator.java
@@ -0,0 +1,31 @@
+package cn.cliveyuan.robin.generator.generator;
+
+import cn.cliveyuan.robin.generator.core.CodeGeneratorXmlConfig;
+import cn.cliveyuan.robin.generator.core.GenerateParam;
+import cn.cliveyuan.robin.generator.core.Generator;
+import cn.cliveyuan.robin.generator.core.GeneratorChain;
+import cn.cliveyuan.robin.generator.core.GeneratorContext;
+
+/**
+ * DTO 生成器
+ *
+ * @author Clive Yuan
+ * @date 2020/12/07
+ */
+public class DTOGenerator implements Generator {
+
+ @Override
+ public void generate(GeneratorContext context, GeneratorChain generatorChain) {
+ CodeGeneratorXmlConfig.MapperGeneratorConfig controllerGenerator = context.getXmlConfig().getControllerGenerator();
+ CodeGeneratorXmlConfig.MapperGeneratorConfig dtoGenerator = context.getXmlConfig().getDtoGenerator();
+ // dto 依附于controller, 若不生成controller, 则不生成dto
+ dtoGenerator.setDisabled(controllerGenerator.isDisabled());
+ generatorChain.doGenerate(GenerateParam.builder()
+ .generator(this)
+ .context(context)
+ .generatorConfig(dtoGenerator)
+ .build());
+ }
+
+
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/EntityGenerator.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/EntityGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ae588c89900a783a8013cba2503b976bba7e265
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/EntityGenerator.java
@@ -0,0 +1,28 @@
+package cn.cliveyuan.robin.generator.generator;
+
+import cn.cliveyuan.robin.generator.core.Entity;
+import cn.cliveyuan.robin.generator.core.GenerateParam;
+import cn.cliveyuan.robin.generator.core.Generator;
+import cn.cliveyuan.robin.generator.core.GeneratorChain;
+import cn.cliveyuan.robin.generator.core.GeneratorContext;
+
+/**
+ * 实体 生成器
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+public class EntityGenerator implements Generator {
+
+ @Override
+ public void generate(GeneratorContext context, GeneratorChain generatorChain) {
+ generatorChain.doGenerate(GenerateParam.builder()
+ .generator(this)
+ .context(context)
+ .fileNameFunction(Entity::getEntityName)
+ .generatorConfig(context.getXmlConfig().getJavaModelGenerator())
+ .build());
+ }
+
+
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/MapperJavaGenerator.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/MapperJavaGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..dfc76598321ad78adee2c859dff28289a8498aef
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/MapperJavaGenerator.java
@@ -0,0 +1,23 @@
+package cn.cliveyuan.robin.generator.generator;
+
+import cn.cliveyuan.robin.generator.core.GenerateParam;
+import cn.cliveyuan.robin.generator.core.Generator;
+import cn.cliveyuan.robin.generator.core.GeneratorChain;
+import cn.cliveyuan.robin.generator.core.GeneratorContext;
+
+/**
+ * Java Mapper 生成器
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+public class MapperJavaGenerator implements Generator {
+ @Override
+ public void generate(GeneratorContext context, GeneratorChain generatorChain) {
+ generatorChain.doGenerate(GenerateParam.builder()
+ .generator(this)
+ .context(context)
+ .generatorConfig(context.getXmlConfig().getJavaClientGenerator())
+ .build());
+ }
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/MapperJavaImplGenerator.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/MapperJavaImplGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..74b9c34c844343cf64c37b16148c99ba363b034b
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/MapperJavaImplGenerator.java
@@ -0,0 +1,34 @@
+package cn.cliveyuan.robin.generator.generator;
+
+import cn.cliveyuan.robin.generator.core.CodeGeneratorXmlConfig;
+import cn.cliveyuan.robin.generator.core.GenerateParam;
+import cn.cliveyuan.robin.generator.core.Generator;
+import cn.cliveyuan.robin.generator.core.GeneratorChain;
+import cn.cliveyuan.robin.generator.core.GeneratorConst;
+import cn.cliveyuan.robin.generator.core.GeneratorContext;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * Java MapperImpl 生成器
+ *
+ * @author Clive Yuan
+ * @date 2020/12/07
+ */
+public class MapperJavaImplGenerator implements Generator {
+ @Override
+ public void generate(GeneratorContext context, GeneratorChain generatorChain) {
+ CodeGeneratorXmlConfig.MapperGeneratorConfig mapperGenerator = context.getXmlConfig().getJavaClientGenerator();
+ CodeGeneratorXmlConfig.MapperGeneratorConfig mapperImplGenerator = CodeGeneratorXmlConfig.MapperGeneratorConfig.builder().build();
+ BeanUtils.copyProperties(mapperGenerator, mapperImplGenerator);
+ mapperImplGenerator.setTargetPackage(mapperGenerator.getTargetPackage() + GeneratorConst.IMPL_PKG_SUFFIX);
+ mapperImplGenerator.setModuleName("mapperImplGenerator");
+ mapperImplGenerator.setDisabled(!context.getXmlConfig().getBaseConfig().isEnableReadWriteSeparation());
+ String suffix = generatorChain.getSuffix(mapperGenerator, this);
+ generatorChain.doGenerate(GenerateParam.builder()
+ .generator(this)
+ .context(context)
+ .generatorConfig(mapperImplGenerator)
+ .fileNameFunction(x -> x.getUpperCamelName() + suffix + "Impl")
+ .build());
+ }
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/MapperXmlGenerator.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/MapperXmlGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..25838109185e3adc96a2ac9372c32984ba98ef23
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/MapperXmlGenerator.java
@@ -0,0 +1,64 @@
+package cn.cliveyuan.robin.generator.generator;
+
+import cn.cliveyuan.robin.generator.core.ExtensionParam;
+import cn.cliveyuan.robin.generator.core.GenerateParam;
+import cn.cliveyuan.robin.generator.core.Generator;
+import cn.cliveyuan.robin.generator.core.GeneratorChain;
+import cn.cliveyuan.robin.generator.core.GeneratorContext;
+import cn.cliveyuan.robin.generator.core.GeneratorEnum;
+import cn.cliveyuan.robin.generator.util.FileContentModifyUtils;
+import cn.cliveyuan.robin.generator.util.FreemarkerUtils;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * Xml Mapper 生成器
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+public class MapperXmlGenerator implements Generator {
+ @Override
+ public void generate(GeneratorContext context, GeneratorChain generatorChain) {
+ generatorChain.doGenerate(GenerateParam.builder()
+ .generator(this)
+ .context(context)
+ .generatorConfig(context.getXmlConfig().getSqlMapGenerator())
+ .beforeExtensionHandle(this::modifyExistMapperXml)
+ .build());
+ }
+
+ private void modifyExistMapperXml(ExtensionParam extensionParam) {
+ extensionParam.getContext().getEntityList().forEach(entity -> {
+ String fileName = entity.getFileName() + GeneratorEnum.MAPPER_XML.getFileType().getExtension();
+ String filePath = String.format("%s/%s", extensionParam.getCodeFilePath(), fileName);
+ // 判断文件是否存在
+ File file = new File(filePath);
+ if (!file.exists()) {
+ return;
+ }
+ Map paramMap = extensionParam.getParamMap();
+ paramMap.put("entity", entity);
+
+ // 修改BaseColumnList
+ this.modifyBaseColumnList(paramMap, filePath);
+ // 修改BaseResultMap
+ this.modifyBaseResultMap(paramMap, filePath);
+ });
+ }
+
+ private void modifyBaseColumnList(Map paramMap, String filePath) {
+ this.modifyContent(paramMap, filePath, "templates/segment/base_column_list.inc.ftl", "", "");
+ }
+
+ private void modifyBaseResultMap(Map paramMap, String filePath) {
+ this.modifyContent(paramMap, filePath, "templates/segment/base_result_map.inc.ftl", "");
+ }
+
+ private void modifyContent(Map paramMap, String filePath, String ftlPath, String start, String end) {
+ String codeContent = FreemarkerUtils.parseTemplate(ftlPath, paramMap);
+ FileContentModifyUtils.modify(filePath, start, end, String.format(" %s", codeContent.trim()));
+ }
+
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/ServiceGenerator.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/ServiceGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..de8fe10cf82bea7078bfc51f5f70199114ab0da2
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/ServiceGenerator.java
@@ -0,0 +1,23 @@
+package cn.cliveyuan.robin.generator.generator;
+
+import cn.cliveyuan.robin.generator.core.GenerateParam;
+import cn.cliveyuan.robin.generator.core.Generator;
+import cn.cliveyuan.robin.generator.core.GeneratorChain;
+import cn.cliveyuan.robin.generator.core.GeneratorContext;
+
+/**
+ * Service 生成器
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+public class ServiceGenerator implements Generator {
+ @Override
+ public void generate(GeneratorContext context, GeneratorChain generatorChain) {
+ generatorChain.doGenerate(GenerateParam.builder()
+ .generator(this)
+ .context(context)
+ .generatorConfig(context.getXmlConfig().getServiceGenerator())
+ .build());
+ }
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/ServiceImplGenerator.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/ServiceImplGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..cfb123fc29e36a89e4169fd2e5779a56416de1ef
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/generator/ServiceImplGenerator.java
@@ -0,0 +1,33 @@
+package cn.cliveyuan.robin.generator.generator;
+
+import cn.cliveyuan.robin.generator.core.CodeGeneratorXmlConfig;
+import cn.cliveyuan.robin.generator.core.GenerateParam;
+import cn.cliveyuan.robin.generator.core.Generator;
+import cn.cliveyuan.robin.generator.core.GeneratorChain;
+import cn.cliveyuan.robin.generator.core.GeneratorConst;
+import cn.cliveyuan.robin.generator.core.GeneratorContext;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * Service 实现 生成器
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+public class ServiceImplGenerator implements Generator {
+ @Override
+ public void generate(GeneratorContext context, GeneratorChain generatorChain) {
+ CodeGeneratorXmlConfig.MapperGeneratorConfig serviceGenerator = context.getXmlConfig().getServiceGenerator();
+ CodeGeneratorXmlConfig.MapperGeneratorConfig serviceImplGenerator = CodeGeneratorXmlConfig.MapperGeneratorConfig.builder().build();
+ BeanUtils.copyProperties(serviceGenerator, serviceImplGenerator);
+ serviceImplGenerator.setTargetPackage(serviceGenerator.getTargetPackage() + GeneratorConst.IMPL_PKG_SUFFIX);
+ serviceImplGenerator.setModuleName("serviceImplGenerator");
+ String suffix = generatorChain.getSuffix(serviceGenerator, this);
+ generatorChain.doGenerate(GenerateParam.builder()
+ .generator(this)
+ .context(context)
+ .generatorConfig(serviceImplGenerator)
+ .fileNameFunction(x -> x.getUpperCamelName() + suffix + "Impl")
+ .build());
+ }
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/impl/MybatisGenerator.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/impl/MybatisGenerator.java
index 244619b095e97ace3591d4cfc4d63b04afdf8af5..0f2911d3483fbad738bab88c4ee5156058b963fa 100644
--- a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/impl/MybatisGenerator.java
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/impl/MybatisGenerator.java
@@ -1,16 +1,40 @@
package cn.cliveyuan.robin.generator.impl;
import cn.cliveyuan.robin.generator.RobinGenerator;
+import cn.cliveyuan.robin.generator.core.GeneratorChain;
+import cn.cliveyuan.robin.generator.core.GeneratorContext;
+import cn.cliveyuan.robin.generator.core.GeneratorContextResolver;
+import lombok.Builder;
+import lombok.extern.slf4j.Slf4j;
/**
* @author Clive Yuan
* @date 2020/12/23
*/
+@Slf4j
public class MybatisGenerator implements RobinGenerator {
+ private final String configFilePath;
+
+ @Builder
+ public MybatisGenerator(String configFilePath) {
+ this.configFilePath = configFilePath;
+ }
+
@Override
public void generate() {
- // TODO: implement me
- System.out.println("Hello RobinGenerator");
+ log.info("MybatisGenerator.generate [START] customized configFilePath is {}", configFilePath);
+
+ long start = System.currentTimeMillis();
+ // 初始化上下文解析器
+ GeneratorContextResolver generatorContextResolver = new GeneratorContextResolver();
+ // 解析配置上下文
+ GeneratorContext generatorContext = generatorContextResolver.resolve(configFilePath);
+ // 创建生成链路
+ GeneratorChain generatorChain = new GeneratorChain();
+ // 执行生成链路
+ generatorChain.generate(generatorContext, generatorChain);
+
+ log.info("MybatisGenerator.generate [SUCCESS] cost {}ms", System.currentTimeMillis() - start);
}
}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/CollectionUtils.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/CollectionUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..9dd473e0cdc0768f1e637bcef0f57ae2cc4bf968
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/CollectionUtils.java
@@ -0,0 +1,33 @@
+package cn.cliveyuan.robin.generator.util;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author Clive Yuan
+ * @date 2020/12/24
+ */
+public class CollectionUtils {
+
+
+ public static List listOf(E... elements) {
+ checkNotNull(elements);
+ return Arrays.asList(elements);
+ }
+
+ private static T checkNotNull(T reference) {
+ if (reference == null) {
+ throw new NullPointerException();
+ }
+ return reference;
+ }
+
+ public static boolean isEmpty(final Collection> coll) {
+ return coll == null || coll.isEmpty();
+ }
+
+ public static boolean isNotEmpty(final Collection> coll) {
+ return !isEmpty(coll);
+ }
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/FileContentModifyUtils.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/FileContentModifyUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..b4ea5a8fc8c3beffcb7d2656c670f05665b97b44
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/FileContentModifyUtils.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020 Clive Yuan (cliveyuan@foxmail.com).
+ */
+
+package cn.cliveyuan.robin.generator.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 文件内容修改工具
+ *
+ * @author Clive Yuan
+ * @date 2021/01/06
+ */
+@Slf4j
+public class FileContentModifyUtils {
+
+ public static void modify(String fileName, String startMark, String endMark, String content) {
+ try {
+ List newLines = new ArrayList<>();
+ List lines = Files.readAllLines(Paths.get(fileName), StandardCharsets.UTF_8);
+ int start = -1;
+ int end = -1;
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines.get(i).trim();
+ if (line.startsWith(startMark)) {
+ start = i;
+ }
+ if (line.contains(endMark)) {
+ end = i;
+ if (start >= 0) {
+ break;
+ }
+ }
+ }
+ log.info("FileContentModifyUtils.modify start={}, end={}", start, end);
+ if (start >= 0 && end >= 0) {
+ for (int i = 0; i < lines.size(); i++) {
+ if (i < start || i > end) {
+ newLines.add(lines.get(i));
+ }
+ }
+
+ newLines.add(start, content);
+ Files.write(Paths.get(fileName), newLines, StandardCharsets.UTF_8);
+ } else {
+ log.info("FileContentModifyUtils.modify can't find the segment to modify");
+ }
+ } catch (IOException e) {
+ log.error("FileContentModifyUtils.modify error", e);
+ }
+ }
+
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/FreemarkerUtils.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/FreemarkerUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..5fa7fc6964b4fdc59d10c521ed4b13d4c9310082
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/FreemarkerUtils.java
@@ -0,0 +1,58 @@
+package cn.cliveyuan.robin.generator.util;
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * ftl解析工具
+ *
+ * @author Clive Yuan
+ * @date 2020-11-07
+ */
+public class FreemarkerUtils {
+
+ private static final Logger logger = LoggerFactory.getLogger(FreemarkerUtils.class);
+ private static final String ENCODING = "UTF-8";
+
+ /**
+ * 解析模板
+ *
+ * @param filePath 模板文件路径 (resources下的路径)
+ * @param dataModelMap 模板中的变量和值
+ * @return 返回解析后的内容
+ */
+ public static String parseTemplate(String filePath, Map dataModelMap) {
+ try {
+ int index = filePath.lastIndexOf('/');
+ String basePackagePath = filePath.substring(0, index + 1);
+ String fileName = filePath.substring(index + 1);
+ Template template = getTemplateByName(basePackagePath, fileName);
+ StringWriter strWriter = new StringWriter();
+ template.setOutputEncoding(ENCODING);
+ template.process(dataModelMap, strWriter);
+ return strWriter.toString();
+ } catch (Exception e) {
+ logger.error("parseTemplate Exc", e);
+ throw new IllegalArgumentException("freemarker 模板解析失败");
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private static Template getTemplateByName(String basePackagePath, String fileName) throws IOException {
+ Configuration configuration = new Configuration();
+ configuration.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
+ configuration.setClassLoaderForTemplateLoading(FreemarkerUtils.class.getClassLoader(), basePackagePath);
+ configuration.setEncoding(Locale.ENGLISH, ENCODING);
+ configuration.setDefaultEncoding(ENCODING);
+ configuration.setOutputEncoding(ENCODING);
+ return configuration.getTemplate(fileName);
+ }
+
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/GeneratorUtils.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/GeneratorUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd289ce1c8a2359ccf18baf35be9fc1a1a86f00d
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/GeneratorUtils.java
@@ -0,0 +1,46 @@
+package cn.cliveyuan.robin.generator.util;
+
+import com.google.common.base.CaseFormat;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Objects;
+
+/**
+ * 生成工具类
+ *
+ * @author Clive Yuan
+ * @date 2020/09/09
+ */
+public class GeneratorUtils {
+
+ /**
+ * 获取小写驼峰名
+ *
+ * user -> user
+ * User -> user
+ * USER -> user
+ * UsER -> usER
+ * TemplateEnterprise -> templateEnterprise
+ * t_user_info -> tUserInfo
+ * user_list_detail -> userListDetail
+ * USER_LIST_DETAIL -> userListDetail
+ *
+ * @param str 字符串
+ * @return
+ */
+ public static String getLowerCamelName(String str) {
+ // 全大写, 包含下划线
+ Objects.requireNonNull(str, "str can't be null");
+ if (str.contains("_")) {
+ return StringUtils.uncapitalize(lineToHump(str));
+ }
+ if (StringUtils.isAllUpperCase(str)) {
+ return str.toLowerCase();
+ }
+ return StringUtils.uncapitalize(str);
+ }
+
+ private static String lineToHump(String str) {
+ return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, str.toUpperCase());
+ }
+}
diff --git a/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/ReflectUtils.java b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/ReflectUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..369852481e68d7f82c301bae5233bfb25724495f
--- /dev/null
+++ b/robin-generator/src/main/java/cn/cliveyuan/robin/generator/util/ReflectUtils.java
@@ -0,0 +1,25 @@
+package cn.cliveyuan.robin.generator.util;
+
+/**
+ * 反射工具类
+ *
+ * @author Clive Yuan
+ * @date 2020/11/05
+ */
+public class ReflectUtils {
+
+ /**
+ * 初始化
+ *
+ * @param clazz 类
+ * @param 泛型
+ * @return
+ */
+ public static T newInstance(Class> clazz) {
+ try {
+ return (T)clazz.getDeclaredConstructor().newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/robin-generator/src/main/resources/templates/controller.java.ftl b/robin-generator/src/main/resources/templates/controller.java.ftl
new file mode 100644
index 0000000000000000000000000000000000000000..c7d98740126b7cf6c7129558ffad0ea8de1f3f3f
--- /dev/null
+++ b/robin-generator/src/main/resources/templates/controller.java.ftl
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2007-${.now?string('yyyy')}, CIIC Guanaitong, Co., Ltd.
+ * All rights reserved.
+ */
+
+package ${controllerPackage};
+
+import ${entityPackage}.${entity.entityName};
+import ${dtoPackage}.${entity.upperCamelName}${dtoSuffix};
+import ${servicePackage}.${entity.upperCamelName}${serviceSuffix};
+import cn.cliveyuan.robin.base.condition.PageQueryExample;
+import cn.cliveyuan.robin.base.common.PageQueryRequest;
+import cn.cliveyuan.robin.base.util.BeanCopyUtils;
+import cn.cliveyuan.robin.base.common.ApiResponse;
+import cn.cliveyuan.robin.base.common.Pagination;
+
+[#if baseConfig.enableSwagger]
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+[/#if]
+[#if baseConfig.enableValidation]
+import org.springframework.validation.annotation.Validated;
+[/#if]
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * ${entity.comment} 控制器
+ */
+@RestController
+@RequestMapping("/${entity.lowerCamelName}")
+[#if baseConfig.enableSwagger]
+@Api(tags = "${entity.comment}接口")
+[/#if]
+public class ${entity.fileName} {
+
+ @Resource
+ private ${entity.upperCamelName}${serviceSuffix} ${entity.lowerCamelName}${serviceSuffix};
+
+ @PostMapping("save")
+[#if baseConfig.enableSwagger]
+ @ApiOperation("保存")
+[/#if]
+ public ApiResponse save([#if baseConfig.enableValidation]@Validated[/#if] ${entity.upperCamelName}${dtoSuffix} request) {
+ ${entity.entityName} entity = BeanCopyUtils.copy(request, ${entity.entityName}.class);
+ ${entity.lowerCamelName}${serviceSuffix}.save(entity);
+ return ApiResponse.success(entity.getId());
+ }
+
+ @PostMapping("delete")
+[#if baseConfig.enableSwagger]
+ @ApiOperation("删除")
+[/#if]
+ public ApiResponse delete(Long id) {
+ return ApiResponse.success(${entity.lowerCamelName}${serviceSuffix}.delete(id) > 0);
+ }
+
+ @PostMapping("get")
+[#if baseConfig.enableSwagger]
+ @ApiOperation("获取")
+[/#if]
+ public ApiResponse<${entity.upperCamelName}${dtoSuffix}> get(Long id) {
+ return ApiResponse.success(BeanCopyUtils.copy(${entity.lowerCamelName}${serviceSuffix}.get(id), ${entity.upperCamelName}${dtoSuffix}.class));
+ }
+
+ @PostMapping("page")
+[#if baseConfig.enableSwagger]
+ @ApiOperation("分页查询")
+[/#if]
+ public ApiResponse> page(@RequestBody PageQueryRequest<${entity.upperCamelName}${dtoSuffix}> request) {
+ PageQueryExample<${entity.entityName}> query = new PageQueryExample<>();
+ query.setPageNo(request.getPageNo());
+ query.setPageSize(request.getPageSize());
+ if (request.getEntity() != null) {
+ query.setEntity(BeanCopyUtils.copy(request.getEntity(), ${entity.entityName}.class));
+ }
+ return ApiResponse.success(BeanCopyUtils.copyPagination(${entity.lowerCamelName}${serviceSuffix}.page(query), ${entity.upperCamelName}${dtoSuffix}.class));
+ }
+}
diff --git a/robin-generator/src/main/resources/templates/dto.java.ftl b/robin-generator/src/main/resources/templates/dto.java.ftl
new file mode 100644
index 0000000000000000000000000000000000000000..c1b174ff0271b51a0e2cf4ab5d2578aff1b2f6ae
--- /dev/null
+++ b/robin-generator/src/main/resources/templates/dto.java.ftl
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2007-${.now?string('yyyy')}, CIIC Guanaitong, Co., Ltd.
+ * All rights reserved.
+ */
+
+package ${dtoPackage};
+
+[#if baseConfig.enableSwagger]
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+[/#if]
+[#if baseConfig.enableValidation]
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotNull;
+[/#if]
+import java.io.Serializable;
+import java.util.Date;
+[#if baseConfig.enableLombok]
+import lombok.Data;
+[/#if]
+
+[#if entity.hasBigDecimalField]
+import java.math.BigDecimal;
+[/#if]
+
+
+/**
+ * ${entity.comment}
+ */
+[#if baseConfig.enableLombok]
+@Data
+[/#if]
+[#if baseConfig.enableSwagger]
+@ApiModel(value = "${entity.comment}")
+[/#if]
+public class ${entity.fileName} implements Serializable {
+
+[#--fields--]
+ private static final long serialVersionUID = 1L;
+
+[#list entity.fields as field]
+ /**
+ * ${field.comment}
+ */
+ [#if baseConfig.enableSwagger]
+ @ApiModelProperty(value = "${field.comment}"[#if field.swaggerHidden], hidden = true[/#if])
+ [/#if]
+ [#if baseConfig.enableValidation]
+ [#if !field.nullable]
+ @NotNull(message = "${field.comment}不能为空")
+ [/#if]
+ [#if field.jdbcType.javaType.simpleName == 'String']
+ [#assign fieldLength = field.length?string('#')]
+ @Length(max = ${fieldLength}, message = "${field.comment}长度不能超过${fieldLength}个字符")
+ [/#if]
+ [/#if]
+ private ${field.jdbcType.javaType.simpleName} ${field.lowerCamelName};
+
+[/#list]
+[#-- getters & setters --]
+[#if !baseConfig.enableLombok]
+[#list entity.fields as field]
+ public ${field.jdbcType.javaType.simpleName} get${field.upperCamelName}() {
+ return ${field.lowerCamelName};
+ }
+
+ public void set${field.upperCamelName}(${field.jdbcType.javaType.simpleName} ${field.lowerCamelName}) {
+ this.${field.lowerCamelName} = ${field.lowerCamelName};
+ }
+
+[/#list]
+[#--toString--]
+ @Override
+ public String toString() {
+ return "${entity.entityName}{" +
+ [#list entity.fields as field]
+ "[#if field_index !=0], [/#if]${field.lowerCamelName}=" + ${field.lowerCamelName} +
+ [/#list]
+ '}';
+ }
+[/#if]
+}
diff --git a/robin-generator/src/main/resources/templates/entity.java.ftl b/robin-generator/src/main/resources/templates/entity.java.ftl
new file mode 100644
index 0000000000000000000000000000000000000000..5b2e612c6cfc0b189a362527d71cf782745ca360
--- /dev/null
+++ b/robin-generator/src/main/resources/templates/entity.java.ftl
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2007-${.now?string('yyyy')}, CIIC Guanaitong, Co., Ltd.
+ * All rights reserved.
+ */
+
+package ${entityPackage};
+
+import cn.cliveyuan.robin.base.annotation.TableField;
+import cn.cliveyuan.robin.base.annotation.TableId;
+import cn.cliveyuan.robin.base.annotation.TableName;
+
+import java.io.Serializable;
+[#if entity.hasBigDecimalField]
+import java.math.BigDecimal;
+[/#if]
+import java.util.Date;
+[#if baseConfig.enableLombok]
+
+import lombok.Data;
+[/#if]
+
+/**
+ * ${entity.comment}
+ */
+[#if baseConfig.enableLombok]
+@Data
+[/#if]
+@TableName("${entity.tableName}")
+public class ${entity.entityName} implements Serializable {
+
+[#--fields--]
+ private static final long serialVersionUID = 1L;
+
+[#list entity.fields as field]
+ /**
+ * ${field.comment}
+ */
+ [#if field.markColumnName || field.ignoreSaving]
+ @TableField([#if field.markColumnName]value = "${field.columnName}"[/#if][#if field.markColumnName && field.ignoreSaving], [/#if][#if field.ignoreSaving]ignoreSaving = true[/#if])
+ [/#if]
+ [#if field.primaryKey]
+ @TableId
+ [/#if]
+ private ${field.jdbcType.javaType.simpleName} ${field.lowerCamelName};
+
+[/#list]
+[#-- getters & setters --]
+[#if !baseConfig.enableLombok]
+[#list entity.fields as field]
+ public ${field.jdbcType.javaType.simpleName} get${field.upperCamelName}() {
+ return ${field.lowerCamelName};
+ }
+
+ public void set${field.upperCamelName}(${field.jdbcType.javaType.simpleName} ${field.lowerCamelName}) {
+ this.${field.lowerCamelName} = ${field.lowerCamelName};
+ }
+
+[/#list]
+[#--toString--]
+ @Override
+ public String toString() {
+ return "${entity.entityName}{" +
+ [#list entity.fields as field]
+ "[#if field_index !=0], [/#if]${field.lowerCamelName}=" + ${field.lowerCamelName} +
+ [/#list]
+ '}';
+ }
+[/#if]
+}
diff --git a/robin-generator/src/main/resources/templates/mapper.java.ftl b/robin-generator/src/main/resources/templates/mapper.java.ftl
new file mode 100644
index 0000000000000000000000000000000000000000..7b939c6083c6efd9a04df01d71236a88c70ff176
--- /dev/null
+++ b/robin-generator/src/main/resources/templates/mapper.java.ftl
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2007-${.now?string('yyyy')}, CIIC Guanaitong, Co., Ltd.
+ * All rights reserved.
+ */
+
+package ${mapperPackage};
+
+import ${entityPackage}.${entity.entityName};
+import cn.cliveyuan.robin.base.BaseMapper;
+
+/**
+ * ${entity.comment} Mapper
+ */
+public interface ${entity.fileName} extends BaseMapper<${entity.entityName}> {
+
+}
diff --git a/robin-generator/src/main/resources/templates/mapper.xml.ftl b/robin-generator/src/main/resources/templates/mapper.xml.ftl
new file mode 100644
index 0000000000000000000000000000000000000000..323a6aabd49daf3c44a7ec423104641582a627fd
--- /dev/null
+++ b/robin-generator/src/main/resources/templates/mapper.xml.ftl
@@ -0,0 +1,13 @@
+
+
+
+
+
+ [#include 'segment/base_column_list.inc.ftl']
+
+ [#include 'segment/base_result_map.inc.ftl']
+
+
diff --git a/robin-generator/src/main/resources/templates/segment/base_column_list.inc.ftl b/robin-generator/src/main/resources/templates/segment/base_column_list.inc.ftl
new file mode 100644
index 0000000000000000000000000000000000000000..00f67cce4b843e1308de690a40c99662104d739c
--- /dev/null
+++ b/robin-generator/src/main/resources/templates/segment/base_column_list.inc.ftl
@@ -0,0 +1,3 @@
+
+ [#list entity.fields as field]`${field.columnName}`[#if field_has_next],[/#if][/#list]
+
diff --git a/robin-generator/src/main/resources/templates/segment/base_result_map.inc.ftl b/robin-generator/src/main/resources/templates/segment/base_result_map.inc.ftl
new file mode 100644
index 0000000000000000000000000000000000000000..272dc5d2cc73468fc1fdd8892afaa9ecb7a90791
--- /dev/null
+++ b/robin-generator/src/main/resources/templates/segment/base_result_map.inc.ftl
@@ -0,0 +1,9 @@
+
+[#list entity.fields as field]
+ [#if field.columnName == 'id']
+
+ [#else ]
+
+ [/#if]
+[/#list]
+
diff --git a/robin-generator/src/main/resources/templates/service.java.ftl b/robin-generator/src/main/resources/templates/service.java.ftl
new file mode 100644
index 0000000000000000000000000000000000000000..dcf8af008b804eb69ec7bbfbbe7882d1d4a23a95
--- /dev/null
+++ b/robin-generator/src/main/resources/templates/service.java.ftl
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2007-${.now?string('yyyy')}, CIIC Guanaitong, Co., Ltd.
+ * All rights reserved.
+ */
+
+package ${servicePackage};
+
+import cn.cliveyuan.robin.base.BaseService;
+import ${entityPackage}.${entity.entityName};
+
+/**
+ * ${entity.comment} 服务接口
+ */
+public interface ${entity.fileName} extends BaseService<${entity.entityName}> {
+
+}
diff --git a/robin-generator/src/main/resources/templates/serviceimpl.java.ftl b/robin-generator/src/main/resources/templates/serviceimpl.java.ftl
new file mode 100644
index 0000000000000000000000000000000000000000..d5b086a04b9592e94f8eacffa29ec68c10220a68
--- /dev/null
+++ b/robin-generator/src/main/resources/templates/serviceimpl.java.ftl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2007-${.now?string('yyyy')}, CIIC Guanaitong, Co., Ltd.
+ * All rights reserved.
+ */
+
+package ${serviceImplPackage};
+
+import cn.cliveyuan.robin.base.BaseServiceImpl;
+import ${entityPackage}.${entity.entityName};
+import ${servicePackage}.${entity.upperCamelName}${serviceSuffix};
+import org.springframework.stereotype.Service;
+
+/**
+ * ${entity.comment} 服务实现
+ */
+@Service
+public class ${entity.fileName} extends BaseServiceImpl<${entity.entityName}> implements ${entity.upperCamelName}${serviceSuffix} {
+
+}
diff --git a/robin-generator/src/test/java/cn/cliveyuan/robin/generator/test/RobinGeneratorTest.java b/robin-generator/src/test/java/cn/cliveyuan/robin/generator/test/RobinGeneratorTest.java
index 153a9b96164cc1443520ccb56f4f2a5aab378d3e..b7caa793d8c635d8b1e5988cf7dc5b40953c8c32 100644
--- a/robin-generator/src/test/java/cn/cliveyuan/robin/generator/test/RobinGeneratorTest.java
+++ b/robin-generator/src/test/java/cn/cliveyuan/robin/generator/test/RobinGeneratorTest.java
@@ -12,7 +12,7 @@ public class RobinGeneratorTest {
@Test
public void testGenerate() {
- RobinGenerator robinGenerator = new MybatisGenerator();
+ RobinGenerator robinGenerator = MybatisGenerator.builder().build();
robinGenerator.generate();
}
}
diff --git a/robin-generator/src/test/resources/code-generator.xml b/robin-generator/src/test/resources/code-generator.xml
new file mode 100755
index 0000000000000000000000000000000000000000..dfb414895e384c938b4b61aee35b7f6fff92b374
--- /dev/null
+++ b/robin-generator/src/test/resources/code-generator.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/update_versions.sh b/update_versions.sh
new file mode 100755
index 0000000000000000000000000000000000000000..61de130efd218465e2dc7f682915523293d7172c
--- /dev/null
+++ b/update_versions.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+
+mvn versions:set -DnewVersion=1.1.0