Springboot + Vue ElementUI 实现MySQLPostgresql可视化

news2024/11/26 19:31:15

一、功能展示:

效果如图:

DB连接配置维护:

Schema功能:集成Screw生成文档,导出库的表结构,导出表结构和数据

 表对象操作:翻页查询,查看创建SQL,生成代码

可以单个代码文件下载,也可以全部下载(打成zip包下载返回)

可以自定义文件名称,包路径和代码信息

二、实现过程

一开始只是想实现若依的生成代码功能:

然后我发现若依的这个只能使用SpringBoot配置的单例数据源

实际上来说应该可以像Navicat一样配置多个连接访问多个数据源

1、解决数据源问题

开动态数据源太麻烦了,我只是针对这个功能需要动态访问,所以第一步要解决的是访问DB的问题

在默认数据源下建了一张连接配置表来维护:

CREATE TABLE `db_conn` (
  `id` int NOT NULL AUTO_INCREMENT,
  `conn_name` varchar(32) COLLATE utf8mb4_general_ci NOT NULL,
  `username` varchar(32) COLLATE utf8mb4_general_ci NOT NULL,
  `password` varchar(32) COLLATE utf8mb4_general_ci NOT NULL,
  `host` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `port` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `creator` varchar(24) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `updater` varchar(24) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unq_idx_conn_name` (`conn_name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

除了Mybatis,轻量级的操作工具类我选择了Hutool的DB操作

使用容器对象保管使用的数据源,每次动态操作都需要从容器存取

package cn.cloud9.server.tool.ds;
 
import cn.cloud9.server.struct.constant.ResultMessage;
import cn.cloud9.server.struct.file.FileProperty;
import cn.cloud9.server.struct.file.FileUtil;
import cn.cloud9.server.struct.spring.SpringContextHolder;
import cn.cloud9.server.struct.util.Assert;
import cn.cloud9.server.struct.util.DateUtils;
import cn.hutool.core.io.IoUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.db.Page;
import cn.hutool.db.PageResult;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
 
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
 
@Lazy
@Slf4j
@Service("dbConnService")
public class DbConnServiceImpl extends ServiceImpl<DbConnMapper, DbConnDTO> implements IDbConnService {
 
    /* 数据源管理容器 */
    private static final Map<Integer, DataSource> dsContainer = new ConcurrentHashMap<>();
    private static final DbConnMapper dbConnMapper = SpringContextHolder.getBean("dbConnMapper", DbConnMapper.class);
 
    /**
     * 获取数据源对象的方法 (所有的接口操作都需要经过这个步骤)
     * @param dbId 连接参数实体ID
     * @return 参数对应的数据源对象
     */
    public static DataSource getDsById(Integer dbId) {
        DataSource dataSource = dsContainer.get(dbId);
        boolean isEmpty = Objects.isNull(dataSource);
        if (isEmpty) {
            DbConnDTO dbConn = dbConnMapper.selectById(dbId);
            Assert.isTrue(Objects.isNull(dbConn), ResultMessage.NOT_FOUND_ERROR, "连接配置");
            DruidDataSource druidDs = new DruidDataSource();
            druidDs.setUrl("jdbc:mysql://" + dbConn.getHost() + ":" + dbConn.getPort() + "/sys");
            druidDs.setUsername(dbConn.getUsername());
            druidDs.setPassword(dbConn.getPassword());
            druidDs.addConnectionProperty("useInformationSchema", "true");
            druidDs.addConnectionProperty("characterEncoding", "utf-8");
            druidDs.addConnectionProperty("useSSL", "false");
            druidDs.addConnectionProperty("serverTimezone", "UTC");
            druidDs.addConnectionProperty("useAffectedRows", "true");
            dataSource = druidDs;
            /* 置入容器 */
            dsContainer.put(dbId, druidDs);
        }
        return dataSource;
    }
 
}

2、界面信息的获取

只要有访问 information_schema 的权限就可以获取db的大部分信息了

【后面什么触发器,视图都没写,只是列在那留个坑以后来填】

就是hutool的翻页用的人很少没什么资料,我这里是自己看API注释琢磨的写的

这个pageResult我一开始以为和mp的page对象一样,结果发现是继承了ArrayList,序列化之后输出的只有结果集合,没有翻页的信息

所以要自己提取出来,他会算好放在里面

Entity就理解为一个Map对象,返回就是存键值对,按键值对取就行了,也可以作为SQL查询条件的封装对象

@SuppressWarnings("all")
@Override
public IPage<Entity> getTablePage(Integer dbId, String schemaName, DbTableDTO dto) throws SQLException {
    Db db = Db.use(getDsById(dbId));
 
    Entity condition = Entity.create();
    condition.setTableName("`information_schema`.`TABLES`");
    condition.set("`TABLE_TYPE`", "BASE TABLE");
    condition.set("`TABLE_SCHEMA`", schemaName);
    if (StringUtils.isNotBlank(dto.getTableName()))
        condition.put("`TABLE_NAME`", "LIKE '%" + dto.getTableName() + "%'");
    if (StringUtils.isNotBlank(dto.getTableComment()))
        condition.put("`TABLE_COMMENT`", "LIKE '%" + dto.getTableComment() + "%'");
    if (StringUtils.isNotBlank(dto.getStartCreateTime()) && StringUtils.isNotBlank(dto.getEndCreateTime())) {
        condition.put("`CREATE_TIME`", "BETWEEN '" + dto.getStartCreateTime() + "' AND '" + dto.getEndCreateTime() + "'");
    }
 
    IPage<Entity> page = dto.getPage();
    PageResult<Entity> pageResult = db.page(condition, new Page((int)page.getCurrent() - 1, (int)page.getSize()));
 
    page.setRecords(pageResult);
    page.setTotal(pageResult.getTotal());
    page.setPages(pageResult.getTotalPage());
    return page;
}

3、生成代码的思路

生成这种模板文件肯定是用模板引擎来做的,若依用的是velocity好像,那我这里就还是用freemarker,因为对freemarker接触的多一点

这还外加了一个starter,不过我没怎么用到,有原始支持包就行了

<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.32</version>
</dependency>
 
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-freemarker</artifactId>-->
<!--        </dependency>-->

先写好模板文件,里面标记的变量名称,FreeMarker会通过一个Map参数去取出来打在模板文件里渲染

 我先开了一个简单的测试Controller检查生成情况:

package cn.cloud9.server.test.controller;
 
import cn.cloud9.server.struct.common.BaseController;
import cn.cloud9.server.struct.file.FileUtil;
import cn.cloud9.server.struct.util.DateUtils;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.io.BufferedWriter;
import java.io.File;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
@RestController
@RequestMapping("/test/ftl")
public class TemplateTestController extends BaseController {
 
    @GetMapping("/getTemplate")
    public void getCodeFile() throws Exception {
        Map<String, Object> paramMap = new ConcurrentHashMap<>();
 
        paramMap.put("modelPath", "cn.cloud9.sever.test.model");
        paramMap.put("author", "cloud9");
        paramMap.put("tableName", "info_account");
        paramMap.put("description", "测试模板代码生成");
        paramMap.put("projectName", "tt-server");
        paramMap.put("dateTime", DateUtils.format(LocalDateTime.now(), DateUtils.DEFAULT_DATE_TIME_FORMAT));
        paramMap.put("modelName", "InfoAccount");
 
        List<Map<String, String>> columns = new ArrayList<>();
 
        Map<String, String> field1 = new ConcurrentHashMap<>();
        field1.put("columnName", "id");
        field1.put("fieldName", "id");
        field1.put("type", "Integer");
        columns.add(field1);
 
        Map<String, String> field2 = new ConcurrentHashMap<>();
        field2.put("columnName", "field_2");
        field2.put("fieldName", "field2");
        field2.put("type", "String");
        columns.add(field2);
 
        paramMap.put("columns", columns);
 
        String path = this.getClass().getResource("/code-template").getFile();
        File file = new File(path);
 
        Configuration config = new Configuration();
        TemplateLoader loader = new FileTemplateLoader(file);
        config.setTemplateLoader(loader);
 
        Template template = config.getTemplate("/java/Model.java.ftl", "UTF-8");
 
        FileUtil.setDownloadResponseInfo(response, "TestDTO.java");
        Writer out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8));
        template.process(paramMap, out);
        out.flush();
        out.close();
    }
}

模板文件内容:

package ${modelPath};
 
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
 
/**
 * @author ${author}
 * @description ${tableName} 实体类 ${modelDescription!}
 * @project ${projectName}
 * @date ${dateTime}
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("${tableName}")
public class ${modelName} {
 
<#list columns as column>
    @TableField("${column.columnName}")
    private ${column.type} ${column.fieldName};
 
</#list>
 
    @TableField(exist = false)
    private Page<${modelName}> page;
}

然后要解决输出文件和输出内容的两个方式:

两个方法基本是一样的,就是输出对象不一样,一个流,一个Writter

/**
 * 下载模板生成文件
 * @param response 响应对象
 * @param tp 模板参数
 * @throws Exception IO异常
 */
public void downloadTemplateFile(HttpServletResponse response, TemplateParam tp) throws Exception {
    TemplateType tt = tp.getTemplateTypeByCode();
    tp.setDateTime();
    if (TemplateType.MODEL.equals(tt)) getColumnsMetaData(tp);
    Assert.isTrue(TemplateType.NONE.equals(tt), ResultMessage.NOT_FOUND_ERROR, "模板类型编号");
    Map<String, Object> paramMap = BeanUtil.beanToMap(tp);
 
    Template template = configuration.getTemplate(tt.getPath(), "UTF-8");
    FileUtil.setDownloadResponseInfo(response, paramMap.get(tt.getFilenameKey()) + tt.getSuffix());
    Writer out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8));
    /* 输出成型的文件 */
    template.process(paramMap, out);
    out.flush();
    out.close();
}
 
/**
 * 获取模板生成的输出内容
 * @param tp 模板参数
 * @return 输出内容
 * @throws Exception IO异常
 */
public String getTemplateContent(TemplateParam tp, TemplateType tt) throws Exception {
    tp.setDateTime();
    if (TemplateType.MODEL.equals(tt)) getColumnsMetaData(tp);
    Assert.isTrue(TemplateType.NONE.equals(tt), ResultMessage.NOT_FOUND_ERROR, "模板类型编号");
    Map<String, Object> paramMap = BeanUtil.beanToMap(tp);
    Template template = configuration.getTemplate(tt.getPath(), "UTF-8");
 
    /* 代替方法 String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, paramMap); */
    StringWriter result = new StringWriter(1024);
    template.process(paramMap, result);
    return result.toString();
}

下载全部文件,返回的是zip包,就要先把文件在服务器上创建好打包返回,然后再删除缓存的文件:

/**
* 下载全部模板文件
* @param response
* @param tp
*/
public void downloadAllTemplateFile(HttpServletResponse response, TemplateParam tp) throws Exception {
   final String cachePath = fileProperty.getBaseDirectory() + ROOT_PATH;
   File file = new File(cachePath);
   if (!file.exists()) file.mkdirs();
 
   List<TemplateType> templateTypes = TemplateType.getAvailableTypes();
   List<File> cacheFiles = new ArrayList<>(templateTypes.size());
   for (TemplateType templateType : templateTypes) {
       /* 获取输出的文件名 */
       String fileName = ReflectUtil.getFieldValue(tp, templateType.getFilenameKey()).toString();
       fileName = fileName + templateType.getSuffix();
 
       /* 创建模板文件 */
       String fullPath = cachePath + File.separator + fileName;
       File templateFile = new File(fullPath);
       if (!templateFile.exists()) file.createNewFile();
 
       /* 获取输出的成型内容 */
       tp.setTemplateCode(templateType.getCode());
       String content = this.getTemplateContent(tp, templateType);
 
       /* 写入本地文件 */
       FileOutputStream fos = new FileOutputStream(fullPath);
       fos.write(content.getBytes());
       fos.close();
       cacheFiles.add(templateFile);
   }
 
   /* 创建压缩文件 */
   String zipFileName = cachePath + File.separator + tp.getTableName() + ".zip";
   File zipFile = new File(zipFileName);
   ZipUtil.zip(zipFile, false, cacheFiles.toArray(new File[]{}));
 
   /* 给压缩文件设置信息 */
   FileUtil.setDownloadResponseInfo(response, zipFile, zipFile.getName());
 
   /* 输出压缩文件 */
   BufferedInputStream in = cn.hutool.core.io.FileUtil.getInputStream(zipFile.getAbsolutePath());
   ServletOutputStream os = response.getOutputStream();
   IoUtil.copy(in, os, IoUtil.DEFAULT_BUFFER_SIZE);
   in.close();
   os.close();
 
   /* 删除本地缓存 */
   zipFile.delete();
   cacheFiles.forEach(File::delete);
}

上面的模板类型用枚举区分,枚举顺便存储一下模板的信息,这样取参数就方便了

一些没有写好的模板暂时设置null,逻辑就会跳过

package cn.cloud9.server.tool.template;
 
import lombok.Getter;
 
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
 
@Getter
public enum TemplateType {
    /* 没有文件 */
    NONE(null, null, null, null),
 
    /* 后台文件 */
    MODEL(1001, "/java/Model.java.ftl", "modelName", ".java"),
    MAPPER(1002, "/java/Mapper.java.ftl", "mapperName", ".java"),
    I_SERVICE(1003, "/java/IService.java.ftl", "iServiceName", ".java"),
    SERVICE_IMPL(1004, "/java/ServiceImpl.java.ftl", "serviceImplName", ".java"),
    CONTROLLER(1005, "/java/Controller.java.ftl", "controllerName", ".java"),
 
    /* 前端文件 */
    VUE_API(2001, "/vue/Api.js.ftl", "apiName", ".js"),
    /* VUE_VIEW(2002, "/vue/View.vue.ftl", "viewName", ".vue"), */
    VUE_VIEW(2002, "/vue/View.vue.ftl", null, null),
    ;
 
    /* 编号和模板文件路径 */
    private final Integer code;
    private final String path;
    private final String filenameKey;
    private final String suffix;
 
    TemplateType(Integer code, String path, String filenameKey, String suffix) {
        this.code = code;
        this.path = path;
        this.filenameKey = filenameKey;
        this.suffix = suffix;
    }
     
    /**
     * 获取可用的模板类型集合
     * @return List<TemplateType>
     */
    public static List<TemplateType> getAvailableTypes() {
        return Arrays
            .stream(values())
            .filter(t -> Objects.nonNull(t.code) && Objects.nonNull(t.path) && Objects.nonNull(t.filenameKey) && Objects.nonNull(t.suffix))
            .collect(Collectors.toList());
    }
 
}

所有的模板参数统一用一个对象接受:

package cn.cloud9.server.tool.template;
 
import cn.cloud9.server.struct.util.DateUtils;
import cn.hutool.db.Entity;
import lombok.Data;
 
import java.time.LocalDateTime;
import java.util.List;
 
@Data
public class TemplateParam {
 
    /* 注释信息部分 */
    private String author;
    private String projectName;
    private String dateTime;
    private String modelDescription;
    private String mapperDescription;
    private String iServiceDescription;
    private String serviceImplDescription;
    private String controllerDescription;
 
    /* 包路径 */
    private String modelPath;
    private String mapperPath;
    private String iServicePath;
    private String serviceImplPath;
    private String controllerPath;
 
    /* 类名称 & 文件名 */
    private String modelName;
    private String mapperName;
    private String iServiceName;
    private String serviceImplName;
    private String controllerName;
    private String apiName;
 
    /* controller接口与路径 */
    private String urlPath;
 
    /* 元数据信息 */
    private Integer dbConnId;
    private String schemaName;
    private String tableName;
    private List<Entity> columns;
 
    /* 模板类型枚举编号 */
    private Integer templateCode;
 
    /**
     * 设置当前时间
     */
    public void setDateTime() {
        dateTime = DateUtils.format(LocalDateTime.now(), DateUtils.DEFAULT_DATE_TIME_FORMAT);
    }
 
    public TemplateType getTemplateTypeByCode() {
        return TemplateType.getAvailableTypes().stream()
                .filter(tt -> tt.getCode().equals(templateCode))
                .findFirst()
                .orElse(TemplateType.NONE);
    }
 
    public static TemplateType getTemplateTypeByCode(Integer ttCode) {
        return TemplateType.getAvailableTypes().stream()
                .filter(tt -> tt.getCode().equals(ttCode))
                .findFirst()
                .orElse(TemplateType.NONE);
    }
}

解决字段列的模板参数问题:

/**
* 获取列的元数据
* @param tp
*/
private void getColumnsMetaData(TemplateParam tp) throws Exception {
   Db db = Db.use(DbConnServiceImpl.getDsById(tp.getDbConnId()));
   String querySql =
       "SELECT\n" +
       "\tCOLUMN_NAME AS `columnName`,\n" +
       "\tREPLACE(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(\n" +
       "\tLOWER(COLUMN_NAME), '_a','A'),'_b','B'),'_c','C'),'_d','D'),'_e','E'),'_f','F'),'_g','G'),'_h','H'),'_i','I'),'_j','J'),'_k','K'),'_l','L'),'_m','M'),'_n','N'),'_o','O'),'_p','P'),'_q','Q'),'_r','R'),'_s','S'),'_t','T'),'_u','U'),'_v','V'),'_w','W'),'_x','X'),'_y','Y'),'_z','Z') AS `fieldName`,\n" +
       "\tCASE\n" +
       "\t\tWHEN DATA_TYPE ='bit' THEN 'Boolean'\n" +
       "\t\tWHEN DATA_TYPE ='tinyint' THEN 'Byte'\n" +
       "\t\tWHEN LOCATE(DATA_TYPE, 'smallint,mediumint,int,integer') > 0 THEN 'Integer'\n" +
       "\t\tWHEN DATA_TYPE = 'bigint' THEN 'Long'\n" +
       "\t\tWHEN LOCATE(DATA_TYPE, 'float, double') > 0 THEN 'Double'\n" +
       "\t\tWHEN DATA_TYPE = 'decimal' THEN 'BigDecimal'\n" +
       "\t\tWHEN LOCATE(DATA_TYPE, 'time, date, year, datetime, timestamp') > 0 THEN 'LocalDateTime'\n" +
       "\t\tWHEN LOCATE(DATA_TYPE, 'json, char, varchar, tinytext, text, mediumtext, longtext') > 0 THEN 'String'\n" +
       "\tELSE 'String' END AS `type`\n" +
       "FROM\n" +
       "\t`information_schema`.`COLUMNS`\n" +
       "WHERE\n" +
       "\t`TABLE_SCHEMA` = ? \n" +
       "\tAND `TABLE_NAME` = ? ";
   List<Entity> entityList = db.query(querySql, tp.getSchemaName(), tp.getTableName());
   tp.setColumns(entityList);
}

数据类型可以用CASE WHEN 解决,但是头疼的问题是,怎么解决MySQL的下划线转驼峰处理

大部分提供的方法都是所有字母全部Replace一边,办法笨归笨,但是有效,所有我这里也是用这个办法,没有写成函数处理

但是牛逼的群友提供了一个子查询的办法来实现:

SET @target = 'ABC_DEF_gh_asdqw';
SELECT group_concat(
                concat(
                upper(substr(SUBSTRING_INDEX(SUBSTRING_index(@target,'_',help_topic_id+1),'_',-1) ,1,1)),
                substr(lower(SUBSTRING_INDEX(SUBSTRING_index(@target,'_',help_topic_id+1),'_',-1)) ,2)
                )
                separator '') AS num
FROM mysql.help_topic
WHERE help_topic_id <= length(@target)-LENGTH(replace(@target,'_',''))
order by help_topic_id desc

4、页面展示代码的几个功能问题:

一、展示代码块:

我看了若依源码是用了高亮JS,然后百度搜了下

"highlight.js": "^11.7.0",
"vue-highlightjs": "^1.3.3",

全局注册:

/* 挂载highlightJS */
import VueHighlightJS from 'vue-highlightjs'
import 'highlight.js/styles/monokai-sublime.css'
Vue.use(VueHighlightJS)

然后使用:

<pre v-loading="loadingFlag" v-highlightjs>
    <code class="sql" v-text="createSQL" />
</pre>

二、复制粘贴功能:

这个再vue-admin后台框架里面已经装了,而且有案例:

标签添加粘贴板指令:

<div class="dialog-bottom-bar">
  <el-button v-clipboard:copy="createSQL" v-clipboard:success="clipboardSuccess" type="primary" size="mini" icon="el-icon-document">复制</el-button>
  <el-button type="default" size="mini" icon="el-icon-close" @click="closeDialog">取消</el-button>
</div>

这里我没有全局注册,因为案例也没这么做,应该是个别功能用用

在使用的组件中导入指令并注册:

import clipboard from '@/directive/clipboard' // use clipboard by v-directive
 
export default {
    name: 'CreateSqlView',
    directives: { clipboard }
}

复制成功事件绑定一个方法:

clipboardSuccess() {
  this.$message.success('复制成功')
},

三、扩展延申

一、集成Screw文档生成:

这个之前写过一份固定的,用读取ini配置文件方式生成的

https://www.cnblogs.com/mindzone/p/16315169.html

我想反正我都解决动态数据源的问题了,干脆把文档生成集成进来正好

文档参数对象:

package cn.cloud9.server.tool.doc;
 
import cn.smallbun.screw.core.engine.EngineFileType;
import lombok.Data;
 
import java.util.List;
 
@Data
public class DbDocDTO {
 
    /* 文档名称 */
    private String docName;
    /* 文档标题 */
    private String docTitle;
    /* 组织名称 */
    private String organization;
    /* 组织连接 */
    private String organizationUrl;
 
    private String version;
    private String description;
    private Integer docType; /* 1,HTML 2,WORD 3,MD */
 
    /* 按指定的表名生成 */
    private List<String> specifyTables;
    /* 按指定的表前缀生成 */
    private List<String> specifyTablePrefixes;
    /* 按指定的表后缀生成 */
    private List<String> specifyTableSuffixes;
    /* 需要忽略的表名 */
    private List<String> ignoreTables;
    /* 需要忽略的表前缀 */
    private List<String> ignoreTablePrefixes;
    /* 需要忽略的表后缀 */
    private List<String> ignoreTableSuffixes;
 
 
    public EngineFileType getDocType() {
        switch (docType) {
            case 2: return EngineFileType.WORD;
            case 3: return EngineFileType.MD;
            default: return EngineFileType.HTML;
        }
    }
}

文档生成逻辑,就用官方Demo随便改改参数组合就OK了

package cn.cloud9.server.tool.doc;
 
import cn.cloud9.server.struct.file.FileProperty;
import cn.cloud9.server.struct.file.FileUtil;
import cn.cloud9.server.tool.ds.DbConnDTO;
import cn.cloud9.server.tool.ds.DbConnMapper;
import cn.hutool.core.io.IoUtil;
import cn.smallbun.screw.core.Configuration;
import cn.smallbun.screw.core.engine.EngineConfig;
import cn.smallbun.screw.core.engine.EngineTemplateType;
import cn.smallbun.screw.core.execute.DocumentationExecute;
import cn.smallbun.screw.core.process.ProcessConfig;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
 
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
 
@Slf4j
@Service
public class DbDocService {
 
    @Resource
    private FileProperty fileProperty;
 
    @Resource
    private DbConnMapper dbConnMapper;
 
    /**
     * 生成mysql文档
     * @param dbId 连接实体
     * @param schemaName 约束名
     * @param dto 文档参数实体
     * @param response 响应对象
     * @throws IOException IO异常
     */
    public void getDbSchemaDoc(Integer dbId, String schemaName, DbDocDTO dto, HttpServletResponse response) throws IOException {
        Configuration config = Configuration.builder()
                .organization(dto.getOrganization()) // 组织机构
                .organizationUrl(dto.getOrganizationUrl()) // 组织链接
                .title(dto.getDocTitle()) // 文档标题
                .version(dto.getVersion()) // 版本
                .description(dto.getDescription()) // 描述
                .dataSource(buildDataSource(dbId, schemaName)) // 数据源
                .engineConfig(buildEngineConfig(dto)) // 引擎配置
                .produceConfig(buildProcessConfig(dto)) // 处理配置
                .build();
        new DocumentationExecute(config).execute();
 
        /* 从服务器本地读取生成的文档文件下载 */
        final String fileName =  dto.getDocName() + dto.getDocType().getFileSuffix();
        final File docFile = new File(fileProperty.getBaseDirectory() + File.separator + fileName);
        if (!docFile.exists()) return;
        FileUtil.setDownloadResponseInfo(response, docFile, fileName);
 
        /* 下载 */
        BufferedInputStream in = cn.hutool.core.io.FileUtil.getInputStream(docFile.getAbsolutePath());
        ServletOutputStream os = response.getOutputStream();
        long copySize = IoUtil.copy(in, os, IoUtil.DEFAULT_BUFFER_SIZE);
        log.info("文档生成成功! {}bytes", copySize);
        in.close();
        os.close();
         
        /* 必须要等到其他IO流使用完毕资源释放了才能操作删除 */
        docFile.delete();
    }
 
    private HikariDataSource buildDataSource(Integer dbId, String schemaName) {
        DbConnDTO dbConn = dbConnMapper.selectById(dbId);
        // 创建 HikariConfig 配置类
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver");
        hikariConfig.setJdbcUrl("jdbc:mysql://" + dbConn.getHost() + ":" + dbConn.getPort() + "/" + schemaName);
        hikariConfig.setUsername(dbConn.getUsername());
        hikariConfig.setPassword(dbConn.getPassword());
        // 设置可以获取 tables remarks 信息
        hikariConfig.addDataSourceProperty("useInformationSchema", "true");
        hikariConfig.addDataSourceProperty("characterEncoding", "utf-8");
        hikariConfig.addDataSourceProperty("useSSL", "false");
        hikariConfig.addDataSourceProperty("serverTimezone", "UTC");
        hikariConfig.addDataSourceProperty("useAffectedRows", "true");
        hikariConfig.setMinimumIdle(2);
        hikariConfig.setMaximumPoolSize(5);
        // 创建数据源
        return new HikariDataSource(hikariConfig);
    }
 
    private ProcessConfig buildProcessConfig(DbDocDTO dto) {
        return ProcessConfig.builder()
            // 根据名称指定表生成
            .designatedTableName(CollectionUtils.isEmpty(dto.getSpecifyTables()) ? Collections.emptyList() : dto.getSpecifyTables())
            // 根据表前缀生成
            .designatedTablePrefix(CollectionUtils.isEmpty(dto.getSpecifyTablePrefixes()) ? Collections.emptyList() : dto.getSpecifyTablePrefixes())
            // 根据表后缀生成
            .designatedTableSuffix(CollectionUtils.isEmpty(dto.getSpecifyTableSuffixes()) ? Collections.emptyList() : dto.getSpecifyTableSuffixes())
            // 忽略数据库中address这个表名
            .ignoreTableName(CollectionUtils.isEmpty(dto.getIgnoreTables()) ? Collections.emptyList() : dto.getIgnoreTables())
            // 忽略表前缀,就是db1数据库中表名是t_开头的都不生产数据库文档(t_student,t_user这两张表)
            .ignoreTablePrefix(CollectionUtils.isEmpty(dto.getIgnoreTablePrefixes()) ? Collections.emptyList() : dto.getIgnoreTablePrefixes())
            // 忽略表后缀(就是db1数据库中表名是_teacher结尾的都不生产数据库文档:stu_teacher)
            .ignoreTableSuffix(CollectionUtils.isEmpty(dto.getIgnoreTableSuffixes()) ? Collections.emptyList() : dto.getIgnoreTableSuffixes())
            .build();
    }
 
    /**
     * 引擎配置创建
     * @param dto 文档参数实体
     * @return EngineConfig
     */
    private EngineConfig buildEngineConfig(DbDocDTO dto) {
        return EngineConfig.builder()
                // 生成文件路径
                .fileOutputDir(fileProperty.getBaseDirectory())
                // 打开目录
                .openOutputDir(false)
                // 文件类型
                .fileType(dto.getDocType())
                // 文件类型
                .produceType(EngineTemplateType.freemarker)
                // 自定义文件名称
                .fileName(dto.getDocName())
                .build();
    }
}

Vue前端表单view:

<template>
  <div>
    <el-form :ref="formRef" :model="form" :rules="formRules" size="small">
      <el-row :gutter="$ui.layout.gutter.g10">
        <el-col :span="$ui.layout.span.two">
          <el-form-item label="文件名" prop="docName">
            <el-input v-model="form.docName" placeholder="文件名" clearable maxlength="32" show-word-limit />
          </el-form-item>
        </el-col>
 
        <el-col :span="$ui.layout.span.two">
          <el-form-item label="文档标题" prop="docTitle">
            <el-input v-model="form.docTitle" placeholder="文档标题" clearable maxlength="32" show-word-limit />
          </el-form-item>
        </el-col>
 
        <el-col :span="$ui.layout.span.two">
          <el-form-item label="文档版本" prop="version">
            <el-input v-model="form.version" placeholder="文档版本" clearable maxlength="32" show-word-limit />
          </el-form-item>
        </el-col>
 
        <el-col :span="$ui.layout.span.two">
          <el-form-item label="文档描述" prop="description">
            <el-input v-model="form.description" placeholder="文档描述" clearable maxlength="32" show-word-limit />
          </el-form-item>
        </el-col>
 
        <el-col :span="$ui.layout.span.two">
          <el-form-item label="组织机构" prop="organization">
            <el-input v-model="form.organization" placeholder="组织机构" clearable maxlength="32" show-word-limit />
          </el-form-item>
        </el-col>
 
        <el-col :span="$ui.layout.span.two">
          <el-form-item label="组织机构链接" prop="organizationUrl">
            <el-input v-model="form.organizationUrl" placeholder="组织机构链接" clearable maxlength="64" show-word-limit />
          </el-form-item>
        </el-col>
 
        <el-col :span="$ui.layout.span.two">
          <el-form-item label="指定生成的表" prop="specifyTables">
            <el-input v-model="form.specifyTables" placeholder="指定生成的表" clearable maxlength="128" show-word-limit />
          </el-form-item>
        </el-col>
 
        <el-col :span="$ui.layout.span.two">
          <el-form-item label="指定生成的表前缀" prop="specifyTablePrefixes">
            <el-input v-model="form.specifyTablePrefixes" placeholder="指定生成的表前缀" clearable maxlength="128" show-word-limit />
          </el-form-item>
        </el-col>
 
        <el-col :span="$ui.layout.span.two">
          <el-form-item label="指定生成的表后缀" prop="specifyTableSuffixes">
            <el-input v-model="form.specifyTableSuffixes" placeholder="指定生成的表后缀" clearable maxlength="128" show-word-limit />
          </el-form-item>
        </el-col>
 
        <el-col :span="$ui.layout.span.two">
          <el-form-item label="要忽略的表" prop="specifyTables">
            <el-input v-model="form.ignoreTables" placeholder="要忽略的表" clearable maxlength="128" show-word-limit />
          </el-form-item>
        </el-col>
 
        <el-col :span="$ui.layout.span.two">
          <el-form-item label="要忽略的表前缀" prop="specifyTablePrefixes">
            <el-input v-model="form.ignoreTablePrefixes" placeholder="要忽略的表前缀" clearable maxlength="128" show-word-limit />
          </el-form-item>
        </el-col>
 
        <el-col :span="$ui.layout.span.two">
          <el-form-item label="要忽略的表后缀" prop="specifyTableSuffixes">
            <el-input v-model="form.ignoreTableSuffixes" placeholder="要忽略的表后缀" clearable maxlength="128" show-word-limit />
          </el-form-item>
        </el-col>
 
        <el-col :span="$ui.layout.span.two">
          <el-form-item label="文件类型" prop="docType">
            <el-select v-model="form.docType" placeholder="请选择" clearable style="width: 100%">
              <el-option v-for="item in docTypes" :key="item.value" :label="item.label" :value="item.value" />
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
 
    <div align="center">
      <el-button size="mini" type="primary" icon="el-icon-check" @click="formSubmit">确定</el-button>
      <el-button size="mini" type="default" plain icon="el-icon-close" @click="formCancel">取消</el-button>
    </div>
  </div>
</template>
 
<script>
import { getDbSchemaDoc } from '@/api/tt-server/tool-lib/mysql-visualize'
import { axiosDownloadFile } from '@/utils'
 
export default {
  name: 'CreateDocView',
  props: {
    param: {
      type: Object,
      required: true,
      default: () => {}
    }
  },
  data() {
    return {
      formRef: 'formRefKey',
      form: {
        docName: '',
        docTitle: '',
        version: 'V1.0.0',
        docType: 1,
        description: '',
        organization: 'OnCloud9',
        organizationUrl: 'https://www.cnblogs.com/mindzone/',
 
        /* 输出的表指定参数 */
        specifyTables: '',
        specifyTablePrefixes: '',
        specifyTableSuffixes: '',
        ignoreTables: '',
        ignoreTablePrefixes: '',
        ignoreTableSuffixes: ''
      },
      formRules: {
        docName: [{ required: true, message: '请填写文件名', trigger: 'blur' }],
        docTitle: [{ required: true, message: '请填写文档标题', trigger: 'blur' }],
        version: [{ required: true, message: '请填写文档版本', trigger: 'blur' }],
        description: [{ required: true, message: '请填写文档描述', trigger: 'blur' }],
        organization: [{ required: true, message: '请填写组织机构', trigger: 'blur' }],
        organizationUrl: [{ required: true, message: '请填写组织机构链接', trigger: 'blur' }],
        docType: [{ required: true, message: '请选择文件类型', trigger: 'blur' }]
      },
      docTypes: [
        { label: 'html | .html', value: 1 },
        { label: 'word | .docx', value: 2 },
        { label: 'markdown | .md', value: 3 }
      ]
    }
  },
  methods: {
    formSubmit() {
      this.$refs[this.formRef].validate(async(isValid) => {
        if (!isValid) return
        const docParam = JSON.parse(JSON.stringify(this.form))
        docParam.specifyTables = docParam.specifyTables ? docParam.specifyTables.split(',') : ''
        docParam.specifyTablePrefixes = docParam.specifyTablePrefixes ? docParam.specifyTablePrefixes.split(',') : ''
        docParam.specifyTableSuffixes = docParam.specifyTableSuffixes ? docParam.specifyTableSuffixes.split(',') : ''
        docParam.ignoreTables = docParam.ignoreTables ? docParam.ignoreTables.split(',') : ''
        docParam.ignoreTablePrefixes = docParam.ignoreTablePrefixes ? docParam.ignoreTablePrefixes.split(',') : ''
        docParam.ignoreTableSuffixes = docParam.ignoreTableSuffixes ? docParam.ignoreTableSuffixes.split(',') : ''
        const res = await getDbSchemaDoc(this.param.dbConnId, this.param.schemaName, docParam)
        axiosDownloadFile(res)
      })
    },
    formCancel() {
      this.$parent.$parent['closeAllDrawer']()
    }
  }
}
</script>

二、实现导出SQL脚本

先看导出表结构,表结构的SQL是有SQL可以直接查询的

mysql> SHOW CREATE TABLE sys_role;
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table    | Create Table                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| sys_role | CREATE TABLE `sys_role` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '角色主键',
  `role_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称',
  `role_value` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色值',
  `creator` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `updater` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '更新人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统角色表' |
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.10 sec)

  

但是导出数据脚本就没有,这个SQL脚本难就难在这里,于是我百度了下看看别人的思路:

然后参考了这位的:

https://blog.csdn.net/z1353095373/article/details/109679268?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22109679268%22%2C%22source%22%3A%22unlogin%22%7Dy 

他的办法就是从查询的结果集里面提取key和value一个个拼过去

但是我们都知道,前面的INSERT的句式是固定的,只是值动态的,所以我不太接受他这个拼的做法

因为既然是固定的东西就不应该浪费资源去反复做,一开始就拼好固定句式再加动态值就行了

1、提取表的所有字段:

SQL我就不细说过程了,直接贴:

mysql> SELECT GROUP_CONCAT(DISTINCT CONCAT('`', COLUMN_NAME, '`') ORDER BY `ORDINAL_POSITION` ASC SEPARATOR ', ') AS `fields` FROM `information_schema`.`COLUMNS` WHERE TABLE_SCHEMA = 'tt' AND TABLE_NAME = 'sys_role';
+-------------------------------------------------------------------------------------+
| fields                                                                              |
+-------------------------------------------------------------------------------------+
| `id`, `role_name`, `role_value`, `creator`, `create_time`, `updater`, `update_time` |
+-------------------------------------------------------------------------------------+

 

通过上面的SQL你就可以直接得到SELECT要查询的字段,

再把字段用SEPARATOR的字符分割成数组,就得到结果集需要按顺序遍历提取的字段了

由于Hutool的查询默认小写处理,所以这个结果默认也小处理,顺便移除反引号(解决key取不到的问题)

/* 获取这个表的字段, 按原始定位排序, 并列为一行, 逗号空格分隔 */
Entity fieldsEntity= db.queryOne("SELECT GROUP_CONCAT(DISTINCT CONCAT('`', COLUMN_NAME, '`') ORDER BY `ORDINAL_POSITION` ASC SEPARATOR ', ') AS `fields` FROM `information_schema`.`COLUMNS` WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?", schemaName, tableName);
String fields = fieldsEntity.get("fields").toString();
String[] columns = fields.trim().toLowerCase().replaceAll("`", "").split(", ");

  

有前面的字段,就有了固定句式了

1

2

/* 这张表固定的字段格式 */

final String specifyFields = "INSERT INTO `" + tableName + "` (" + fields + ") VALUES (";

  

 再通过翻页查询,动态组装值就可以了

如果值是NULL,就写NULL,数值和时间都可以是字符型来存

/**
* 翻页查询来组装INSERT脚本 缓解IO压力
* @param builder 脚本组装对象
* @param db sql执行对象
* @param schemaName 库名
* @param tableName 表明
* @param insertSqlPrePart INSERT前半部分
* @param columns 列名数组
*/
public void batchBuildInsertSql(StringBuilder builder, Db db, String schemaName, String tableName, String insertSqlPrePart, String[] columns) {
   try {
       int pageIdx = 0;
       int pageSize = 1000;
       int pageTotal = 1;
 
       final Entity condition = Entity.create();
       condition.setTableName("`" + schemaName + "`.`" + tableName + "`");
 
       while(pageIdx < pageTotal) {
           PageResult<Entity> pageResult = db.page(condition, new Page(pageIdx, pageSize));
           if (CollectionUtils.isEmpty(pageResult)) return;
 
           pageTotal = pageResult.getTotalPage();
           ++ pageIdx;
 
           /* 组装INSERT语句 */
           for (Entity recordEntity : pageResult) {
               builder.append(insertSqlPrePart);
               List<String> valList = new ArrayList<>();
               for (String column : columns) {
                   Object val = recordEntity.get(column.trim());
                   boolean isEmpty = Objects.isNull(val);
                   valList.add(isEmpty ? "NULL" : "'" + val + "'");
               }
               String joinedValues = String.join(", ", valList);
               builder.append(joinedValues).append(");\n");
           }
           builder.append("\n\n");
       }
   } catch (Exception e) {
       e.printStackTrace();
   }
}

  

三、Axios下载文件的问题

1、文件的接口都需要声明响应类型是blob,这要才能认定为文件

/**
 * 下载模板文件
 * @param data
 * @returns {*}
 */
export function downloadTemplate(data) {
  return request({
    url: `${PATH}/download`,
    method: 'post',
    responseType: 'blob',
    data
  })
}
 
/**
 * 下载全部文件
 * @param data
 * @returns {*}
 */
export function downloadAllTemplate(data) {
  return request({
    url: `${PATH}/download-all`,
    method: 'post',
    responseType: 'blob',
    data
  })
}

  

2、改写Request.js,因为默认的axios是不接受任何文件形式的响应对象

只要没有code就直接报错,但是接口实际上是正常返回

 改写就是追加前置条件,如果请求头说明了content-type信息,并且能在以下type中找到,就直接返回整个response对象

const fileHeaders = [
  'text/plain',
  'text/html',
  'application/vnd.ms-word2006ml',
  'text/x-web-markdown',
  'application/x-rar-compressed',
  'application/x-zip-compressed',
  'application/octet-stream',
  'application/zip',
  'multipart/x-zip',
  'text/x-sql'
]

  

 然后axios包装的response,不能直接下载,要给浏览器包装之后通过超链接点击实现下载:

/**
 * 转成文件流之后,可以通过模拟点击实现下载效果
 * axios下载文件, 请求接口需要追加属性:responseType: 'blob'
 * @param response
 */
export function axiosDownloadFile(response) {
  /* js创建一个a标签 */
  const element = document.createElement('a')
  /* 文档流转化成Base64 */
  const href = window.URL.createObjectURL(response.data)
  element.href = href
  /* 下载后文件名 */
  element.download = decodeURIComponent(response.headers['content-disposition'].match(/filename=(.*)/)[1])
  document.body.appendChild(element)
  /* 点击下载 */
  element.click()
  /* 下载完成移除元素 */
  document.body.removeChild(element)
  window.URL.revokeObjectURL(href)
}

  

源码仓库,已开放访问:

https://download.csdn.net/download/askuld/88216937

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/881157.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

获取部门完整路径数据

1: 获取部门数据 (基础) SELECT id, CONCAT(pid_path,id) as pid_path2,pid_path,title FROM web_department;2: 获取部门数据 (进阶, 但是是,分隔) SELECT t1.id, t1.title, CONCAT(t1.pid_path, ,, t1.id) AS pid_path,(SELECT GROUP_CONCAT(t2.title ORDER BY FIND_IN_SET…

Unity 实现2D地面挖洞!涂抹地形(碰撞部分,方法二)

文章目录 前言一、初始化虚拟点1.1点结构:1.2每个点有的状态:1.3生成点结构: 二、实例化边缘碰撞盒2.1计算生成边缘碰撞盒 三、涂抹部分3.1.虚拟点3.2.鼠标点3.3.内圈3.4.外圈 四、关于优化结语: 前言 老规矩先上效果图 继上一篇涂抹地形文章讲解发出后&#xff0c;有不少网友…

与微服务平台厂家联手,一起实现高效率发展!

在如今的快节奏发展社会中&#xff0c;只有利用科技的力量&#xff0c;才能与市场接轨&#xff0c;了解市场和客户需求&#xff0c;最终实现更快速的发展。如果还停留在闭门造车的环境中&#xff0c;不“引进来&#xff0c;走出去”&#xff0c;那势必会与成功擦肩而过。微服务…

SpringSecurity结合电商项目

pom <!--SpringSecurity及JWT依赖配置--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</ artifactId></dependency> <!--Hutool Java工具包--> <dependency>&l…

面试热题(每日温度)

请根据每日 气温 列表 temperatures &#xff0c;重新生成一个列表&#xff0c;要求其对应位置的输出为&#xff1a;要想观测到更高的气温&#xff0c;至少需要等待的天数。如果气温在这之后都不会升高&#xff0c;请在该位置用 0 来代替。 输入: temperatures [73,74,75,71,69…

ADC实验

查看VR1链接的丝印&#xff1a;XadcAIN3 设置相关寄存器 使用的是通道3&#xff0c;要设置相应的通道寄存器 #include "exynos_4412.h"int main() {unsigned int AdcValue 0;/*将ADC的精度设置成 12bit*/ADCCON ADCCON | (1 << 16);/*使能ADC的分频器*…

Python Flask+Echarts+sklearn+MySQL(评论情感分析、用户推荐、BI报表)项目分享

Python FlaskEchartssklearnMySQL(评论情感分析、用户推荐、BI报表)项目分享 项目背景&#xff1a; 随着互联网的快速发展和智能手机的普及&#xff0c;人们越来越倾向于在网上查找餐厅、购物中心、酒店和旅游景点等商户的点评和评分信息&#xff0c;以便做出更好的消费决策。…

第三代网关,POE级联蓝牙网关VDB3601,至多可连接38台蓝牙设备

第三代蓝牙网关&#xff0c;网关集成了蓝牙4.2/5.0WiFi无线协议&#xff0c;采用双网口设计&#xff0c;1台主蓝牙网关可级联多个从蓝牙网关设备&#xff0c;至多支持远距离连接和控制38台蓝牙设备的蓝牙网关VDB3601&#xff0c;支持双蓝牙模组、485通信、可兼容4G/Cat.1模块&a…

敏捷项目管理如何做好Sprint Backlog?迭代管理

什么是Sprint Backlog&#xff1f; Sprint Backlog是Scrum的主要工件之一。在Scrum中&#xff0c;团队按照迭代的方式工作&#xff0c;每个迭代称为一个Sprint。在Sprint开始之前&#xff0c;PO会准备好产品Backlog&#xff0c;准备好的产品Backlog应该是经过梳理、估算和优先…

STM32CUBE IDE 使用F407的CCMRAM

F407有64K的CCMRAM闲着怪浪费&#xff0c;用一下 使用STM32CUBE IDE配置。只需要在定义的变量后面加 __attribute__((section(".ccmram") ))即可。不用修改FLASH.LD文件。 举例使用LVGL定义一个大数组并使用&#xff1a; #define MY_DISP_HOR_RES (320)/* Examp…

聊聊智能手表

目录 1.什么是智能手表 2.智能手表的发展过程 3.智能手表有哪些功能 4.智能手表给人类带来的福利 1.什么是智能手表 智能手表是一种智能穿戴设备&#xff0c;结合了传统手表的时间显示功能和智能手机的一些功能。它通常配备有触摸屏、操作系统、处理器、内存、传感器以及与智…

jpg图片太大怎么压缩?这样做轻松压缩图片

图片太大会给存储、分享带来麻烦&#xff0c;但其实现在压缩图片大小也不是什么难事&#xff0c;下面就给大家分享几个一直用的图片压缩方法&#xff0c;包含批量压缩、在线压缩、免费压缩等多种方式&#xff0c;大家按需自取哈~ 方法一&#xff1a;嗨格式压缩大师 这是一个可…

WebApIs 第五天

window对象 BOM&#xff08;浏览器对象模型&#xff09;定时器-延时函数JS执行机制location对象navigator对象histroy对象 本地存储 一.BOM&#xff08;浏览器对象模型&#xff09; ① BOM是浏览器对象模型 window 对象是一个全局对象&#xff0c;也可以说是JavaScript中的…

外企开展中国在线业务的三种网络加速方案:含免ICP备案CDN解决方案

中国作为全球除美国外最大的消费市场&#xff0c;是几乎每个国际化企业都想要深入挖掘的市场&#xff0c;但外国企业在中国开展在线业务需要面临一个比较特殊的挑战&#xff1a;互联网防火墙&#xff08;GFW&#xff09;。为此所有想要在中国市场有所作为的外企都需要首先解决这…

超级品牌,都在打造数据飞轮

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 引入 「收钱吧到账15元。」 从北京大栅栏的糖葫芦铺子&#xff0c;到南京夫子庙的鸭血粉丝汤馆&#xff0c;再到广州珠江畔的早茶店&#xff0c;不知不觉间&#xf…

健康数据抬腕可见,记录每一次心跳,dido Y60智能手表体验

记得最开始的智能手表&#xff0c;功能和手环差不了多少&#xff0c;而随着技术的发展&#xff0c;现在新款的智能手表已经可以全方位测量我们每天的健康数据了。相比于专门的健康测量工具&#xff0c;智能手表用起来更加方便&#xff0c;很多都可以在后台静默监测心率等数据&a…

AWK +iptables+shell实战脚本案例

目录 一、在Centos下安装httpd 查看安装是否成功 重启httpd 查看80端口是否开放 在主机上查询 查看防火墙 在浏览器中查询主机IP地址 查看日志是否生成 二、AWK iptablesshell实战脚本案例 1、封堵扫描器 (1) 开始扫描器 特别注意&#xff1a;在Vim中尽量不要使用空格…

20230815在淘宝的代扫描服务【仅供参考】

20230815在淘宝的代扫描服务【仅供参考】 2023/8/15 12:35 https://item.taobao.com/item.htm?spma21n57.1.0.0.3d47523caCFZ3T&id601206116790&ns1&abbucket4#detail e邦生活服务 https://item.taobao.com/item.htm?_ufju3ku42b4&id629900806906 寄书扫描…

Java基础篇--Number(包装) Math (数学运算)类

目录 Number类 扩展小知识 Math类 实例 Number类 Java中的Number类是一个抽象类&#xff0c;它是所有包装类&#xff08;如Integer、Double、Long等&#xff09;的父类。这个类提供了将基本数据类型&#xff08;如int、double、long等&#xff09;封装为对象&#xff0c;…

小程序-uni-app:hbuildx uni-app 安装 uni-icons 及使用

一、官方文档找到uni-icons uni-app官网 二、下载插件 三、点击“打开HBuildX” 四、选择要安装的项目 五、勾选要安装的插件 六、安装后&#xff0c;项目插件目录 根目录uni_modules目录下增加uni-icons、uni-scss 七、引入组件&#xff0c;使用组件 <uni-icons type&qu…