package com.yanzuoguang.dao.impl;

import com.yanzuoguang.dao.BaseDao;
import com.yanzuoguang.dao.DaoConst;
import com.yanzuoguang.dao.QueryPara;
import com.yanzuoguang.db.DbExecute;
import com.yanzuoguang.util.YzgError;
import com.yanzuoguang.util.base.ObjectHelper;
import com.yanzuoguang.util.exception.CodeException;
import com.yanzuoguang.util.helper.DateHelper;
import com.yanzuoguang.util.helper.StringHelper;
import com.yanzuoguang.util.vo.*;

import java.util.*;

/**
 * 数据库操作的基本工具类
 * 1. 实现了基本的增删该查
 * 2. 实现了统计的增肌和修改
 * 3. 实现了一定功能的基本验证,验证数据是否存在
 * 4. 数据主键的获取等功能。
 * 5. 获取自增等功能
 *
 * @author 颜佐光
 */
public abstract class BaseDaoImpl extends BaseDaoSql implements BaseDao {

    /**
     * 构造函数
     */
    public BaseDaoImpl() {
    }

    public BaseDaoImpl(DbExecute db, TableSqlCache table) {
        super(db, table);
    }

    /**
     * 获取当前主键
     *
     * @return
     */
    private String getIdentity() {
        return StringHelper.toString(this.getDb().queryCell(this.getClass(), "GET_KEY", "SELECT @@IDENTITY"));
    }

    protected String createReplace(String sqlName, Object model) {
        this.checkTable();
        // 判断主键是字符串和需要生成主键
        boolean isKeyString = this.table.getTable().getKeyType() == String.class;
        String keyString = this.table.initKeyValue(model);
        // 检测数据合法性
        this.check(DaoConst.OPERATOR_TYPE_CREATE, keyString, model);
        // 执行创建的SQL语句
        int ret = updateSql(sqlName, model);
        // 判断是否需要获取自增编号(主键为整形)
        if (StringHelper.isEmpty(keyString) && !isKeyString) {
            keyString = this.getIdentity();
            this.table.setKeyValue(model, keyString);
        }
        // 最终处理
        this.created(model);
        // 返回执行结果
        return ret > 0 ? keyString : "";
    }

    protected List<String> createReplaceList(String sqlName, Collection collection) {
        if (collection == null || collection.isEmpty()) {
            return new ArrayList<>();
        }
        this.checkTable();
        int size = collection.size();
        // 写入主键,用于排序修改,防止互相锁数据
        for (Object item : collection) {
            this.table.initKeyValue(item);
        }
        // 获取排序后的数据
        List list = this.table.getKeySort(collection);
        boolean isKeyString = this.table.getTable().getKeyType() == String.class;
        boolean isEmpty = true;
        // 循环创建
        for (Object item : list) {
            // 判断主键是字符串和需要生成主键
            String keyString = this.table.initKeyValue(item);
            // 检测数据合法性
            this.check(DaoConst.OPERATOR_TYPE_CREATE, keyString, item);
            // 判断是否为空
            if (!StringHelper.isEmpty(keyString)) {
                isEmpty = false;
            }
        }
        // 获取创建语句
        SqlData sqlData = this.getSql(sqlName);
        // 最终sql
        StringBuilder sqlList = new StringBuilder();
        // 最终列表
        List<Object> paraList = new ArrayList<>();
        for (Object item : list) {
            List<Object> paras = new ArrayList<>();
            String sql = this.getPara(paras, sqlData, item, true);
            if (sqlList.length() > 0) {
                sql = sql.replaceAll(DaoConst.SQL_LIST_FROM, DaoConst.SQL_LIST_TO);
            }
            sqlList.append(sql);
            paraList.addAll(paras);
        }
        // 最终执行的更新语句
        int ret = this.getDb().update(this.getClass(), sqlData.getName(), sqlList.toString(), paraList.toArray());
        // 判断是否需要获取自增编号(主键为整形)
        if (isEmpty && !isKeyString) {
            long identity = StringHelper.toInt(this.getIdentity());
            int pos = 0;
            // 回写主键
            for (Object item : list) {
                pos++;
                String keyString = StringHelper.toString(identity - size + pos);
                this.table.setKeyValue(item, keyString);
            }
        }
        // 触发更新事件
        for (Object item : list) {
            this.onUpdateSql(item);
        }
        // 最后获取主键集合
        return this.table.getCollectionRet(collection);
    }

    /**
     * 创建数据,当不传入了主键时,则会自动生成主键,传入时不会生成。
     *
     * @param model 需要创建的数据
     * @return 创建的主键编号, 创建成功,返回主键,否则为空
     */
    @Override
    public String create(Object model) {
        return createReplace(DaoConst.CREATE, model);
    }


    /**
     * 创建数据,当不传入了主键时,则会自动生成主键,传入时不会生成。
     *
     * @param model 需要创建的数据
     * @return 创建的主键编号, 创建成功,返回主键,否则为空
     */
    @Override
    public String replace(Object model) {
        return createReplace(DaoConst.REPLACE, model);
    }

    /**
     * 修改数据
     *
     * @param model 需要修改的数据
     * @return 删除的主键编号
     */
    @Override
    public String update(Object model) {
        this.checkTable();
        String keyString = this.table.getKeyValue(model);
        if (StringHelper.isEmpty(keyString)) {
            throw YzgError.getRuntimeException("029", this.table.getTable().getName());
        }
        this.check(DaoConst.OPERATOR_TYPE_UPDATE, keyString, model);
        SqlData sqlData = this.getSql(DaoConst.UPDATE);
        int ret = updateSql(sqlData, model);
        if (ret == 0) {
            throw YzgError.getRuntimeException("030");
        }
        return ret > 0 ? keyString : "";
    }

    /**
     * 保存数据
     *
     * @param model 需要保存的数据
     * @return 保存的主键编号
     */
    @Override
    public String save(Object model) {
        this.checkTable();
        String id = this.table.getKeyValue(model);
        if (StringHelper.isEmpty(id)) {
            return create(model);
        } else {
            return update(model);
        }
    }

    /**
     * 删除数据
     *
     * @param model 需要删除的数据
     * @return 删除的主键编号
     */
    @Override
    public int remove(Object model) {
        this.checkTable();
        // 获取来源主键
        Object from = model;
        String keyString = this.table.getInputKey(from);
        // 调用删除日志
        this.check(DaoConst.OPERATOR_TYPE_REMOVE, keyString, from);
        // 当主键存在值时,直接通过主键删除
        from = this.table.getKeyObject(from, keyString);

        TableStruct tableStruct = this.table.getTable();

        TableFieldVo remove = tableStruct.getRemove();
        addModelFromValue(from, model, remove, StringHelper.EMPTY);
        addModelFromValue(from, model, remove, DaoConst.OLD_FLAG);
        // 处理来源值
        for (TableFieldVo fieldVo : tableStruct.getRemoveUpdate()) {
            addModelFromValue(from, model, fieldVo, StringHelper.EMPTY);
        }

        // 调用删除语句
        SqlData sqlData = this.getSql(DaoConst.REMOVE);
        return updateSql(sqlData, from);
    }

    private void addModelFromValue(Object from, Object model, TableFieldVo fieldVo, String tag) {
        if (fieldVo == null) {
            return;
        }
        String fieldName = fieldVo.inputName + tag;
        Object fromValue = ObjectHelper.get(model, fieldName);
        ObjectHelper.set(from, fieldName, fromValue);
    }

    /**
     * 创建数据,当不传入了主键时,则会自动生成主键,传入时不会生成。
     *
     * @param collection 需要创建的数据
     * @return 创建的主键编号
     */
    @Override
    public List<String> createList(Collection collection) {
        return this.createReplaceList(DaoConst.CREATE, collection);
    }

    /**
     * 创建数据,当不传入了主键时,则会自动生成主键,传入时不会生成。
     *
     * @param model 需要创建的数据
     * @return 创建的主键编号
     */
    @Override
    public List<String> createArray(Object... model) {
        return createList(Arrays.asList(model));
    }

    /**
     * 修改数据
     *
     * @param collection 需要修改的数据
     * @return 删除的主键编号
     */
    @Override
    public List<String> updateList(Collection collection) {
        this.checkTable();
        // 获取排序后的数据
        List list = this.table.getKeySort(collection);
        for (Object item : list) {
            this.update(item);
        }
        // 最后获取主键集合
        return this.table.getCollectionRet(collection);

    }

    /**
     * 创建数据,当不传入了主键时,则会自动生成主键,传入时不会生成。
     *
     * @param model 需要创建的数据
     * @return 创建的主键编号
     */
    @Override
    public List<String> replaceArray(Object... model) {
        return replaceList(Arrays.asList(model));
    }

    /**
     * 修改数据
     *
     * @param collection 需要修改的数据
     * @return 删除的主键编号
     */
    @Override
    public List<String> replaceList(Collection collection) {
        return this.createReplaceList(DaoConst.REPLACE, collection);
    }

    /**
     * 修改数据
     *
     * @param model 需要修改的数据
     * @return 删除的主键编号
     */
    @Override
    public List<String> updateArray(Object... model) {
        return updateList(Arrays.asList(model));
    }

    /**
     * 保存数据,有主键时修改,无主键时创建
     *
     * @param collection 需要保存的数据
     * @return 保存的主键编号
     */
    @Override
    public List<String> saveList(Collection collection) {
        // 创建列表
        List<Object> creates = new ArrayList<>();
        // 修改列表
        List<Object> updates = new ArrayList<>();
        // 将保存的集合数据划分为创建列表和修改列表
        for (Object item : collection) {
            String keyValue = this.table.getKeyValue(item);
            if (StringHelper.isEmpty(keyValue)) {
                creates.add(item);
            } else {
                updates.add(item);
            }
        }
        // 首先执行更新,防止和创建时发生冲突
        this.updateList(updates);
        // 然后执行创建SQL语句
        this.createList(creates);
        // 最后获取主键集合
        return this.table.getCollectionRet(collection);
    }

    /**
     * 保存数据,有主键时修改,无主键时创建
     *
     * @param model 需要保存的数据
     * @return 保存的主键编号
     */
    @Override
    public List<String> saveArray(Object... model) {
        return saveList(Arrays.asList(model));
    }


    /**
     * 删除数据,可以用于父子表删除,如通过订单删除游客信息 visitorDao.remove({orderId:1});
     * int field;
     *
     * @param collection 需要删除的数据,可以是主键字符串(Int),或者是包含主键的实体,或者是包含其他非主键的实体完全匹配.
     * @return 删除的记录数量
     */
    @Override
    public int removeList(Collection collection) {
        this.checkTable();
        // 获取删除的列表,会按照主键顺序进行删除,没有主键时,不进行排序
        List list = this.table.getKeySort(collection);
        // 返回影响的行数
        int ret = 0;
        for (Object item : list) {
            ret += this.remove(item);
        }
        return ret;
    }

    /**
     * 删除数据,可以用于父子表删除,如通过订单删除游客信息 visitorDao.remove({orderId:1});
     * int field;
     *
     * @param model 需要删除的数据,可以是主键字符串(Int),或者是包含主键的实体,或者是包含其他非主键的实体完全匹配.
     * @return 删除的记录数量
     */
    @Override
    public int removeArray(Object... model) {
        return removeList(Arrays.asList(model));
    }

    /**
     * 检测数据
     *
     * @param operatorType 操作方法类型
     * @param keyString    主键
     * @param model        需要检测的数据
     */
    protected void check(int operatorType, String keyString, Object model) {
        if (operatorType == DaoConst.OPERATOR_TYPE_CREATE || operatorType == DaoConst.OPERATOR_TYPE_UPDATE) {
            if (model instanceof InitDao) {
                InitDao to = (InitDao) model;
                to.init();
            }
            List<SqlData> sqlArray = this.table.getSqlType(DaoConst.SQL_TYPE_EXISTS);
            if (sqlArray != null) {
                for (SqlData sql : sqlArray) {
                    this.checkExist(sql.getName(), model, String.format("%s已经存在", sql.getName()));
                }
            }
        } else if (operatorType == DaoConst.OPERATOR_TYPE_REMOVE) {
            if (model instanceof InitRemoveDao) {
                InitRemoveDao to = (InitRemoveDao) model;
                to.initRemove();
            }
        }
    }

    /**
     * 获取请求对象爱
     *
     * @param model 来源对象
     * @return
     */
    protected Object getLoadFrom(Object model, QueryPara queryPara) {
        this.checkTable();
        if (queryPara != null && queryPara.isFullCond()) {
            return model;
        }
        // 获取来源主键
        return this.table.getKeyObject(model);
    }

    private <T> void checkLoadResult(T toItem, List<T> tos) {
        this.checkTable();
        // 判断来源主键是否存在,不存在则获取加载后的主键
        if (toItem != null) {
            String key = this.table.getKeyValue(toItem);
            check(DaoConst.OPERATOR_TYPE_LOAD, key, toItem);
        }
        if (tos != null) {
            for (T item : tos) {
                String key = this.table.getKeyValue(item);
                check(DaoConst.OPERATOR_TYPE_LOAD, key, item);
            }
        }
    }


    /**
     * 加载数据
     *
     * @param model       加载数据的请求参数
     * @param resultClass 需要加载的数据的类型
     * @param queryPara   查询参数
     * @param <T>         返回数据的类型
     * @return 需要返回的数据
     */
    @Override
    public <T> T load(Object model, Class<T> resultClass, QueryPara queryPara) {
        // 获取来源主键
        Object from = this.getLoadFrom(model, queryPara);
        // 通过传入数据进行加载
        T to = this.queryFirst(resultClass, DaoConst.LOAD, from, queryPara);
        if (to == null) {
            return null;
        }

        // 判断来源主键是否存在,不存在则获取加载后的主键
        checkLoadResult(to, null);

        return to;
    }


    /**
     * 加载数据
     *
     * @param model       加载数据的请求参数
     * @param resultClass 需要加载的数据的类型
     * @param <T>         返回数据的类型
     * @return 需要返回的数据
     */
    @Override
    public <T> T load(Object model, Class<T> resultClass) {
        return this.load(model, resultClass, null);
    }

    /**
     * 加载数据
     *
     * @param model       加载数据的请求参数
     * @param resultClass 需要加载的数据的类型
     * @param queryPara   查询参数
     * @param <T>         返回数据的类型
     * @return 需要返回的数据
     */
    @Override
    public <T> List<T> loadList(Object model, Class<T> resultClass, QueryPara queryPara) {
        // 获取来源主键
        Object from = this.getLoadFrom(model, queryPara);

        // 通过传入数据进行加载
        List<T> to = this.query(resultClass, DaoConst.LOAD, from, queryPara);
        if (to == null || to.size() == 0) {
            return new ArrayList<>();
        }

        checkLoadResult(null, to);

        return to;
    }

    /**
     * 加载数据
     *
     * @param model       加载数据的请求参数
     * @param resultClass 需要加载的数据的类型
     * @param <T>         返回数据的类型
     * @return 需要返回的数据
     */
    @Override
    public <T> List<T> loadList(Object model, Class<T> resultClass) {
        return this.loadList(model, resultClass, null);
    }


    /**
     * 加载分页数据
     *
     * @param model       加载数据的请求参数
     * @param resultClass 需要加载的数据的类型
     * @param <T>         返回数据的类型
     * @return 需要返回的数据
     */
    @Override
    public <T> PageSizeData<T> loadPage(PageSizeReqVo model, Class<T> resultClass) {
        return this.loadPage(model, model, resultClass, null);
    }

    /**
     * 加载分页数据
     *
     * @param model       加载数据的请求参数
     * @param resultClass 需要加载的数据的类型
     * @param queryPara   查询参数
     * @param <T>         返回数据的类型
     * @return 需要返回的数据
     */
    @Override
    public <T> PageSizeData<T> loadPage(PageSizeReqVo model, Class<T> resultClass, QueryPara queryPara) {
        return this.loadPage(model, model, resultClass, queryPara);
    }

    /**
     * 加载分页数据
     *
     * @param model       加载数据的请求参数
     * @param resultClass 需要加载的数据的类型
     * @param <T>         返回数据的类型
     * @return 需要返回的数据
     */
    @Override
    public <T> PageSizeData<T> loadPage(PageSizeReqVo pageReq, Object model, Class<T> resultClass) {
        return this.loadPage(pageReq, model, resultClass, null);
    }

    /**
     * 加载分页数据
     *
     * @param model       加载数据的请求参数
     * @param resultClass 需要加载的数据的类型
     * @param queryPara   查询参数
     * @param <T>         返回数据的类型
     * @return 需要返回的数据
     */
    @Override
    public <T> PageSizeData<T> loadPage(PageSizeReqVo pageReq, Object model, Class<T> resultClass, QueryPara queryPara) {
        // 获取来源主键
        Object from = this.getLoadFrom(model, queryPara);

        // 通过传入数据进行加载
        PageSizeData<T> to = this.queryPage(resultClass, pageReq, DaoConst.LOAD, from, queryPara);
        if (to == null || to.getPageTotal() == 0) {
            return to;
        }

        // 判断来源主键是否存在,不存在则获取加载后的主键
        checkLoadResult(null, to.getList());

        return to;
    }


    /**
     * 加载分页数据
     *
     * @param model       加载数据的请求参数
     * @param resultClass 需要加载的数据的类型
     * @param <T>         返回数据的类型
     * @return 需要返回的数据
     */
    @Override
    public <T> List<T> loadPageData(PageSizeReqVo model, Class<T> resultClass) {
        return this.loadPageData(model, model, resultClass, null);
    }

    /**
     * 加载分页数据
     *
     * @param model       加载数据的请求参数
     * @param resultClass 需要加载的数据的类型
     * @param queryPara   查询参数
     * @param <T>         返回数据的类型
     * @return 需要返回的数据
     */
    @Override
    public <T> List<T> loadPageData(PageSizeReqVo model, Class<T> resultClass, QueryPara queryPara) {
        return this.loadPageData(model, model, resultClass, queryPara);
    }


    /**
     * 加载分页数据
     *
     * @param model       加载数据的请求参数
     * @param resultClass 需要加载的数据的类型
     * @param <T>         返回数据的类型
     * @return 需要返回的数据
     */
    @Override
    public <T> List<T> loadPageData(PageSizeReqVo pageReq, Object model, Class<T> resultClass) {
        return this.loadPageData(pageReq, model, resultClass, null);
    }

    /**
     * 加载分页数据
     *
     * @param model       加载数据的请求参数
     * @param resultClass 需要加载的数据的类型
     * @param queryPara   查询参数
     * @param <T>         返回数据的类型
     * @return 需要返回的数据
     */
    @Override
    public <T> List<T> loadPageData(PageSizeReqVo pageReq, Object model, Class<T> resultClass, QueryPara queryPara) {
        // 获取来源主键
        Object from = this.getLoadFrom(model, queryPara);

        // 通过传入数据进行加载
        List<T> to = this.queryPageData(resultClass, pageReq, DaoConst.LOAD, from, queryPara);
        if (to == null) {
            return new ArrayList<>();
        }

        // 判断来源主键是否存在,不存在则获取加载后的主键
        checkLoadResult(null, to);

        return to;
    }

    /**
     * 设置MD5主键
     *
     * @param model
     * @param <T>
     */
    @Override
    public <T> String setGroupId(T model) {
        this.checkTable();
        // 判断前台实体
        if (model == null) {
            return StringHelper.EMPTY;
        }

        String md5Field = this.table.getTable().getMD5KeyName();
        // 获取前台分组的MD5标识
        String md5 = StringHelper.isEmpty(md5Field) ? StringHelper.EMPTY : ObjectHelper.getString(model, md5Field);;
        if (StringHelper.isEmpty(md5)) {
            md5 = this.getMd5(DaoConst.GROUP_QUERY, model);
        }
        if (!StringHelper.isEmpty(md5Field)) {
            ObjectHelper.set(model, md5Field, md5);
        }
        if (this.table.getTable().getKeyType() == String.class) {
            this.table.setKeyValue(model, md5);
        }
        return md5;
    }

    /**
     * 添加统计数据
     *
     * @param cls   类型
     * @param model 实体
     * @param <T>   泛型类型
     * @return 增加统计的数据编号
     */
    @Override
    public <T> String addGroup(Class<T> cls, T model) {
        this.setGroupId(model);
        // 判断前台实体
        if (model == null) {
            return "";
        }

        // 获取标识的实体
        T from;
        String md5Field = this.table.getTable().getMD5KeyName();
        if (this.table.getTable().getKeyType() == String.class) {
            from = model;
        } else if (!StringHelper.isEmpty(md5Field)) {
            Map<String, Object> map = new HashMap<>(DaoConst.COLLECTION_INIT_SIZE);
            map.put(md5Field, ObjectHelper.get(model, md5Field));
            from = this.load(map, cls);
        } else {
            from = this.queryFirst(cls, DaoConst.GROUP_QUERY, model);
        }

        // 当没有存在,则创建,否则 UpdateAdd
        if (from == null) {
            return this.create(model);
        } else {
            // 获取后台的编号,并写入到前台传入的数据上面
            String keyField = this.table.getKeyField();
            Object key = ObjectHelper.get(from, keyField);
            ObjectHelper.set(model, keyField, key);
            if (model instanceof InitDao) {
                ((InitDao) model).init();
            }
            // 执行更新SQL语句
            SqlData sqlData = this.getSql(DaoConst.GROUP_ADD);
            int ret = this.updateSql(sqlData, model);
            if (ret < 1) {
                return this.create(model);
            } else {
                return key.toString();
            }
        }
    }

    /**
     * 添加统计数组
     *
     * @param cls    类型
     * @param models 请求实体
     * @param <T>    泛型数据
     * @return 返回列表
     */
    @Override
    public <T> List<String> addGroupList(Class<T> cls, List<T> models) {
        List<String> ret = new ArrayList<>();
        for (T model : models) {
            ret.add(this.addGroup(cls, model));
        }
        return ret;
    }

    /**
     * 添加统计数组
     *
     * @param cls    类型
     * @param models 请求实体
     * @param <T>    泛型数据
     * @return 返回列表
     */
    @Override
    public <T> List<String> addGroupArray(Class<T> cls, T... models) {
        return this.addGroupList(cls, Arrays.asList(models));
    }

    /**
     * 根据SQL语句名称和实体获取MD5值
     *
     * @param sqlName SQL语句名称
     * @param model   获取加密之后的实体
     * @param <T>     获取的类型
     * @return 获取加密之后的值
     */
    protected <T> String getMd5(String sqlName, T model) {
        SqlData sql = this.getSql(sqlName);
        StringBuilder sb = new StringBuilder();
        String date = StringHelper.EMPTY;
        for (SqlDataField field : sql.getSqlDataFields()) {
            List<Object> values = field.getCond().getValues(model);
            for (Object item : values) {
                if (sb.length() > 0) {
                    sb.append(":");
                }
                sb.append(item);
                if (StringHelper.isEmpty(date)) {
                    String itemString = String.valueOf(item);
                    if (item instanceof Date) {
                        date = DateHelper.getDateTimeString((Date) item);
                    } else if (DateHelper.isDateFormat(itemString)) {
                        date = itemString;
                    }
                }
            }
        }
        if (StringHelper.isEmpty(date)) {
            return StringHelper.md5(sb.toString());
        } else {
            return StringHelper.getNewMD5Id(DateHelper.getDateTime(date), sb.toString());
        }
    }

    /**
     * 添加统计数据列表
     *
     * @param sqlName 需要执行的SQL语句名称
     * @param model   需要执行的SQL语句的实体
     * @return
     */
    protected int addGroupList(String sqlName, Object model) {
        this.updateSql(sqlName + "_GroupInit", model);
        return this.updateSql(sqlName + "_GroupAdd", model);
    }


    /***
     * 查询数据是否存在,当存在时修改,否则增加
     * @param cls 需要创建的实体的类型
     * @param requests 前台参数,不能包含主键以及其他不需要修改的字段
     * @return 保存成功,返回保存的ID,保存失败,返回空值
     */
    @Override
    public <T> List<String> saveByLoadArray(Class<T> cls, Object... requests) {
        return this.saveByLoadList(cls, Arrays.asList(requests));
    }

    /***
     * 查询数据是否存在,当存在时修改,否则增加
     * @param cls 需要创建的实体的类型
     * @param requests 前台参数,不能包含主键以及其他不需要修改的字段
     * @return 保存成功,返回保存的ID,保存失败,返回空值
     */
    @Override
    public <T> List<String> saveByLoadList(Class<T> cls, List requests) {
        List<String> ret = new ArrayList<>();
        if (requests != null) {
            for (Object model : requests) {
                ret.add(this.saveByLoad(cls, model));
            }
        }
        return ret;
    }

    /***
     * 查询数据是否存在,当存在时修改,否则增加
     * @param cls 需要创建的实体的类型
     * @param request 前台参数,不能包含主键以及其他不需要修改的字段
     * @return 保存成功,返回保存的ID,保存失败,返回空值
     */
    @Override
    public <T> String saveByLoad(Class<T> cls, Object request) {
        this.checkTable();
        String key = this.table.getInputKey(request);
        T from = null;
        if (!StringHelper.isEmpty(key)) {
            from = this.load(request, cls);
            return saveFromCreate(cls, from, request);
        }
        return saveFromCreate(cls, from, request);
    }

    /**
     * 根据from来创建对象,对象存在时修改,对象不存在时创建
     *
     * @param cls     自动创建的对象类型
     * @param from    需要修改的对象,当该对象不存在时创建数据
     * @param request 需要创建的对象
     * @param <T>     创建的数据类型
     * @return 创建成功的编号
     */
    protected <T> String saveFromCreate(Class<T> cls, T from, Object request) {
        this.checkTable();
        if (from == null) {
            try {
                from = cls.newInstance();
            } catch (Exception ex) {
                throw YzgError.getRuntimeException(ex, "046", cls.getName());
            }
            ObjectHelper.writeWithFrom(from, request);
            return this.create(from);
        } else {
            TableStruct tableStruct = this.table.getTable();
            // 写入历史版本号
            TableFieldVo version = tableStruct.getVersion();
            if (version != null) {
                Object hisVersion = ObjectHelper.get(from, version.inputName);
                ObjectHelper.writeWithFrom(from, request);
                ObjectHelper.set(from, version.inputName, hisVersion);
            } else {
                ObjectHelper.writeWithFrom(from, request);
            }
            return this.update(from);
        }
    }

    /***
     * 查询数据是否存在,当存在时修改,否则增加
     * @param cls 需要创建的实体的类型
     * @param sqlName   执行的SQL语句
     * @param request 前台参数,不能包含主键以及其他不需要修改的字段
     * @return 保存成功,返回保存的ID,保存失败,返回空值
     */
    public <T> String saveWith(Class<T> cls, String sqlName, Object request) {
        this.checkTable();
        if (request instanceof InitDao) {
            ((InitDao) request).init();
        }
        TableStruct tableStruct = this.table.getTable();
        // 获取所有条件字段
        MapRow condition = new MapRow();
        List<TableFieldVo> fields = tableStruct.getFields(DaoConst.FIELD_SAVE_WITH);
        for (TableFieldVo field : fields) {
            condition.put(field.inputName, ObjectHelper.get(request, field.inputName));
        }
        // 查询历史数据
        T from = this.queryFirst(cls, sqlName, condition);
        return saveFromCreate(cls, from, request);
    }

    /**
     * 创建成功后的处理
     *
     * @param model 创建的实体数据
     */
    protected void created(Object model) {

    }

    /**
     * 执行是否存在的SQL语句
     *
     * @param sqlName SQL语句名称
     * @param model   实体
     * @param msg     消息
     */
    protected void checkExist(String sqlName, Object model, String msg) {
        MapRow row = this.queryFirst(sqlName, model);
        if (row != null) {
            throw new CodeException(msg);
        }
    }


    /**
     * 对数据据进行集合操作
     *
     * @param daoVo
     * @return
     */
    @Override
    public List<String> dataDao(DataDaoVo daoVo) {
        List<String> res = new ArrayList<>();
        this.removeList(daoVo.getRemoves());
        res.addAll(this.updateList(daoVo.getUpdates()));
        res.addAll(this.createList(daoVo.getCreates()));
        return res;
    }
}