GeneratorServiceImpl.java 16.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package top.charles7c.cnadmin.tool.service.impl;

19 20
import java.io.File;
import java.nio.charset.StandardCharsets;
21
import java.sql.SQLException;
22
import java.util.Collection;
23
import java.util.Comparator;
24
import java.util.List;
25 26 27
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
28 29 30 31 32 33 34

import javax.sql.DataSource;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Service;
35
import org.springframework.transaction.annotation.Transactional;
36

37 38
import com.baomidou.mybatisplus.core.toolkit.Wrappers;

39
import cn.hutool.core.bean.BeanUtil;
40
import cn.hutool.core.collection.CollUtil;
41 42 43
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
44
import cn.hutool.core.util.ClassUtil;
45
import cn.hutool.core.util.StrUtil;
46
import cn.hutool.db.meta.Column;
47
import cn.hutool.system.SystemUtil;
48

49
import top.charles7c.cnadmin.common.constant.StringConsts;
50 51
import top.charles7c.cnadmin.common.enums.QueryTypeEnum;
import top.charles7c.cnadmin.common.exception.ServiceException;
52 53
import top.charles7c.cnadmin.common.model.query.PageQuery;
import top.charles7c.cnadmin.common.model.vo.PageDataVO;
54
import top.charles7c.cnadmin.common.util.TemplateUtils;
55
import top.charles7c.cnadmin.common.util.validate.CheckUtils;
56
import top.charles7c.cnadmin.tool.config.properties.GeneratorProperties;
57
import top.charles7c.cnadmin.tool.config.properties.GeneratorProperties.TemplateConfig;
58
import top.charles7c.cnadmin.tool.mapper.FieldConfigMapper;
59
import top.charles7c.cnadmin.tool.mapper.GenConfigMapper;
60
import top.charles7c.cnadmin.tool.model.entity.FieldConfigDO;
61
import top.charles7c.cnadmin.tool.model.entity.GenConfigDO;
62
import top.charles7c.cnadmin.tool.model.query.TableQuery;
63
import top.charles7c.cnadmin.tool.model.request.GenConfigRequest;
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
import top.charles7c.cnadmin.tool.model.vo.TableVO;
import top.charles7c.cnadmin.tool.service.GeneratorService;
import top.charles7c.cnadmin.tool.util.MetaUtils;
import top.charles7c.cnadmin.tool.util.Table;

/**
 * 代码生成业务实现
 *
 * @author Charles7c
 * @since 2023/4/12 23:58
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class GeneratorServiceImpl implements GeneratorService {

    private final DataSource dataSource;
81
    private final GeneratorProperties generatorProperties;
82
    private final FieldConfigMapper fieldConfigMapper;
83
    private final GenConfigMapper genConfigMapper;
84 85 86 87 88 89 90 91

    @Override
    public PageDataVO<TableVO> pageTable(TableQuery query, PageQuery pageQuery) throws SQLException {
        List<Table> tableList = MetaUtils.getTables(dataSource);
        String tableName = query.getTableName();
        if (StrUtil.isNotBlank(tableName)) {
            tableList.removeIf(table -> !StrUtil.containsAny(table.getTableName(), tableName));
        }
92
        tableList.removeIf(table -> StrUtil.equalsAny(table.getTableName(), generatorProperties.getExcludeTables()));
93 94
        CollUtil.sort(tableList,
            Comparator.comparing(Table::getCreateTime).thenComparing(Table::getUpdateTime).reversed());
95
        List<TableVO> tableVOList = BeanUtil.copyToList(tableList, TableVO.class);
96 97 98 99 100 101 102
        PageDataVO<TableVO> pageDataVO = PageDataVO.build(pageQuery.getPage(), pageQuery.getSize(), tableVOList);
        for (TableVO tableVO : pageDataVO.getList()) {
            long count = genConfigMapper.selectCount(
                Wrappers.lambdaQuery(GenConfigDO.class).eq(GenConfigDO::getTableName, tableVO.getTableName()));
            tableVO.setIsConfiged(count > 0);
        }
        return pageDataVO;
103
    }
104

105 106
    @Override
    public GenConfigDO getGenConfig(String tableName) throws SQLException {
107
        GenConfigDO genConfig = genConfigMapper.selectById(tableName);
108
        if (null == genConfig) {
109 110
            genConfig = new GenConfigDO(tableName);
            // 默认包名(当前包名)
111 112
            String packageName = ClassUtil.getPackage(GeneratorService.class);
            genConfig.setPackageName(StrUtil.subBefore(packageName, StringConsts.DOT, true));
113
            // 默认业务名(表注释)
114 115 116 117 118
            List<Table> tableList = MetaUtils.getTables(dataSource, tableName);
            if (CollUtil.isNotEmpty(tableList)) {
                Table table = tableList.get(0);
                genConfig.setBusinessName(StrUtil.replace(table.getComment(), "表", StringConsts.EMPTY));
            }
119 120 121 122 123
            // 默认作者名称(上次保存使用的作者名称)
            GenConfigDO lastGenConfig = genConfigMapper.selectOne(
                Wrappers.lambdaQuery(GenConfigDO.class).orderByDesc(GenConfigDO::getCreateTime).last("LIMIT 1"));
            if (null != lastGenConfig) {
                genConfig.setAuthor(lastGenConfig.getAuthor());
124
            }
125
            // 默认表前缀(sys_user -> sys_)
126 127 128 129 130 131 132 133
            int underLineIndex = StrUtil.indexOf(tableName, StringConsts.C_UNDERLINE);
            if (-1 != underLineIndex) {
                genConfig.setTablePrefix(StrUtil.subPre(tableName, underLineIndex + 1));
            }
        }
        return genConfig;
    }

134
    @Override
135 136 137
    public List<FieldConfigDO> listFieldConfig(String tableName, Boolean requireSync) {
        List<FieldConfigDO> fieldConfigList = fieldConfigMapper.selectListByTableName(tableName);
        if (CollUtil.isEmpty(fieldConfigList)) {
138
            Collection<Column> columnList = MetaUtils.getColumns(dataSource, tableName);
139
            return columnList.stream().map(FieldConfigDO::new).collect(Collectors.toList());
140 141 142 143 144
        }

        // 同步最新数据表列信息
        if (requireSync) {
            Collection<Column> columnList = MetaUtils.getColumns(dataSource, tableName);
145
            // 移除已不存在的字段配置
146
            List<String> columnNameList = columnList.stream().map(Column::getName).collect(Collectors.toList());
147 148 149 150
            fieldConfigList.removeIf(column -> !columnNameList.contains(column.getColumnName()));
            // 新增或更新字段配置
            Map<String, FieldConfigDO> fieldConfigMap = fieldConfigList.stream()
                .collect(Collectors.toMap(FieldConfigDO::getColumnName, Function.identity(), (key1, key2) -> key2));
151
            for (Column column : columnList) {
152 153 154
                FieldConfigDO fieldConfig = fieldConfigMap.get(column.getName());
                if (null != fieldConfig) {
                    // 更新已有字段配置
155
                    String columnType = StrUtil.splitToArray(column.getTypeName(), StringConsts.SPACE)[0].toLowerCase();
156 157
                    fieldConfig.setColumnType(columnType);
                    fieldConfig.setComment(column.getComment());
158
                } else {
159 160 161
                    // 新增字段配置
                    fieldConfig = new FieldConfigDO(column);
                    fieldConfigList.add(fieldConfig);
162
                }
163 164
            }
        }
165
        return fieldConfigList;
166
    }
167 168 169 170

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveConfig(GenConfigRequest request, String tableName) {
171 172 173 174 175 176
        // 保存字段配置
        fieldConfigMapper.delete(Wrappers.lambdaQuery(FieldConfigDO.class).eq(FieldConfigDO::getTableName, tableName));
        List<FieldConfigDO> fieldConfigList = request.getFieldConfigs();
        for (FieldConfigDO fieldConfig : fieldConfigList) {
            if (fieldConfig.getShowInForm()) {
                CheckUtils.throwIfNull(fieldConfig.getFormType(), "字段 [{}] 的表单类型不能为空", fieldConfig.getFieldName());
177 178
            } else {
                // 在表单中不显示,不需要设置必填
179
                fieldConfig.setIsRequired(false);
180
            }
181 182 183
            if (fieldConfig.getShowInQuery()) {
                CheckUtils.throwIfNull(fieldConfig.getFormType(), "字段 [{}] 的表单类型不能为空", fieldConfig.getFieldName());
                CheckUtils.throwIfNull(fieldConfig.getQueryType(), "字段 [{}] 的查询方式不能为空", fieldConfig.getFieldName());
184 185
            } else {
                // 在查询中不显示,不需要设置查询方式
186
                fieldConfig.setQueryType(null);
187 188
            }
            // 既不在表单也不在查询中显示,不需要设置表单类型
189 190
            if (!fieldConfig.getShowInForm() && !fieldConfig.getShowInQuery()) {
                fieldConfig.setFormType(null);
191
            }
192
            fieldConfig.setTableName(tableName);
193
        }
194
        fieldConfigMapper.insertBatch(fieldConfigList);
195 196 197

        // 保存或更新生成配置信息
        GenConfigDO newGenConfig = request.getGenConfig();
198 199 200 201
        String frontendPath = newGenConfig.getFrontendPath();
        if (StrUtil.isNotBlank(frontendPath)) {
            CheckUtils.throwIf(!StrUtil.containsAll(frontendPath, "src", "views"), "前端路径配置错误");
        }
202 203 204 205 206 207 208 209
        GenConfigDO oldGenConfig = genConfigMapper.selectById(tableName);
        if (null != oldGenConfig) {
            BeanUtil.copyProperties(newGenConfig, oldGenConfig);
            genConfigMapper.updateById(oldGenConfig);
        } else {
            genConfigMapper.insert(newGenConfig);
        }
    }
210 211 212 213 214 215 216

    @Override
    public void generate(String tableName) {
        GenConfigDO genConfig = genConfigMapper.selectById(tableName);
        CheckUtils.throwIfNull(genConfig, "请先进行数据表 [{}] 生成配置", tableName);
        List<FieldConfigDO> fieldConfigList = fieldConfigMapper.selectListByTableName(tableName);
        CheckUtils.throwIfEmpty(fieldConfigList, "请先进行数据表 [{}] 字段配置", tableName);
217 218 219 220 221 222 223
        Map<String, Object> genConfigMap = BeanUtil.beanToMap(genConfig);
        genConfigMap.put("date", DateUtil.date().toString("yyyy/MM/dd HH:mm"));
        String packageName = genConfig.getPackageName();
        String apiModuleName =
            StrUtil.subSuf(packageName, StrUtil.lastIndexOfIgnoreCase(packageName, StringConsts.DOT) + 1);
        genConfigMap.put("apiModuleName", apiModuleName);
        genConfigMap.put("apiName", StrUtil.lowerFirst(genConfig.getClassNamePrefix()));
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242

        try {
            String classNamePrefix = genConfig.getClassNamePrefix();
            Boolean isOverride = genConfig.getIsOverride();
            // 生成后端代码
            // 1、确定后端代码基础路径
            // 例如:D:/continew-admin
            String projectPath = SystemUtil.getUserInfo().getCurrentDir();
            // 例如:D:/continew-admin/continew-admin-tool
            File backendModuleFile = new File(projectPath, genConfig.getModuleName());
            // 例如:D:/continew-admin/continew-admin-tool/src/main/java/top/charles7c/cnadmin/tool
            List<String> backendModuleChildPathList = CollUtil.newArrayList("src", "main", "java");
            backendModuleChildPathList.addAll(StrUtil.split(genConfig.getPackageName(), StringConsts.DOT));
            File backendParentFile =
                FileUtil.file(backendModuleFile, backendModuleChildPathList.toArray(new String[0]));
            // 2、生成代码
            Map<String, TemplateConfig> templateConfigMap = generatorProperties.getTemplateConfigs();
            for (Map.Entry<String, TemplateConfig> templateConfigEntry : templateConfigMap.entrySet()) {
                // 例如:D:/continew-admin/continew-admin-tool/src/main/java/top/charles7c/cnadmin/tool/service/impl/XxxServiceImpl.java
243
                this.pretreatment(genConfigMap, fieldConfigList, templateConfigEntry);
244 245
                String className = classNamePrefix + StrUtil.nullToEmpty(templateConfigEntry.getKey());
                genConfigMap.put("className", className);
246 247 248
                TemplateConfig templateConfig = templateConfigEntry.getValue();
                File classParentFile = FileUtil.file(backendParentFile,
                    StrUtil.splitToArray(templateConfig.getPackageName(), StringConsts.DOT));
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
                File classFile = new File(classParentFile, className + FileNameUtil.EXT_JAVA);
                // 如果已经存在,且不允许覆盖,则跳过
                if (classFile.exists() && !isOverride) {
                    continue;
                }
                String content = TemplateUtils.render(templateConfig.getTemplatePath(), genConfigMap);
                FileUtil.writeString(content, classFile, StandardCharsets.UTF_8);
            }

            // 生成前端代码
            String frontendPath = genConfig.getFrontendPath();
            if (StrUtil.isBlank(frontendPath)) {
                return;
            }
            // 1、生成 api 代码
            // 例如:D:/continew-admin/continew-admin-ui
265
            genConfigMap.put("fieldConfigs", fieldConfigList);
266 267 268
            List<String> frontendSubPathList = StrUtil.split(frontendPath, "src");
            String frontendModulePath = frontendSubPathList.get(0);
            // 例如:D:/continew-admin/continew-admin-ui/src/api/tool/xxx.ts
269
            File apiParentFile = FileUtil.file(frontendModulePath, "src", "api", apiModuleName);
270 271 272 273 274 275 276 277 278
            String apiFileName = classNamePrefix.toLowerCase() + ".ts";
            File apiFile = new File(apiParentFile, apiFileName);
            if (apiFile.exists() && !isOverride) {
                return;
            }
            String apiContent = TemplateUtils.render("generator/api.ftl", genConfigMap);
            FileUtil.writeString(apiContent, apiFile, StandardCharsets.UTF_8);
            // 2、生成 view 代码
            // 例如:D:/continew-admin/continew-admin-ui/src/views/tool/xxx/index.vue
279 280
            File indexFile =
                FileUtil.file(frontendPath, apiModuleName, StrUtil.lowerFirst(classNamePrefix), "index.vue");
281 282 283 284 285 286 287 288 289 290 291 292 293 294
            if (indexFile.exists() && !isOverride) {
                return;
            }
            String indexContent = TemplateUtils.render("generator/index.ftl", genConfigMap);
            FileUtil.writeString(indexContent, indexFile, StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error("Generate code occurred an error: {}. tableName: {}.", e.getMessage(), tableName, e);
            throw new ServiceException("代码生成失败,请手动清理生成文件");
        }
    }

    /**
     * 预处理生成配置
     *
295
     * @param genConfigMap
296
     *            生成配置
297 298 299 300
     * @param originFieldConfigList
     *            原始字段配置列表
     * @param templateConfigEntry
     *            模板配置
301
     */
302 303 304 305 306 307 308 309 310
    private void pretreatment(Map<String, Object> genConfigMap, List<FieldConfigDO> originFieldConfigList,
        Map.Entry<String, TemplateConfig> templateConfigEntry) {
        TemplateConfig templateConfig = templateConfigEntry.getValue();
        // 移除需要忽略的字段
        List<FieldConfigDO> fieldConfigList = originFieldConfigList.stream()
            .filter(fieldConfig -> !StrUtil.equalsAny(fieldConfig.getFieldName(), templateConfig.getExcludeFields()))
            .collect(Collectors.toList());
        genConfigMap.put("fieldConfigs", fieldConfigList);
        // 统计部分特殊字段特征
311 312 313 314 315 316 317 318 319 320
        genConfigMap.put("hasLocalDateTime", false);
        genConfigMap.put("hasBigDecimal", false);
        genConfigMap.put("hasRequiredField", false);
        genConfigMap.put("hasListQueryField", false);
        for (FieldConfigDO fieldConfig : fieldConfigList) {
            String fieldType = fieldConfig.getFieldType();
            if ("LocalDateTime".equals(fieldType)) {
                genConfigMap.put("hasLocalDateTime", true);
            }
            if ("BigDecimal".equals(fieldType)) {
321
                genConfigMap.put("hasBigDecimal", true);
322 323 324 325 326 327 328 329 330 331
            }
            if (Boolean.TRUE.equals(fieldConfig.getIsRequired())) {
                genConfigMap.put("hasRequiredField", true);
            }
            QueryTypeEnum queryType = fieldConfig.getQueryType();
            if (null != queryType && StrUtil.equalsAny(queryType.name(), QueryTypeEnum.IN.name(),
                QueryTypeEnum.NOT_IN.name(), QueryTypeEnum.BETWEEN.name())) {
                genConfigMap.put("hasListQueryField", true);
            }
        }
332 333
        String subPackageName = templateConfig.getPackageName();
        genConfigMap.put("subPackageName", subPackageName);
334
    }
335
}