MyBatisPlus新版代码生成器(Velocity模板引擎详解)
文章目录
- 一、Velocity模板引擎
- 1、velocity简介
- 2、快速入门
- 3、基础语法
- 4、注释
- 5、变量
- 6、循环
- 7、条件
- 8、引入资源
- 9、macro宏
- 二、MybatisPlus代码生成器
- 1、MP代码生成器
- 2、自定义velocity模板
- 2.1、MybatisPlus自带模板和变量
- 2.2、公共模板`common.vm`文件
- 2.3、实体模板`entity.java.vm`文件
- 2.4、Mapper接口模板`mapper.java.vm`文件
- 2.5、Mapper.xml模板`mapper.xml.vm`文件
- 2.6、dto模板
- 2.7、Service接口模板`service.java.vm`文件
- 2.8、Service实现类模板`serviceImpl.java.vm`文件
- 2.9、Controller模板`controller.java.vm`文件
- 3、代码生成
- 三、总结
一、Velocity模板引擎
1、velocity简介
Velocity是一个基于Java的模板引擎,可以通过特定的语法获取在java对象的数据 , 填充到模板中,从而实现界面和java代码的分离。
应用场景
- Web应用程序 : 作为为应用程序的视图, 展示数据
- 源代码生成 : Velocity可用于基于模板生成Java源代码
- 自动电子邮件 : 网站注册 , 认证等的电子邮件模板
- 网页静态化 : 基于velocity模板 , 生成静态网页
2、快速入门
引入依赖
org.apache.velocity velocity-engine-core 2.2模板文件:resources目录下test.vm
/** * @author xuchang */ public class MyTest { private ${fieldType} ${fieldName}; }测试类
public class velocityTest { @Test public void test() throws IOException { //1.设置velocity的资源加载类 Properties prop = new Properties(); prop.put("file.resource.loader.class","org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); //2.加载velocity引擎 Velocity.init(prop); //3.加载velocity容器 VelocityContext velocityContext = new VelocityContext(); // 最重要一步,设置模板中填充的数据 velocityContext.put("fieldType","String"); velocityContext.put("fieldName","studentName"); //4.加载velocity模板 Template template = Velocity.getTemplate("test.vm", "utf-8"); //5.合并数据 FileWriter fileWriter = new FileWriter("src/main/java/MyTest.java"); template.merge(velocityContext,fileWriter); //6.释放资源 fileWriter.close(); } }输出结果:
3、基础语法
# 关键字
Velocity关键字都是使用#开头的,如#set、#if、#else、#end、#foreach等。
$ 变量
Velocity变量都是使用$开头的,如:$name、$msg。
{}变量
Velocity对于需要明确表示的Velocity变量,可以使用{}将变量包含起来。如在页面中,需要有$someoneName这种内容,此时为了让Velocity能够区分,可以使用${someone}Name
!变量
如果某个Velocity变量不存在,那么页面中就会显示$xxx的形式,为了避免这种形式,可以在变量名称前加上!如页面中含有$msg,如果msg有值,将显示msg的值;如果不存在就会显示$msg。这是我们不希望看到的,为了把不存在的变量显示为空白,可以使用$!msg。
4、注释
单行注释
##这里写注释
多行注释
#* 这个写注释 可以写多行的注释 *#5、变量
变量定义
#set($root = "www") #set($name = "index") #set($template = "$root/$name") $template
执行输出结果:
www/index
变量赋值
- 赋值的左边必须是一个变量,或者是属性的引用
- 右边可以是:变量引用、字面字符串、属性引用、方法引用、字面数字、数组
#set($name = $bill) ##变量引用 #set($name.pre = "monica") ##字符串 #set($name.last = $address.num) ##属性引用 #set($name.mid = $hotel.find("test",10)) ##方法引用 #set($name.num = 123) ##数字 #set($name.say = ["yes",$my,"yes"]) ##数组(ArrayList) #set($name.maps = {"banana":"good", "beef":"bad"}) ##Map赋值 ## 双引号可实现字符串拼接,假设$var1为abc,则$var为hello abc #set($var = "hello $var1") ## 单引号将不解析其中引用,假设$var1为abc,则$var为hello $var1 #set($var = 'hello $var1')需要注意的是${person.name} 并不是直接访问 person 的 name 属性,而是访问 person 的 getName() 方法,所以${person.name} 和${person.getName()} 是一样的。
6、循环
遍历数组或者集合
#foreach($item in $list) #if(${foreach.count} == 3) #break ##跳出循环 #end #end- $item : 变量名称, 代表遍历的每一项
- #break : 退出循环
- 内置属性 :
- $foreach.index : 获取遍历的索引 , 从0开始
- $foreach.count : 获取遍历的次数 , 从1开始
遍历Map
#foreach($data in $mapData.entrySet()) key:${data.key} value:${data.value} #end7、条件
关系和逻辑操作符:&&并且、||或者、!取反
#if(condition1 && condition2) ...... #elseif(!condition) ...... #else ...... #end
8、引入资源
#include
引入外部资源 , 引入的资源不会被引擎所解析
#include("demo8.vm")输出结果为字符串
demo8.vm
#parse
引入外部资源 , 引入的资源会被引擎所解析
#parse("demo8.vm")输出结果为demo8.vm模板的解析内容
9、macro宏
macro宏指令用于定义一个VTL模板的重复代码块脚本函数(宏)
#macro(宏的名称 $参数1 $参数2 .....) 语句体(即函数体) #end宏的调用
#宏的名称 ($参数1 $参数2 ...)
二、MybatisPlus代码生成器
1、MP代码生成器
com.baomidou mybatis-plus-generator 3.5.1 org.apache.velocity velocity-engine-core 2.3根据MybatisPlus官网https://baomidou.com/pages/779a6e/代码生成(新)配置。主要分为全局配置(作者、输出目录),包配置(实体、service等类的包路径),策略配置(实体、service等类名格式化),模板配置(实体、service等类velocity模板设置),注入配置(设置velocity模板里的变量),模板引擎配置(支持多种模板引擎,默认Veloctiy引擎)。
public class CodeGeneration { public static void main(String[] args) { generation("user_info"); } // module名称 private static final String MODULE_NAME = "xxx-module"; // Controller映射路径前缀 private static final String MAPPING_PREFIX = "user/xxx"; /** * 根据表名生成相应结构代码 * @param tableName 表名 */ public static void generation(String tableName) { FastAutoGenerator.create("jdbc:mysql://localhost:3306/dev","root", "root") /** * 全局配置 */ .globalConfig(builder -> { builder // 作者名称 .author("xuchang") // 启用swagger //.enableSwagger() .disableOpenDir() // 禁止打开输出目录(否则一直弹窗打开文件夹) .dateType(DateType.ONLY_DATE) // 设置时间类型(java.util.date) .fileOverride() // 覆盖已有文件(默认 false) // 指定输出目录 .outputDir(System.getProperty("user.dir") + "/" + MODULE_NAME + "/src/main/java"); }) /** * 包配置 */ .packageConfig(builder -> { builder.entity("entity")// 实体类包名 .parent("com.xc")// 父包名。如果为空,将下面子包名必须写全部, 否则就只需写子包名 .controller("controller")// 控制层包名 .mapper("mapper")// mapper层包名 .other("dto")// 生成dto目录 .service("service")// service层包名 .serviceImpl("service.impl")// service实现类包名 // 自定义mapper.xml文件输出目录 .pathInfo(Collections.singletonMap(OutputFile.mapperXml, System.getProperty("user.dir") + "/" + MODULE_NAME + "/src/main/resources/mapper")); }) /** * 策略配置 */ .strategyConfig(builder -> { // 设置要生成的表名 builder.addInclude(tableName) //.addTablePrefix("sys_")//设置表前缀过滤 /** * 实体配置 */ .entityBuilder() // .superClass(SuperCommomPO.class) // 设置实体类父类-父类中存在的字段不会在实体类中存在 .enableLombok() .naming(NamingStrategy.underline_to_camel)// 数据表映射实体命名策略:默认下划线转驼峰underline_to_camel .columnNaming(NamingStrategy.underline_to_camel)// 表字段映射实体属性命名规则:默认null,不指定按照naming执行 .idType(IdType.AUTO)// 添加全局主键类型 .formatFileName("%s")// 格式化实体名称,%s取消首字母I, /** * mapper配置 */ .mapperBuilder() .enableMapperAnnotation()// 开启mapper注解 .enableBaseResultMap()// 启用xml文件中的BaseResultMap 生成 .enableBaseColumnList()// 启用xml文件中的BaseColumnList .formatMapperFileName("%sMapper")// 格式化Dao类名称 .formatXmlFileName("%sMapper")// 格式化xml文件名称 /** * service配置 */ .serviceBuilder() .formatServiceFileName("%sService")// 格式化 service 接口文件名称 .formatServiceImplFileName("%sServiceImpl")// 格式化 service 接口文件名称 .controllerBuilder() .enableRestStyle(); }) /** * 模板配置 */ .templateConfig(builder -> { builder.entity("/templates/entity.java"); builder.service("/templates/service.java"); builder.serviceImpl("/templates/serviceImpl.java"); builder.mapper("/templates/mapper.java"); builder.mapperXml("/templates/mapper.xml"); builder.controller("/templates/controller.java"); }) /** * 注入配置 */ .injectionConfig(consumer -> { // 自定义模板,如dto Map customFile = new HashMap(); customFile.put("Save" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, tableName) + "Req.java", "/templates/saveReq.java.vm"); customFile.put("Update" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, tableName) + "Req.java", "/templates/updateReq.java.vm"); customFile.put("Select" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, tableName) + "Req.java", "/templates/selectReq.java.vm"); customFile.put("Select" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, tableName) + "Res.java", "/templates/selectRes.java.vm"); consumer.customFile(customFile); // 自定义变量,可以在模板中使用 Map customMap = new HashMap(); customMap.put("mappingPrefix", MAPPING_PREFIX); // Controller映射路径前缀 consumer.customMap(customMap); }) /** * 模板引擎设置 */ .templateEngine(new TimerVelocityTemplateEngine()) .execute(); } }修改dto输出路径,默认系统输出路径会在基础设置的路径上添加表名的一层目录(估计是为了区分某个表下的多个dto类)。
public class TimerVelocityTemplateEngine extends VelocityTemplateEngine { @Override protected void outputCustomFile(Map customFile, TableInfo tableInfo, Map objectMap) { String otherPath = getPathInfo(OutputFile.other); customFile.forEach((key, value) -> { String fileName = String.format((otherPath + File.separator + "%s"), key); outputFile(new File(fileName), objectMap, value); }); } }2、自定义velocity模板
2.1、MybatisPlus自带模板和变量
只需要关注.vm结尾模板即可(属于velocity引擎模板),其他模板是其他模板引擎的模板,这里不做过多介绍。
由于自带模板中有好多不实用的地方(实体不用@Data、service和controller没有基础的增删改查操作),所以我们自己根据Velocity模板引擎语法自己写。
自带变量
模板变量 解释 示例 ${entity} 实体类名 User ${table.name} 数据库表名 user_tb ${table.comment} 数据库表注释 用户信息表 ${table.entityName} 实体类名 User ${table.mapperName} mapper类名 UserMapper ${table.xmlName} xml文件名 UserMapper ${table.serviceName} service接口类名 IUserService ${table.serviceImplName} service实现类名 UserServiceImpl ${table.controllerName} controller类名 UserController ${table.entityPath} 首字母小写的实体类名 user ${package.ModuleName} 模块包名 com.xc ${package.Entity} entity包名 com.xc.entity ${package.Mapper} mapper包名 com.xc.mapper ${package.Xml} mapper映射文件包 com.xc.mapper.xml ${package.Service} service包名 com.xc.service ${package.ServiceImpl} serviceImpl实现类包名 com.xc.service.impl ${package.Controller} controller包名 com.xc.controller ${author} 作者 张三 ${date} 日期 2024-03-24 2.2、公共模板common.vm文件
此模板主要为了动态导包使用,排除"id",“createTime”,“updateTime”,“createBy”,"updateBy"字段,因为这些字段在实体公共父类中,排除的字段做成参数是因为dto里面排除的字段不一样。目前只设置了Date和Bigdecimal,可以看需求添加
## ---导包宏开始(参数:排除展示的字段)--- #macro(importEntityPackage $ignoreFieldList) ## 所有字段类型集合 #set($typelist = [] ) ## ---循环变量所有字段--- #foreach($field in ${table.fields}) ## 循环遍历所有字段,排除忽略的字段,将其他字段添加到typelist集合中 #if(${ignoreFieldList.contains($field.propertyName)}) ## 什么也不干 #else ## 添加到集合中 #set($temp = ${typelist.add($field.propertyType)}) #end #end ## 判断类型集合中存在什么类型,导包什么类型 #if(${typelist.contains("Date")}) import java.util.Date; #end #if(${typelist.contains("BigDecimal")}) import java.math.BigDecimal; #end #end ## ---导包宏结束---2.3、实体模板entity.java.vm文件
这里面内容与自带模板相比,我几乎改了百分之七十,这里我想说的是,可以按照自己的需求来改,需要什么样的父类,直接写死就可以,实体类上的注解也可以自由发挥。
package ${package.Entity}; ## 引入公共资源 #parse("templates/common.vm") ## 引入导包宏 #set($ignoreFieldList = ["id","createTime","updateTime","createBy","updateBy"] ) #importEntityPackage($ignoreFieldList) import com.baomidou.mybatisplus.annotation.TableName; ## 实体公共父类,里面有"id","createTime","updateTime","createBy","updateBy"字段 import com.digital.framework.core.base.SuperCommomPO; import lombok.Data; /** * $!{table.comment} * @author ${author} * @date ${date} */ @Data @TableName("${schemaName}${table.name}") public class ${entity} extends SuperCommomPO { ## ---------- BEGIN 字段循环遍历 ---------- #foreach($field in ${table.fields}) #if(${ignoreFieldList.contains($field.propertyName)}) ## 如果是忽略字段什么都不干 #else #if("$!field.comment" != "") /** * ${field.comment} */ #end private ${field.propertyType} ${field.propertyName}; #end #end ## ---------- END 字段循环遍历 ---------- }2.4、Mapper接口模板mapper.java.vm文件
此模板与官网模板大差不差,他这里会判断mapperAnnotation变量,true就添加@Mapper注解,false就不加,其实我们自己写就可以自己写死类上面加@Mapper注解。
package ${package.Mapper}; import ${package.Entity}.${entity}; import ${superMapperClassPackage}; #if(${mapperAnnotation}) import org.apache.ibatis.annotations.Mapper; #end /** * $!{table.comment}Mapper接口 * @author ${author} * @date ${date} */ #if(${mapperAnnotation}) @Mapper #end public interface ${table.mapperName} extends ${superMapperClass}entity} { }2.5、Mapper.xml模板mapper.xml.vm文件
与官网模板一模一样,二级缓存这里其实可以删除,因为一般也不会,其实整个xml一般都不用,如果有复杂sql也是Mapper接口上加@Select去写了。
#if(${enableCache}) #end #if(${baseResultMap}) #foreach($field in ${table.fields}) #if(${field.keyFlag})##生成主键排在第一位 #end #end #foreach($field in ${table.commonFields})##生成公共字段 #end #foreach($field in ${table.fields}) #if(!${field.keyFlag})##生成普通字段 #end #end #end #if(${baseColumnList}) #foreach($field in ${table.commonFields}) ${field.columnName}, #end ${table.fieldNames} #end2.6、dto模板
新增请求实体模板saveReq.java.vm
新增请求没有主键时间和操作人,所以将它们排除掉。
package ${package.Other}; import lombok.Data; ## 引入公共资源 #parse("templates/common.vm") ## 引入导包宏 #set($ignoreFieldList = ["id","createTime","updateTime","createBy","updateBy"] ) #importEntityPackage($ignoreFieldList) /** * $!{table.comment}新增请求 * @author ${author} * @date ${date} */ @Data public class Save${entity}Req { ## ---------- BEGIN 字段循环遍历 ---------- #foreach($field in ${table.fields}) #if(${ignoreFieldList.contains($field.propertyName)}) ## 如果是忽略字段什么都不干 #else #if("$!field.comment" != "") /** * ${field.comment} */ #end private ${field.propertyType} ${field.propertyName}; #end #end ## ---------- END 字段循环遍历 ---------- }修改请求实体模板updateReq.java.vm
修改请求肯定有主键,所以将其他排除掉。
package ${package.Other}; import lombok.Data; ## 引入公共资源 #parse("templates/common.vm") ## 引入导包宏 #set($ignoreFieldList = ["createTime","updateTime","createBy","updateBy"] ) #importEntityPackage($ignoreFieldList) /** * $!{table.comment}修改请求 * @author ${author} * @date ${date} */ @Data public class Update${entity}Req { ## ---------- BEGIN 字段循环遍历 ---------- #foreach($field in ${table.fields}) #if(${ignoreFieldList.contains($field.propertyName)}) ## 如果是忽略字段什么都不干 #else #if("$!field.comment" != "") /** * ${field.comment} */ #end private ${field.propertyType} ${field.propertyName}; #end #end ## ---------- END 字段循环遍历 ---------- }查询请求实体模板selectReq.java.vm
查询请求条件一般没有主键时间和操作人,所以将它们排除掉。
package ${package.Other}; import lombok.Data; import com.digital.framework.core.base.SuperReq; ## 引入公共资源 #parse("templates/common.vm") ## 引入导包宏 #set($ignoreFieldList = ["id","createTime","updateTime","createBy","updateBy"] ) #importEntityPackage($ignoreFieldList) /** * $!{table.comment}查询请求 * @author ${author} * @date ${date} */ @Data public class Select${entity}Req extends SuperReq { ## ---------- BEGIN 字段循环遍历 ---------- #foreach($field in ${table.fields}) #if(${ignoreFieldList.contains($field.propertyName)}) ## 如果是忽略字段什么都不干 #else #if("$!field.comment" != "") /** * ${field.comment} */ #end private ${field.propertyType} ${field.propertyName}; #end #end ## ---------- END 字段循环遍历 ---------- }查询响应实体模板selectRes.java.vm
查询响应一般有主键创建时间创建人,所以将其他项排除掉。
package ${package.Other}; import lombok.Data; ## 引入公共资源 #parse("templates/common.vm") ## 引入导包宏 #set($ignoreFieldList = ["createTime","createBy"] ) #importEntityPackage($ignoreFieldList) /** * $!{table.comment}查询响应 * @author ${author} * @date ${date} */ @Data public class Select${entity}Res { ## ---------- BEGIN 字段循环遍历 ---------- #foreach($field in ${table.fields}) #if(${ignoreFieldList.contains($field.propertyName)}) ## 如果是忽略字段什么都不干 #else #if("$!field.comment" != "") /** * ${field.comment} */ #end private ${field.propertyType} ${field.propertyName}; #end #end ## ---------- END 字段循环遍历 ---------- }2.7、Service接口模板service.java.vm文件
这里响应实体类Response我是导入我们公司的类,这里可以根据自己的情况自己设置(返回值和导包位置)。
package ${package.Service}; import ${package.Entity}.${entity}; import ${superServiceClassPackage}; import ${package.Other}.Save${entity}Req; import ${package.Other}.Update${entity}Req; import ${package.Other}.Select${entity}Req; import ${package.Other}.Select${entity}Res; import com.digital.framework.core.structure.Response; import com.digital.framework.core.base.SuperId; import com.baomidou.mybatisplus.core.metadata.IPage; import java.util.List; /** * $!{table.comment}服务类 * @author ${author} * @date ${date} */ public interface ${table.serviceName} extends ${superServiceClass}entity} { Response save(Save${entity}Req request); Response update(Update${entity}Req request); Response delete(SuperId superId); Responseentity}Res selectPage(Select${entity}Req request); Responseentity}Res selectList(Select${entity}Req request); }2.8、Service实现类模板serviceImpl.java.vm文件
基础的增删改查操作,这里也是可以根据自己需求来写。
package ${package.ServiceImpl}; import ${package.Entity}.${entity}; import ${package.Mapper}.${table.mapperName}; import ${package.Service}.${table.serviceName}; import ${superServiceImplClassPackage}; import ${package.Other}.Save${entity}Req; import ${package.Other}.Update${entity}Req; import ${package.Other}.Select${entity}Req; import ${package.Other}.Select${entity}Res; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import cn.hutool.core.convert.Convert; import com.digital.framework.core.structure.Response; import com.digital.framework.core.base.SuperId; import com.baomidou.mybatisplus.core.metadata.IPage; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; /** * $!{table.comment}服务实现类 * @author ${author} * @date ${date} */ @Service public class ${table.serviceImplName} extends ${superServiceImplClass}table.mapperName}, ${entity} implements ${table.serviceName} { @Override public Response save(Save${entity}Req request) { ${entity} ${table.entityPath} = Convert.convert(${entity}.class, request); super.save(${table.entityPath}); return Response.success(${table.entityPath}.getId()); } @Override public Response update(Update${entity}Req request) { ${entity} ${table.entityPath} = Convert.convert(${entity}.class, request); super.updateById(${table.entityPath}); return Response.success(); } @Override public Response delete(SuperId superId) { super.removeById(superId.getId()); return Response.success(); } @Override public Responseentity}Res selectPage(Select${entity}Req request) { IPageentity}Res ${table.entityPath}Page = super.lambdaQuery().page(new Page(request.getCurrent(), request.getSize())).convert(item -> { Select${entity}Res ${table.entityPath}Res = Convert.convert(Select${entity}Res.class, item); return ${table.entityPath}Res; }); return Response.success(${table.entityPath}Page); } @Override public Responseentity}Res selectList(Select${entity}Req request) { Listentity}Res ${table.entityPath}List = super.lambdaQuery().list().stream().map(item -> { Select${entity}Res ${table.entityPath}Res = Convert.convert(Select${entity}Res.class, item); return ${table.entityPath}Res; }).collect(Collectors.toList()); return Response.success(${table.entityPath}List); } }2.9、Controller模板controller.java.vm文件
这里就是调用下service的实现,这里也可以根据自己的情况输出些固定日志什么的。
package ${package.Controller}; import org.springframework.web.bind.annotation.RestController; import ${package.Service}.${table.serviceName}; import ${package.Other}.Save${entity}Req; import ${package.Other}.Update${entity}Req; import ${package.Other}.Select${entity}Req; import ${package.Other}.Select${entity}Res; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import com.baomidou.mybatisplus.core.metadata.IPage; import com.digital.framework.core.base.SuperId; import com.digital.framework.core.structure.Response; import java.util.List; ## 设置表描述,如果表名以表结尾则去掉表名 #set ($tableComment = "") #set($end=$table.comment.length() - 1) #if(${table.comment.endsWith("表")}) #set($tableComment = ${table.comment.substring(0,$end)}); #end /** * $!{tableComment}信息控制器 * @author ${author} * @since ${date} */ @RestController public class ${table.controllerName} { @Autowired private ${entity}Service ${table.entityPath}Service; /** * 新增$!{tableComment}信息 */ @PostMapping("/${mappingPrefix}/save") public Response save(@RequestBody Save${entity}Req request) { return this.${table.entityPath}Service.save(request); } /** * 修改$!{tableComment}信息 */ @PostMapping("/${mappingPrefix}/update") public Response update(@RequestBody Update${entity}Req request) { return this.${table.entityPath}Service.update(request); } /** * 删除$!{tableComment}信息 */ @PostMapping("/${mappingPrefix}/delete") public Response delete(@RequestBody SuperId superId) { return this.${table.entityPath}Service.delete(superId); } /** * 分页查询$!{tableComment}信息 */ @PostMapping("/${mappingPrefix}/selectPage") public Responseentity}Res selectPage(@RequestBody Select${entity}Req request) { return this.${table.entityPath}Service.selectPage(request); } /** * 查询$!{tableComment}信息 */ @PostMapping("/${mappingPrefix}/selectList") public Responseentity}Res selectList(@RequestBody Select${entity}Req request) { return this.${table.entityPath}Service.selectList(request); } }3、代码生成
三、总结
根据Velocity模板语法随意修改实体类、service接口实现类、controller以及添加自定义dto对象,这样一来每次新需求来了,建表以后,基础的增删改查就有了,只需要在上面小改下就好了。





