当前位置: 首页 > news >正文

根据mybatis plus注解动态创建sqlite表和表字段

根据mybatis plus注解动态创建sqlite表和表字段

启动时动态创建sqlite数据库,根据mybatis plus注解动态创建表。如果有新增字段,动态创建字段。


文章目录

  • 根据mybatis plus注解动态创建sqlite表和表字段
  • 一、初始化数据库
    • 1.系统启动时初始化数据库
    • 2.初始化sqlite数据库文件
    • 3.根据mybatis plus注解初始化数据库
    • 4.解析mybatis plus注解提取数据库信息
  • 后记


一、初始化数据库

1.系统启动时初始化数据库

通过@PostConstruct注解在项目启动时调用初始化方法

   @PostConstructpublic void init() throws SQLException, IOException {//初始化数据库createDatabase();//初始化数据库表createTables();}

2.初始化sqlite数据库文件

sqlite放在外部目录下,如果放在resource目录下。打成jar后不好管理和读取。

    @ApiModelProperty("数据源地址")@Value("${spring.datasource.url}")private String sqliteDbPath;/*** 初始化数据库*/private void createDatabase() throws IOException {String sqlite = sqliteDbPath.substring("jdbc:sqlite:".length(), sqliteDbPath.indexOf("?"));File audioStationDbFile = new File(sqlite);log.info("sqlite数据库文件地址:" + audioStationDbFile.getAbsolutePath());if (!audioStationDbFile.getParentFile().exists()) {audioStationDbFile.getParentFile().mkdirs();}if (!audioStationDbFile.exists()) {audioStationDbFile.createNewFile();}}

3.根据mybatis plus注解初始化数据库

    /*** 初始化表*/private void createTables() throws SQLException {List<String> tables = jdbcTemplate.queryForList("SELECT name FROM sqlite_master ", String.class);log.info("已存在的数据库:" + tables);List<String> domainTableNames = new ArrayList<String>();Map<String, Class> classMap = new HashMap<String, Class>();//spring工具类,可以获取指定路径下的全部类ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();try {String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +ClassUtils.convertClassNameToResourcePath(DOMAIN_PACKAGE) + "/*.class";Resource[] resources = resourcePatternResolver.getResources(pattern);//MetadataReader 的工厂类MetadataReaderFactory readerfactory = new CachingMetadataReaderFactory(resourcePatternResolver);for (Resource resource : resources) {//用于读取类信息MetadataReader reader = readerfactory.getMetadataReader(resource);//扫描到的classString classname = reader.getClassMetadata().getClassName();Class<?> clazz = Class.forName(classname);//判断是否有指定主解TableName anno = clazz.getAnnotation(TableName.class);if (anno != null) {//将注解中的类型值作为key,对应的类作为 valuedomainTableNames.add(anno.value());classMap.put(anno.value(), clazz);}}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}
//        log.info("实体类表清单:" + domainTableNames);for (String tableName : domainTableNames) {if (!tables.contains(tableName)) {log.info("数据库[" + tableName + "]不存在,正在创建");String createTableSql = MyBatisPlusSuppotSqliteInit.getInstance().createTable(classMap.get(tableName));jdbcTemplate.update(createTableSql);} else {//存在表,检查字段是否存在List<Map<String, Object>> sqliteTableMapList = jdbcTemplate.queryForList("PRAGMA  table_info(" + tableName + ")");List<SqliteTableStructureDto> sqliteTableStructureDto = new ArrayList<>();for (Map<String, Object> map : sqliteTableMapList) {SqliteTableStructureDto dto = new SqliteTableStructureDto();BeanUtil.copyProperties(map, dto);dto.setDfltValue(null != map.get("dflt_value") ? String.valueOf(map.get("dflt_value")) : null);dto.setNotNull("1".equals(map.get("notnull")) ? true : false);dto.setPk("1".equals(map.get("pk")) ? true : false);sqliteTableStructureDto.add(dto);}sqliteTableMapList = null;List<String> createFieldSqlList = MyBatisPlusSuppotSqliteInit.getInstance().createField(JsMobileUser.class, sqliteTableStructureDto);String createFieldSql = createFieldSqlList.stream().collect(Collectors.joining(";\n"));jdbcTemplate.update(createFieldSql);}}//初始化参数int userCount = jdbcTemplate.queryForObject("SELECT COUNT(1) FROM js_mobile_user", Integer.class);if (userCount <= 0) {jdbcTemplate.update("INSERT INTO `xxx`(`xxx`) " +" VALUES ('xxx'");}}
@Data
@NoArgsConstructor
@ApiModel("sqlite表结构字段")
public class SqliteTableStructureDto {@ApiModelProperty("主键id")private String cid;@ApiModelProperty("字段名称")private String name;@ApiModelProperty("字段映射")private String type;@ApiModelProperty("是否允许为空")private boolean notNull;@ApiModelProperty("默认值")private String dfltValue;@ApiModelProperty("是否为主键")private boolean pk;}

4.解析mybatis plus注解提取数据库信息

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.faker.audioStation.model.dto.ModelField;
import com.faker.audioStation.model.dto.SqliteTableStructureDto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;/*** <p>MyBatisPlus支持sqlite初始化建表</p>*/
@Slf4j
public class MyBatisPlusSuppotSqliteInit {/*** 单里模式*/private static MyBatisPlusSuppotSqliteInit myBatisPlusSuppotSqliteInit = null;/*** 私有化构造函数,使用getInstance()去获取实例*/private MyBatisPlusSuppotSqliteInit() {}/*** 获取实例** @return*/public static MyBatisPlusSuppotSqliteInit getInstance() {if (null == myBatisPlusSuppotSqliteInit) {myBatisPlusSuppotSqliteInit = new MyBatisPlusSuppotSqliteInit();}return myBatisPlusSuppotSqliteInit;}@Data@ApiModel("mybatisPlus对象")public class MybatisPlusDto {//表名String tableName = null;//主键对应的字段名String tableId = null;//表字段结构List<ModelField> modelFieldList = new ArrayList();//表字段名称列表List<String> columnList = new ArrayList<String>();}/*** 获取实例** @param clazz* @return*/public MybatisPlusDto getMybatisPlusDto(Class clazz) {MybatisPlusDto mybatisPlusDto = new MybatisPlusDto();//检查实体类是否缺少注解boolean isTableName = clazz.isAnnotationPresent(TableName.class);if (isTableName) {TableName tableNameIn = (TableName) clazz.getAnnotation(TableName.class);if (null == tableNameIn.value() || "".equals(tableNameIn.value())) {throw new RuntimeException("实体类无TableName注解!");}mybatisPlusDto.tableName = tableNameIn.value();}//是否包含注解boolean isTableField = false;boolean isTableId = false;Field[] fields = clazz.getDeclaredFields();for (int i = 0; i < fields.length; i++) {if (isTableField == false) {isTableField = fields[i].isAnnotationPresent(TableField.class);}if (isTableId == false) {isTableId = fields[i].isAnnotationPresent(TableId.class);}if (isTableField && isTableId) {//都找到注解了 就终止break;}}if (!isTableField) {throw new RuntimeException("实体类无TableField注解!");}if (!isTableId) {log.warn("实体类无isTableId注解!");}//获取表结构for (int i = 0; i < fields.length; i++) {ModelField modelField = new ModelField();modelField.setModelName(fields[i].getName());modelField.setModelType(fields[i].getType());boolean annotationPresent = fields[i].isAnnotationPresent(TableField.class);if (annotationPresent) {// 获取注解值String tableField = fields[i].getAnnotation(TableField.class).value();if (null != tableField && !"".equals(tableField)) {modelField.setTableField(tableField.toUpperCase());mybatisPlusDto.columnList.add(tableField.toUpperCase());boolean apiMp = fields[i].isAnnotationPresent(ApiModelProperty.class);if (apiMp) {modelField.setApiModelProperty(fields[i].getAnnotation(ApiModelProperty.class).value());} else {log.debug("类" + clazz.getName() + "的字段" + fields[i].getName() + "没有注解ApiModelProperty");}mybatisPlusDto.modelFieldList.add(modelField);} else {log.warn("属性[" + modelField.getModelName() + "]对应表字段为空!");}} else if (fields[i].isAnnotationPresent(TableId.class)) {// 获取注解值mybatisPlusDto.tableId = fields[i].getAnnotation(TableId.class).value().toUpperCase();if (null != mybatisPlusDto.tableId && !"".equals(mybatisPlusDto.tableId)) {modelField.setTableField(mybatisPlusDto.tableId.toUpperCase());mybatisPlusDto.columnList.add(mybatisPlusDto.tableId.toUpperCase());boolean apiMp = fields[i].isAnnotationPresent(ApiModelProperty.class);if (apiMp) {modelField.setApiModelProperty(fields[i].getAnnotation(ApiModelProperty.class).value());} else {log.debug("类" + clazz.getName() + "的字段" + fields[i].getName() + "没有注解ApiModelProperty");}mybatisPlusDto.modelFieldList.add(modelField);} else {log.warn("属性[" + modelField.getModelName() + "]对应表字段为空!");}}}
//        log.debug("类[" + clazz.getName() + "]的表名为[" + mybatisPlusDto.tableName + "];字段信息为:" + mybatisPlusDto.modelFieldList);return mybatisPlusDto;}/*** 建表** @param clazz*/public String createTable(Class clazz) {MybatisPlusDto dto = this.getMybatisPlusDto(clazz);StringBuffer sql = new StringBuffer();sql.append("CREATE TABLE ").append("\"").append(dto.getTableName()).append("\" (\n");//表字段结构List<ModelField> modelFieldList = dto.getModelFieldList();for (ModelField modelField : modelFieldList) {if (modelField.getTableField().equals(dto.getTableId())) {if (modelField.getModelType().equals(Integer.class)|| modelField.getModelType().equals(int.class)|| modelField.getModelType().equals(Long.class)|| modelField.getModelType().equals(long.class)) {sql.append("\"" + modelField.getTableField() + "\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n");} else {sql.append("\"" + modelField.getTableField() + "\" TEXT NOT NULL PRIMARY KEY,\n");}} else {sql.append("\"" + modelField.getTableField() + "\" ").append(this.getSqliteType(modelField.getModelType())).append(",\n");}}sql.setLength(sql.length() - 2);sql.append(");\n");
//        log.info(sql.toString());return sql.toString();}/*** 转换数据类型为sqlite类型** @param aClass* @return*/private String getSqliteType(Class aClass) {if (aClass.equals(Integer.class)|| aClass.equals(int.class)|| aClass.equals(Long.class)|| aClass.equals(long.class)) {return "INTEGER";}if (aClass.equals(Float.class)|| aClass.equals(float.class)|| aClass.equals(Double.class)|| aClass.equals(double.class)) {return "REAL";}if (aClass.equals(Date.class)) {return "NUMERIC";}if (aClass.equals(String.class)) {return "TEXT";}if (aClass.equals(Boolean.class)|| aClass.equals(boolean.class)) {return "INTEGER";}return "NUMERIC";}/*** 创建表字段** @param clazz* @param sqliteList* @return*/public List<String> createField(Class clazz, List<SqliteTableStructureDto> sqliteList) {List<String> sqlList = new ArrayList<String>();List<String> fields = sqliteList.stream().map(item -> item.getName().toUpperCase()).collect(Collectors.toList());MybatisPlusDto dto = this.getMybatisPlusDto(clazz);StringBuffer sql = new StringBuffer();//表字段结构List<ModelField> modelFieldList = dto.getModelFieldList();for (ModelField modelField : modelFieldList) {if (!fields.contains(modelField.getTableField().toUpperCase())) {log.warn("表[" + dto.getTableName() + "]字段[" + modelField.getTableField().toUpperCase() + "]缺失,正在生成重建sql");sqlList.add("alter table \"" + dto.getTableName() + "\" add \"" + modelField.getTableField().toUpperCase() + "\""+ this.getSqliteType(modelField.getModelType()) + "");}}return sqlList;}
}

后记

一个简单的根据注解的转换,也可以替换成mysql或oracle的写法,不过mysql已经有了mybatis-enhance-actable了,就不要重复造轮子了。


相关文章:

根据mybatis plus注解动态创建sqlite表和表字段

根据mybatis plus注解动态创建sqlite表和表字段 启动时动态创建sqlite数据库&#xff0c;根据mybatis plus注解动态创建表。如果有新增字段&#xff0c;动态创建字段。 文章目录根据mybatis plus注解动态创建sqlite表和表字段一、初始化数据库1.系统启动时初始化数据库2.初始化…...

同步、异步ETL架构的比较

背景介绍&#xff1a; 数据的抽取&#xff0c;转换和加载 (ETL, Extract, Transform, Load) 是构建数据仓库过程中最复杂也是至 关重要的一个步骤&#xff0c;我们通常用两种办法来处理 ETL 流程: 一种是异步(Asynchronous) ETL 方式, 也称为文本文件(Flat file)方式。 另外…...

【机会约束、鲁棒优化】具有排放感知型经济调度中机会约束和鲁棒优化研究【IEEE6节点、IEEE118节点算例】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

用Python帮老叔选出好基金,大赚一笔,老叔专门提着茅台登门道谢

我有个老叔很喜欢买基金&#xff0c;因为不想被割韭菜&#xff0c;所以啥群都没进&#xff0c;全部自己精挑细选。 看着他的一个本子密密麻麻地写了一大堆东西&#xff0c;全是基金的数据分析&#xff0c;一大把年纪了挺不容易的&#xff0c;于是就决定帮他一把。 在跟他详谈…...

ZeroTier实现内网穿透详细教程,无需公网IP,实现异地组网

ZeroTier实现内网穿透详细教程&#xff0c;无需公网IP&#xff0c;实现异地组网ZeroTier1.官网注册账号&#xff0c;创建自己的局域网段2.点击创建好的网络&#xff0c;进入设置界面进行设置3.下载客户端&#xff0c;安装客户端&#xff0c;然后连接到网络中4.加入网络成功后&a…...

电商 SaaS 全渠道实时数据中台最佳实践

摘要&#xff1a;本文整理自聚水潭数据专家张成玉&#xff0c;聚水潭高级数据工程师应圣楚&#xff0c;在 FFA 2022 行业案例专场的分享。本篇内容主要分为四个部分&#xff1a;实时数仓的建设和发展数据中台的产品体系及架构实时计算的实践和优化对实时计算的未来展望Tips&…...

macos ncnn 安装踩坑记录···

安装真麻烦踩了无数坑&#xff0c;官方给的安装教程&#xff1a;macos安装ncnn, 安装过程老是报错&#xff0c;记录一下卡的比较久的&#xff0c;网上也不好找资料的错. 我的电脑&#xff1a; 1. 使用homebrew 的时候失败fatal: not in a git directory Error: Command failed…...

ESP32设备驱动-AM2301(DHT21)温度湿度传感器驱动

AM2301(DHT21)温度湿度传感器驱动 文章目录 AM2301(DHT21)温度湿度传感器驱动1、AM2301(DHT21)介绍2、硬件准备3、软件准备4、驱动实现1、AM2301(DHT21)介绍 AM2301 湿敏电容数字温湿度模块是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温…...

[数据结构]:16-归并排序(顺序表指针实现形式)(C语言实现)

目录 前言 已完成内容 归并排序实现 01-开发环境 02-文件布局 03-代码 01-主函数 02-头文件 03-PSeqListFunction.cpp 04-SortFunction.cpp 结语 前言 此专栏包含408考研数据结构全部内容&#xff0c;除其中使用到C引用外&#xff0c;全为C语言代码。使用C引用主要是…...

React(七):Router基本使用、嵌套路由、编程式导航、路由传参、懒加载

React&#xff08;七&#xff09;一、React-Router的基本使用1.安装和介绍2.路由的配置和跳转3.Navigate的使用4.如果找不到对应的路由路径&#xff1f;二、嵌套路由的用法三、编程式路由导航1.类组件中使用useNavigate2.函数式组件中使用useNavigate四、路由跳转传参1.设置好路…...

Java基础面试题(一)

Java基础面试题 一、面向对象和集合专题 1. 面向对象和面向过程的区别 面向过程&#xff1a;是分析解决问题的步骤&#xff0c;然后用函数把这些步骤一步一步地实现&#xff0c;然后在使用的时候一一调用则可。性能较高&#xff0c;所以单片机、嵌入式开发等一般采用面向过程…...

代码命名规范是一种责任也是一种精神(工匠精神)

代码命名规范之美规范概述命名规范管理类命名BootstrapProcessorManagerHolderFactoryProviderRegistrarEngineServiceTask传播类命名ContextPropagator回调类命名Handler &#xff0c;Callback&#xff0c;Trigger&#xff0c;ListenerAware监控类命名MetricsEstimatorAccumul…...

奇淫技巧:阅读源码时基于一组快捷键,让我们知道身在何方!

一个十分蛋疼的问题 在我们阅读框架底层源码的时候&#xff0c;我们往往会一个方法一个方法的往下翻&#xff0c;翻了很久很快就会有这样的灵魂拷问&#xff1a;我从那个类&#xff08;方法&#xff09;来&#xff0c;我要到哪个&#xff08;类&#xff09;方法中去。这个时候…...

你真的弄懂this指向了吗

前言 在说 this 指向之前&#xff0c;请观察以下代码&#xff0c;并说出它们的输出结果&#xff1a; 第 1 组&#xff1a;标准函数 window.color "red"; let o {color: "blue", }; function sayColor() {console.log(this.color); }sayColor(); // 输…...

阿里云服务器使用教程:使用xshell、xFtp工具连接阿里云服务器(Centos7)并修改Centos7的yum源为阿里镜像源

目录 1、下载并安装xshell、xFtp 2、远程连接阿里云服务器 3、 修改Centos7的yum源为阿里镜像源 1、下载并安装xshell、xFtp XShell可以在Windows界面下来访问远端不同系统下的服务器&#xff0c;从而比较好的达到远程控制终端的目的。它支持 RLOGIN、SFTP、SERIAL、TELNET、…...

一文快速入门 HTML 网页基础

专栏简介: 前端从入门到进阶 题目来源: leetcode,牛客,剑指offer. 创作目标: 记录学习JavaEE学习历程 希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长. 学历代表过去,能力代表现在,学习能力代表未来! 目录 1.HTML 结构 1.1. 认识 HTML 标签 1.2 HTML 文件结构…...

DEJA_VU3D - Cesium功能集 之 100-任意多边形(标绘)

前言 编写这个专栏主要目的是对工作之中基于Cesium实现过的功能进行整合,有自己琢磨实现的,也有参考其他大神后整理实现的,初步算了算现在有差不多实现小140个左右的功能,后续也会不断的追加,所以暂时打算一周2-3更的样子来更新本专栏(每篇博文都会奉上完整demo的源代码,…...

Cadence OrCAD Capture全局修改原理图的非本地库符号的方法图文教程Repalce Catch功能

⏪《上一篇》   🏡《总目录》   ⏩《下一篇》 目录 1,概述2,修改方法2.1,新建本地库2.2,待修改搬入本地库2.3,修改原理图符号2.4,全局更新原理图符号3,总结B站关注“硬小二”浏览更多演示视频 1,概述 在完成原理图设计...

npm包版本号详解

npm包在发布时&#xff0c;需要按照包版本语义化中的约定去更新设置&#xff0c;例如我们常见的1.0.0&#xff0c;1.0.1&#xff0c;0.0.1等这样的版本号&#xff0c;那么这些数字分别代表什么意思呢&#xff1f;下面我们将详细介绍。 npm版本号的组成 一个完整的版本号&…...

ubuntu 系统安装docker——使用docker打包python项目,整个流程介绍

目录 1 安装docker和配置镜像源 2 下载基础镜像 3 通过镜像创建容器 4 制作项目所需的容器 5 容器制作好后打包为镜像 6 镜像备份为.tar文件 7 从其他服务器上恢复镜像 8 docker的其他常用指令 首先科普一下镜像、容器和实例&#xff1b; 镜像&#xff1a;相当于安装包&…...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...

代码随想录刷题day30

1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...