zoukankan      html  css  js  c++  java
  • SpringBlade 生成的代码无注释,但是数据库里面有

    一、问题

    1、SQL脚本

    DROP TABLE IF EXISTS `pms_brand`;
    CREATE TABLE pms_brand
    (	   
    	create_user          bigint comment '创建人',
    	create_time          datetime comment '创建时间',
    	update_user          bigint comment '上次修改人',
    	update_time          datetime comment '上次修改时间',
    	is_deleted           int comment '是否已删除',
    	sort                 int comment '排序',
    	tenant_id            varchar(12) comment '租户id',
    	create_dept          bigint comment '创建部门',
    	
    	id                   bigint not null auto_increment comment '编号',
    	name                 varchar(64) comment '名称',
    	letter               varchar(64) comment '每个汉字的首字母',
    	product_count        int comment '关联产品数量',
    	primary key (id)
    );
    

    2、生成后的代码



    3、这个我自己用MyBatis-Plus的代码生成器生成的



    4、出错的地方在于数据库问题

    原来是数据库备注乱码了,估计是本地以前执行SQL语句的时候忘选择编码了




    二、需要注意的地方

    1、脚本编码问题

    MySQL数据库执行脚本的时候,要注意脚本编码的问题,不然很容易就乱码了


    2、数据库版本问题

    MySQL数据库版本 5.x和6.x的版本相差比较大,jdbc的配置需要注意,注意就是下面的两个参数

    有两个参数需要注意:nullCatalogMeansCurrentallowPublicKeyRetrieval

    原文:
    https://blog.csdn.net/jiaoshaoping/article/details/80748065
    https://blog.csdn.net/Yuriey/article/details/80423504

    可参考默认数据库链接的配置

    #spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    #spring.datasource.url=jdbc:mysql://localhost:3306/blade?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&serverTimezone=GMT%2B8
    #spring.datasource.username=root
    #spring.datasource.password=root
    #author=Blade
    

    3、在线代码生成器的问题

    在使用前需要配置一下数据源,不然就会使用本地的blade数据库,在数据源管理就可以配置。


    4、代码生成器本地调试调试问题

    需要特别注意一下数据库链接地址

    如是在blade-core-develop这个项目运行,数据库的配置在这里'resources/templates/code.properties'

    如果是在org.springblade这个项目运行,就需要在线配置一下数据源的地址

    这里也有一个数据库配置文件,我把源码改过一下,就没有用到这里的这个文件




    三、代码的一些调整

    1、修改的类




    2、BladeCodeGenerator

    /**
     * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
     * <p>
     * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     * <p>
     * http://www.gnu.org/licenses/lgpl.html
     * <p>
     * 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 org.springblade.develop.support;
    
    import com.baomidou.mybatisplus.annotation.DbType;
    import com.baomidou.mybatisplus.annotation.FieldFill;
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.core.toolkit.IdWorker;
    import com.baomidou.mybatisplus.core.toolkit.StringPool;
    import com.baomidou.mybatisplus.core.toolkit.StringUtils;
    import com.baomidou.mybatisplus.generator.AutoGenerator;
    import com.baomidou.mybatisplus.generator.InjectionConfig;
    import com.baomidou.mybatisplus.generator.config.*;
    import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert;
    import com.baomidou.mybatisplus.generator.config.converts.OracleTypeConvert;
    import com.baomidou.mybatisplus.generator.config.converts.PostgreSqlTypeConvert;
    import com.baomidou.mybatisplus.generator.config.po.TableFill;
    import com.baomidou.mybatisplus.generator.config.po.TableInfo;
    import com.baomidou.mybatisplus.generator.config.rules.DateType;
    import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
    import com.baomidou.mybatisplus.generator.config.rules.IColumnType;
    import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    import org.springblade.core.tool.utils.Func;
    import org.springblade.core.tool.utils.StringUtil;
    import org.springblade.develop.constant.DevelopConstant;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.core.io.Resource;
    import org.springframework.core.io.support.PropertiesLoaderUtils;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.*;
    
    /**
     * 代码生成器配置类
     *
     * @author Chill
     */
    
    @Slf4j
    public class BladeCodeGenerator {
    
    
    	// configGlobalConfig 方法使用-----------------------
    	/**
    	 * 代码所在服务名
    	 */
    	private String serviceName = "blade-service";
    	/**
    	 * 租户字段
    	 */
    	private String tenantColumn = "tenant_id";
    	/**
    	 * 是否包含包装器
    	 */
    	private Boolean hasWrapper = Boolean.FALSE;
    	/**
    	 * 输出路径
    	 */
    	private String outputDir;
    	/**
    	 * 是否启用swagger
    	 */
    	private Boolean isSwagger2 = Boolean.TRUE;
    	/**
    	 * 作者
    	 */
    	private String author;
    
    
    	// configDataSource 方法使用-----------------------
    	/**
    	 * 数据库驱动名
    	 */
    	private String driverName;
    	/**
    	 * 数据库链接地址
    	 */
    	private String url;
    	/**
    	 * 数据库用户名
    	 */
    	private String username;
    	/**
    	 * 数据库密码
    	 */
    	private String password;
    
    
    	// configStrategyConfig 方法使用-----------------------
    	/**
    	 * 需要去掉的表前缀
    	 */
    	private String[] tablePrefix = {"blade_"};
    	/**
    	 * 需要生成的表名(两者只能取其一)
    	 */
    	private String[] includeTables = {};
    	/**
    	 * 需要排除的表名(两者只能取其一)
    	 */
    	private String[] excludeTables = {};
    	/**
    	 * 是否包含基础业务字段
    	 */
    	private Boolean hasSuperEntity = Boolean.FALSE;
    	/**
    	 * 基础业务字段
    	 */
    	private String[] superEntityColumns = {"create_time" , "create_user" , "create_dept" , "update_time" , "update_user" , "status" , "is_deleted"};
    
    
    	// configPackageConfig 方法使用-----------------------
    	/**
    	 * 代码生成的包名
    	 */
    	private String packageName = "org.springblade.test";
    
    
    	// configPackageConfig 方法使用-----------------------
    	/**
    	 * 代码模块名称
    	 */
    	private String codeName;
    	/**
    	 * 代码所在系统
    	 */
    	private String systemName = DevelopConstant.SWORD_NAME;
    	/**
    	 * 前端代码生成的地址
    	 */
    	private String packageWebDir;
    
    
    	// getOutputDir 方法使用-----------------------
    	/**
    	 * 后端代码生成的地址
    	 */
    	private String packageDir;
    
    
    
    	/**
    	 * CodeGenerator这个类使用
    	 * @param codeName 代码模块名称
    	 * @param serviceName 代码所在服务名
    	 * @param systemName 代码所在系统
    	 * @param packageName 代码生成的包名
    	 * @param packageWebDir 前端代码生成的地址
    	 * @param tablePrefix 需要去掉的表前缀
    	 * @param includeTables 需要生成的表名(两者只能取其一)
    	 * @param excludeTables 需要排除的表名(两者只能取其一)
    	 * @param hasSuperEntity 是否包含基础业务字段
    	 * @param superEntityColumns 基础业务字段
    	 */
    	public BladeCodeGenerator(String codeName,String serviceName,String systemName,String packageName
    		,String packageWebDir,String[] tablePrefix,String[] includeTables,String[] excludeTables
    		,boolean hasSuperEntity,String[] superEntityColumns) {
    		this.codeName=codeName;
    		this.serviceName=serviceName;
    		this.systemName=systemName;
    		this.packageName=packageName;
    		this.packageWebDir=packageWebDir;
    		this.tablePrefix=tablePrefix;
    		this.includeTables=includeTables;
    		this.excludeTables=excludeTables;
    		this.hasSuperEntity=hasSuperEntity;
    		this.superEntityColumns=superEntityColumns;
    
    		// 从配置文件获取数据库链接
    		Properties props = getProperties();
    		this.driverName = props.getProperty("spring.datasource.driver-class-name");
    		this.url = props.getProperty("spring.datasource.url");
    		this.username = props.getProperty("spring.datasource.username");
    		this.password = props.getProperty("spring.datasource.password");
    		this.author = props.getProperty("author");
    	}
    
    	/**
    	 * CodeController类使用
    	 * @param driverName 数据库驱动名
    	 * @param url 数据库链接地址
    	 * @param username 数据库用户名
    	 * @param password 数据库密码
    	 * @param systemName 代码所在系统
    	 * @param serviceName 代码所在服务名
    	 * @param packageName 代码生成的包名
    	 * @param packageDir 后端代码生成的地址
    	 * @param packageWebDir 前端代码生成的地址
    	 * @param tablePrefix 需要去掉的表前缀
    	 * @param includeTables 需要生成的表名(两者只能取其一)
    	 * @param hasSuperEntity 需要排除的表名(两者只能取其一)
    	 * @param hasWrapper 是否包含基础业务字段
    	 */
    	public BladeCodeGenerator(String driverName,String url,String username,String password
    		,String systemName,String serviceName,String packageName,String packageDir,String packageWebDir
    		,String[] tablePrefix,String[] includeTables,boolean hasSuperEntity,boolean hasWrapper){
    		this.driverName=driverName;
    		this.url=url;
    		this.username=username;
    		this.password=password;
    		this.systemName=systemName;
    		this.serviceName=serviceName;
    		this.packageName=packageName;
    		this.packageDir=packageDir;
    		this.packageWebDir=packageWebDir;
    		this.tablePrefix=tablePrefix;
    		this.includeTables=includeTables;
    		this.hasSuperEntity=hasSuperEntity;
    		this.hasWrapper=hasWrapper;
    
    		//其它值的初始化
    		this.outputDir = getOutputDir();
    	}
    
    
    
    	/**
    	 * 自定义生成文件配置
    	 *
    	 * @return
    	 */
    	private InjectionConfig configCustomerConfig() {
    		String servicePackage = serviceName.split("-").length > 1 ? serviceName.split("-")[1] : serviceName;
    		// 自定义配置
    		Map<String, Object> map = new HashMap<>(16);
    		InjectionConfig config = new InjectionConfig() {
    			@Override
    			public void initMap() {
    				map.put("codeName" , codeName);
    				map.put("serviceName" , serviceName);
    				map.put("servicePackage" , servicePackage);
    				map.put("servicePackageLowerCase" , servicePackage.toLowerCase());
    				map.put("tenantColumn" , tenantColumn);
    				map.put("hasWrapper" , hasWrapper);
    				this.setMap(map);
    			}
    		};
    		List<FileOutConfig> focList = new ArrayList<>();
    		focList.add(new FileOutConfig("/templates/sql/menu.sql.vm") {
    			@Override
    			public String outputFile(TableInfo tableInfo) {
    				map.put("entityKey" , (tableInfo.getEntityName().toLowerCase()));
    				map.put("menuId" , IdWorker.getId());
    				map.put("addMenuId" , IdWorker.getId());
    				map.put("editMenuId" , IdWorker.getId());
    				map.put("removeMenuId" , IdWorker.getId());
    				map.put("viewMenuId" , IdWorker.getId());
    				return getOutputDir() + "/" + "/sql/" + tableInfo.getEntityName().toLowerCase() + ".menu.mysql";
    			}
    		});
    		focList.add(new FileOutConfig("/templates/entityVO.java.vm") {
    			@Override
    			public String outputFile(TableInfo tableInfo) {
    				return getOutputDir() + "/" + packageName.replace("." , "/") + "/" + "vo" + "/" + tableInfo.getEntityName() + "VO" + StringPool.DOT_JAVA;
    			}
    		});
    		focList.add(new FileOutConfig("/templates/entityDTO.java.vm") {
    			@Override
    			public String outputFile(TableInfo tableInfo) {
    				return getOutputDir() + "/" + packageName.replace("." , "/") + "/" + "dto" + "/" + tableInfo.getEntityName() + "DTO" + StringPool.DOT_JAVA;
    			}
    		});
    		if (hasWrapper) {
    			focList.add(new FileOutConfig("/templates/wrapper.java.vm") {
    				@Override
    				public String outputFile(TableInfo tableInfo) {
    					return getOutputDir() + "/" + packageName.replace("." , "/") + "/" + "wrapper" + "/" + tableInfo.getEntityName() + "Wrapper" + StringPool.DOT_JAVA;
    				}
    			});
    		}
    		if (Func.isNotBlank(packageWebDir)) {
    			if (Func.equals(systemName, DevelopConstant.SWORD_NAME)) {
    				focList.add(new FileOutConfig("/templates/sword/action.js.vm") {
    					@Override
    					public String outputFile(TableInfo tableInfo) {
    						return getOutputWebDir() + "/actions" + "/" + tableInfo.getEntityName().toLowerCase() + ".js";
    					}
    				});
    				focList.add(new FileOutConfig("/templates/sword/model.js.vm") {
    					@Override
    					public String outputFile(TableInfo tableInfo) {
    						return getOutputWebDir() + "/models" + "/" + tableInfo.getEntityName().toLowerCase() + ".js";
    					}
    				});
    				focList.add(new FileOutConfig("/templates/sword/service.js.vm") {
    					@Override
    					public String outputFile(TableInfo tableInfo) {
    						return getOutputWebDir() + "/services" + "/" + tableInfo.getEntityName().toLowerCase() + ".js";
    					}
    				});
    				focList.add(new FileOutConfig("/templates/sword/list.js.vm") {
    					@Override
    					public String outputFile(TableInfo tableInfo) {
    						return getOutputWebDir() + "/pages" + "/" + StringUtil.upperFirst(servicePackage) + "/" + tableInfo.getEntityName() + "/" + tableInfo.getEntityName() + ".js";
    					}
    				});
    				focList.add(new FileOutConfig("/templates/sword/add.js.vm") {
    					@Override
    					public String outputFile(TableInfo tableInfo) {
    						return getOutputWebDir() + "/pages" + "/" + StringUtil.upperFirst(servicePackage) + "/" + tableInfo.getEntityName() + "/" + tableInfo.getEntityName() + "Add.js";
    					}
    				});
    				focList.add(new FileOutConfig("/templates/sword/edit.js.vm") {
    					@Override
    					public String outputFile(TableInfo tableInfo) {
    						return getOutputWebDir() + "/pages" + "/" + StringUtil.upperFirst(servicePackage) + "/" + tableInfo.getEntityName() + "/" + tableInfo.getEntityName() + "Edit.js";
    					}
    				});
    				focList.add(new FileOutConfig("/templates/sword/view.js.vm") {
    					@Override
    					public String outputFile(TableInfo tableInfo) {
    						return getOutputWebDir() + "/pages" + "/" + StringUtil.upperFirst(servicePackage) + "/" + tableInfo.getEntityName() + "/" + tableInfo.getEntityName() + "View.js";
    					}
    				});
    			} else if (Func.equals(systemName, DevelopConstant.SABER_NAME)) {
    				focList.add(new FileOutConfig("/templates/saber/api.js.vm") {
    					@Override
    					public String outputFile(TableInfo tableInfo) {
    						return getOutputWebDir() + "/api" + "/" + servicePackage.toLowerCase() + "/" + tableInfo.getEntityName().toLowerCase() + ".js";
    					}
    				});
    				focList.add(new FileOutConfig("/templates/saber/crud.vue.vm") {
    					@Override
    					public String outputFile(TableInfo tableInfo) {
    						return getOutputWebDir() + "/views" + "/" + servicePackage.toLowerCase() + "/" + tableInfo.getEntityName().toLowerCase() + ".vue";
    					}
    				});
    			}
    		}
    		config.setFileOutConfigList(focList);
    		return config;
    	}
    
    	/**
    	 * 全局配置
    	 *
    	 * @return
    	 */
    	private GlobalConfig configGlobalConfig() {
    		GlobalConfig config = new GlobalConfig();
    		config.setOutputDir(this.outputDir);
    		config.setAuthor(this.author);
    		config.setFileOverride(true);
    		config.setOpen(false);
    		config.setActiveRecord(false);
    		config.setEnableCache(false);
    		config.setBaseResultMap(true);
    		config.setBaseColumnList(true);
    		config.setMapperName("%sMapper");
    		config.setXmlName("%sMapper");
    		config.setServiceName("I%sService");
    		config.setServiceImplName("%sServiceImpl");
    		config.setControllerName("%sController");
    		config.setSwagger2(isSwagger2);
    		return config;
    	}
    
    	/**
    	 * 数据源配置
    	 *
    	 * @return
    	 */
    	private DataSourceConfig configDataSource() {
    		DataSourceConfig config = new DataSourceConfig();
    
    		if (StringUtil.containsAny(this.driverName, DbType.MYSQL.getDb())) {
    			config.setDbType(DbType.MYSQL);
    			config.setTypeConvert(new MySqlTypeConvert());
    		} else if (StringUtil.containsAny(this.driverName, DbType.POSTGRE_SQL.getDb())) {
    			config.setDbType(DbType.POSTGRE_SQL);
    			config.setTypeConvert(new PostgreSqlTypeConvert());
    		} else {
    			config.setDbType(DbType.ORACLE);
    			config.setTypeConvert(new OracleTypeConvert());
    		}
    		config.setDriverName(this.driverName);
    		config.setUrl(this.url);
    		config.setUsername(this.username);
    		config.setPassword(this.password);
    		return config;
    	}
    
    	/**
    	 * 策略配置
    	 *
    	 * @return
    	 */
    	private StrategyConfig configStrategyConfig() {
    		// 策略配置
    		StrategyConfig config = new StrategyConfig();
    		// config.setCapitalMode(true);// 全局大写命名
    		// config.setDbColumnUnderline(true);//全局下划线命名
    		config.setNaming(NamingStrategy.underline_to_camel);
    		config.setColumnNaming(NamingStrategy.underline_to_camel);
    		config.setTablePrefix(tablePrefix);
    		if (includeTables.length > 0) {
    			config.setInclude(includeTables);
    		}
    		if (excludeTables.length > 0) {
    			config.setExclude(excludeTables);
    		}
    		if (hasSuperEntity) {
    			config.setSuperEntityClass("org.springblade.core.mp.base.BaseEntity");
    			config.setSuperEntityColumns(superEntityColumns);
    			config.setSuperServiceClass("org.springblade.core.mp.base.BaseService");
    			config.setSuperServiceImplClass("org.springblade.core.mp.base.BaseServiceImpl");
    		} else {
    			config.setSuperServiceClass("com.baomidou.mybatisplus.extension.service.IService");
    			config.setSuperServiceImplClass("com.baomidou.mybatisplus.extension.service.impl.ServiceImpl");
    		}
    		// 自定义 controller 父类
    		config.setSuperControllerClass("org.springblade.core.boot.ctrl.BladeController");
    		config.setEntityBuilderModel(false);
    		config.setEntityLombokModel(true);
    		config.setControllerMappingHyphenStyle(true);
    
    		return config;
    	}
    
    	/**
    	 * 包名策略配置
    	 *
    	 * @return
    	 */
    	private PackageConfig configPackageConfig() {
    		// 包配置
    		PackageConfig config = new PackageConfig();
    		// 控制台扫描
    		config.setModuleName(null);
    		config.setParent(packageName);
    		config.setController("controller");
    		config.setEntity("entity");
    		config.setXml("mapper");
    		return config;
    	}
    
    
    
    	/**
    	 * 获取配置文件
    	 *
    	 * @return 配置Props
    	 */
    	private Properties getProperties() {
    		// 读取配置文件
    		Resource resource = new ClassPathResource("/templates/code.properties");
    		Properties props = new Properties();
    		try {
    			props = PropertiesLoaderUtils.loadProperties(resource);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    		return props;
    	}
    
    	/**
    	 * 生成到项目中
    	 *
    	 * @return outputDir
    	 */
    	public String getOutputDir() {
    		return (Func.isBlank(packageDir) ? System.getProperty("user.dir") + "/blade-ops/blade-develop" : packageDir) + "/src/main/java";
    	}
    
    	/**
    	 * 生成到Web项目中
    	 *
    	 * @return outputDir
    	 */
    	public String getOutputWebDir() {
    		return (Func.isBlank(packageWebDir) ? System.getProperty("user.dir") : packageWebDir) + "/src";
    	}
    
    	/**
    	 * 页面生成的文件名
    	 */
    	private String getGeneratorViewPath(String viewOutputDir, TableInfo tableInfo, String suffixPath) {
    		String name = StringUtils.firstToLowerCase(tableInfo.getEntityName());
    		String path = viewOutputDir + "/" + name + "/" + name + suffixPath;
    		File viewDir = new File(path).getParentFile();
    		if (!viewDir.exists()) {
    			viewDir.mkdirs();
    		}
    		return path;
    	}
    
    
    
    	public void run() {
    		AutoGenerator mpg = new AutoGenerator();
    		mpg.setGlobalConfig(configGlobalConfig());
    		mpg.setDataSource(configDataSource());
    		mpg.setStrategy(configStrategyConfig());
    		mpg.setPackageInfo(configPackageConfig());
    		mpg.setCfg(configCustomerConfig());
    		mpg.execute();
    	}
    
    }
    
    

    3、CodeGenerator

    public static void run() {
    	BladeCodeGenerator generator = new BladeCodeGenerator(
    		CODE_NAME,SERVICE_NAME,SYSTEM_NAME,PACKAGE_NAME
    		,PACKAGE_WEB_DIR,TABLE_PREFIX,INCLUDE_TABLES
    		,EXCLUDE_TABLES,HAS_SUPER_ENTITY,SUPER_ENTITY_COLUMNS
    	);
    	generator.run();
    }
    

    4、CodeController

    /**
     * 代码生成
     */
    @PostMapping("/gen-code")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "代码生成" , notes = "传入ids")
    public R genCode(@ApiParam(value = "主键集合" , required = true) @RequestParam String ids, @RequestParam(defaultValue = "sword") String system) {
    	Collection<Code> codes = codeService.listByIds(Func.toLongList(ids));
    	codes.forEach(code -> {
    
    		String driverName = "";
    		String url = "";
    		String username = "";
    		String password = "";
    		String systemName = "";
    		String serviceName = "";
    		String packageName = "";
    		String packageDir = "";
    		String packageWebDir = "";
    		String[] tablePrefix = null;
    		String[] includeTables = null;
    		boolean hasSuperEntity = false;
    		boolean hasWrapper = false;
    
    		// 设置数据源
    		Datasource datasource = datasourceService.getById(code.getDatasourceId());
    		driverName = datasource.getDriverClass();
    		url = datasource.getUrl();
    		username = datasource.getUsername();
    		password = datasource.getPassword();
    
    		// 设置基础配置
    		systemName = system;
    		serviceName = code.getServiceName();
    		packageName = code.getPackageName();
    		packageDir = code.getApiPath();
    		packageWebDir = code.getWebPath();
    		tablePrefix = Func.toStrArray(code.getTablePrefix());
    		includeTables = Func.toStrArray(code.getTableName());
    		hasSuperEntity = code.getBaseMode() == 2;
    		hasWrapper = code.getWrapMode() == 2;
    
    		//构造函数初始化
    		BladeCodeGenerator generator = new BladeCodeGenerator(driverName, url, username, password
    			, systemName, serviceName, packageName, packageDir, packageWebDir
    			, tablePrefix, includeTables, hasSuperEntity, hasWrapper
    		);
    
    		generator.run();
    	});
    	return R.success("代码生成成功");
    }
    

  • 相关阅读:
    [转载] IE8+兼容小结
    vue添加,删除内容
    vue跳转的两种方法
    checked 完整版全选,单选,反选
    网页特殊符号HTML代码大全
    利用css 实现 视觉差效果
    css 自定义滚动条样式
    js 模拟键盘
    css 动画 transition和animation
    浅谈浏览器垃圾回收机制
  • 原文地址:https://www.cnblogs.com/guxingy/p/13470437.html
Copyright © 2011-2022 走看看