2021SC@SDUSC
一、概述
在老年健康管理系统中,有很多工具类,这些工具类在项目中扮演了关键的角色,很多功能都利用这些工具类实现,经过小组成员的讨论和交流,本篇博客主要介绍老年健康管理系统中的工具类。理解这些工具类,有利于我们理解整个系统的一些实现。
本项目的工具类主要有以下几个
generater项目的utils包下的两个工具类
tools项目下的search.lucene.util包下的几个工具类
gate项目下utils包下的工具类
common项目下的几个工具类
在本篇博客中,主要针对其中的几个类进行分析,在下一篇博客中,还会对剩下的几个类进行剖析
二、主要代码
2.1 DateUtils
DateUtil类的作用比较简单,主要提供了对日期进行格式化的方法
可以看到,在DataUtil类中定义了两个用final修饰符修饰的常量,一个是DATE_PATTERN,一个是DATE_TIME_PATTERN。分别规定了日期和含时间日期的类型
定义了两个方法(重载的方法),一个是format(Date date)方法,另一个是format(Date date, String pattern),即在其他程序中,既可以调用已经准备好的样式对日期进行格式化,也可根据具体的需要调用程序。
这个类底层的实现使用了Java的Date类
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateUtils {
/** 时间格式(yyyy-MM-dd) */
public final static String DATE_PATTERN = "yyyy-MM-dd";
/** 时间格式(yyyy-MM-dd HH:mm:ss) */
public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static String format(Date date) {
return format(date, DATE_PATTERN);
}
public static String format(Date date, String pattern) {
if(date != null){
SimpleDateFormat df = new SimpleDateFormat(pattern);
return df.format(date);
}
return null;
}
}
2.2 GeneratorUtils
这个类的主要作用是,将项目中的.vm文件生成对应的代码。.vm文件是velocity模板引擎的一种页面控制文件,而velocity是一个基于Java的模板引擎,他可以让程序员使用简单的模板语言引用Java代码定义的对象,实现界面和 Java 代码的分离。在本项目中,velocity模板主要用于生成Java代码
首先我们可以看到,该类中引入了Java中关于IO读取和Zip读取的几个核心类
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
接下来我们查看构造方法
在构造方法中,首先创建了一个ArrayList对象,接着将.vm文件的路径加入这个List集合中,在对应的路径下,我们可以看到这些.vm文件
public static List<String> getTemplates() {
List<String> templates = new ArrayList<String>();
templates.add("template/index.js.vm");
templates.add("template/index.vue.vm");
templates.add("template/mapper.xml.vm");
templates.add("template/biz.java.vm");
templates.add("template/entity.java.vm");
templates.add("template/mapper.java.vm");
templates.add("template/controller.java.vm");
return templates;
}
接下来我们看到generatorCode方法
在这个方法里需要传入表名的Map,列名等信息,在这个方法中,首先对这些信息(表信息,列信息等)进行了初始化操作,接着设置velocity资源加载器,并将对应的信息添加到模板中。关于方法中具体语句的细节,有相应的注释进行说明。
/**
* 生成代码
*/
public static void generatorCode(Map<String, String> table,
List<Map<String, String>> columns, ZipOutputStream zip, String author, String path, String mainModule, String tablePrefix) {
//配置信息
Configuration config = getConfig();
//表信息
TableEntity tableEntity = new TableEntity();
tableEntity.setTableName(table.get("tableName"));
tableEntity.setComments(table.get("tableComment"));
//表名转换成Java类名
String className = tableToJava(tableEntity.getTableName(), StringUtils.isBlank(tablePrefix) ? config.getString("tablePrefix") : tablePrefix);
tableEntity.setClassName(className);
tableEntity.setClassname(StringUtils.uncapitalize(className));
//列信息
List<ColumnEntity> columsList = new ArrayList<>();
for (Map<String, String> column : columns) {
ColumnEntity columnEntity = new ColumnEntity();
columnEntity.setColumnName(column.get("columnName"));
columnEntity.setDataType(column.get("dataType"));
columnEntity.setComments(column.get("columnComment"));
columnEntity.setExtra(column.get("extra"));
//列名转换成Java属性名
String attrName = columnToJava(columnEntity.getColumnName());
columnEntity.setAttrName(attrName);
columnEntity.setAttrname(StringUtils.uncapitalize(attrName));
//列的数据类型,转换成Java类型
String attrType = config.getString(columnEntity.getDataType(), "unknowType");
columnEntity.setAttrType(attrType);
//是否主键
if ("PRI".equalsIgnoreCase(column.get("columnKey")) && tableEntity.getPk() == null) {
tableEntity.setPk(columnEntity);
}
columsList.add(columnEntity);
}
tableEntity.setColumns(columsList);
//没主键,则第一个字段为主键
if (tableEntity.getPk() == null) {
tableEntity.setPk(tableEntity.getColumns().get(0));
}
//设置velocity资源加载器
Properties prop = new Properties();
prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
Velocity.init(prop);
//封装模板数据
Map<String, Object> map = new HashMap<>();
map.put("tableName", tableEntity.getTableName());
map.put("comments", tableEntity.getComments());
map.put("pk", tableEntity.getPk());
map.put("className", tableEntity.getClassName());
map.put("classname", tableEntity.getClassname());
map.put("pathName", tableEntity.getClassname().toLowerCase());
map.put("columns", tableEntity.getColumns());
map.put("package", path);
map.put("author", author);
map.put("email", config.getString("email"));
map.put("datetime", DateUtils.format(new Date(), DateUtils.DATE_TIME_PATTERN));
map.put("moduleName", mainModule);
map.put("secondModuleName", toLowerCaseFirstOne(className));
VelocityContext context = new VelocityContext(map);
//获取模板列表
List<String> templates = getTemplates();
for (String template : templates) {
//渲染模板
StringWriter sw = new StringWriter();
Template tpl = Velocity.getTemplate(template, "UTF-8");
tpl.merge(context, sw);
try {
//添加到zip
zip.putNextEntry(new ZipEntry(getFileName(template, tableEntity.getClassName(), path, mainModule)));
IOUtils.write(sw.toString(), zip, "UTF-8");
IOUtils.closeQuietly(sw);
zip.closeEntry();
} catch (IOException e) {
throw new RuntimeException("渲染模板失败,表名:" + tableEntity.getTableName(), e);
}
}
}
接下来我们看到下面3个方法
这三个方法在上面的方法中有被调用,第一个方法的作用是将列名转换为Java属性名。第二个方法的作用是将表名转换成Java属性名。在第二个方法中,调用了第一个方法。第三个方法的作用是获取配置信息。
第一个方法中,使用了replace方法,将表名中的_替换为空字符串。关于capitalizeFully方法,我们通过查看它源码的注释可以发现,该方法的作用是:将 String 中所有分隔符分隔的单词转换为大写单词,即每个单词由一个标题字符和一系列小写字符组成。分隔符代表一组被理解为分隔单词的字符。第一个字符串字符和分隔符后的第一个非分隔符字符将大写。
/**
* 列名转换成Java属性名
*/
public static String columnToJava(String columnName) {
return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
}
/**
* 表名转换成Java类名
*/
public static String tableToJava(String tableName, String tablePrefix) {
if (StringUtils.isNotBlank(tablePrefix)) {
tableName = tableName.replace(tablePrefix, "");
}
return columnToJava(tableName);
}
/**
* 获取配置信息
*/
public static Configuration getConfig() {
try {
return new PropertiesConfiguration("generator.properties");
} catch (ConfigurationException e) {
throw new RuntimeException("获取配置文件失败,", e);
}
}
接下来这个方法的作用是获取文件名
这里的template,对应的就是构造方法中对应的template链表的一个对象,这个方法,在上面的generatorCode方法中被调用,目的是获取文件名。
在本方法中,源码首先使用字符串拼接的方式,用File.separator拼接出对应的包名,接着又拼接出包的前缀。之后通过isNotBlank判断包名是否为空(或者只有空格),接着通过if语句,判断该字符串的具体内容,根据对应的情况返回不同的文件名
/**
* 获取文件名
*/
public static String getFileName(String template, String className, String packageName, String moduleName) {
String packagePath = "main" + File.separator + "java" + File.separator;
String frontPath = "ui" + File.separator;
if (StringUtils.isNotBlank(packageName)) {
packagePath += packageName.replace(".", File.separator) + File.separator;
}
if (template.contains("index.js.vm")) {
return frontPath + "api" + File.separator + moduleName + File.separator + toLowerCaseFirstOne(className) + File.separator + "index.js";
}
if (template.contains("index.vue.vm")) {
return frontPath + "views" + File.separator + moduleName + File.separator + toLowerCaseFirstOne(className) + File.separator + "index.vue";
}
if (template.contains("biz.java.vm")) {
return packagePath + "biz" + File.separator + className + "Biz.java";
}
if (template.contains("mapper.java.vm")) {
return packagePath + "mapper" + File.separator + className + "Mapper.java";
}
if (template.contains("entity.java.vm")) {
return packagePath + "entity" + File.separator + className + ".java";
}
if (template.contains("controller.java.vm")) {
return packagePath + "rest" + File.separator + className + "Controller.java";
}
if (template.contains("mapper.xml.vm")) {
return "main" + File.separator + "resources" + File.separator + "mapper" + File.separator + className + "Mapper.xml";
}
return null;
}
本类中的最后一个方法
本类中的最后一个方法是首字母转小写的方法,本方法调用了Java.lang.String类中的toLowerCase方法。如果已经为小写,则不需要再转小写。同时利用StringBuilder类的append方法拼接字符串。这比直接使用String类的效率要高一些
//首字母转小写
public static String toLowerCaseFirstOne(String s) {
if (Character.isLowerCase(s.charAt(0))) {
return s;
} else {
return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
}
}
关于.vm文件
本类涉及对vm文件的读取,在本项目中,我个人认为,vm文件相当于规定了一些java代码的基本结构,比如下面的controller.java.vm,里面固定了Controller类(接口类)的基本形态
import ${package}.biz.${className}Biz;
import ${package}.entity.${className};
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
@RestController
@RequestMapping("${secondModuleName}")
public class ${className}Controller extends BaseController<${className}Biz,${className}> {
}
还有下面关于mybatis中mapper文件的配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${package}.mapper.${className}Mapper">
<!-- 可根据自己的需求,是否要使用 -->
<resultMap type="${package}.entity.${className}" id="${classname}Map">
#foreach($column in $columns)
<result property="${column.attrname}" column="${column.columnName}"/>
#end
</resultMap>
</mapper>
比较有意思的是关于实体类配置的.vm文件,在这个文件中,首先我们可以看到,在引入包时,通过#if判断是否含有BigDecimal,如果有,就引入java.math.BigDecimal类,在之后还对注释进行了相应的配置。在实体类的配置中,使用了对应的for-each循环,设置属性
package ${package}.entity;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;
#if(${hasBigDecimal})
import java.math.BigDecimal;
#end
/**
* ${comments}
*
* @author ${author}
* @version ${datetime}
*/
@Table(name = "${tableName}")
public class ${className} implements Serializable {
private static final long serialVersionUID = 1L;
#foreach ($column in $columns)
#if($column.columnName == $pk.columnName)
//$column.comments
@Id
private $column.attrType $column.attrname;
#else
//$column.comments
@Column(name = "$column.columnName")
private $column.attrType $column.attrname;
#end
#end
#foreach ($column in $columns)
/**
* 设置:${column.comments}
*/
public void set${column.attrName}($column.attrType $column.attrname) {
this.$column.attrname = $column.attrname;
}
/**
* 获取:${column.comments}
*/
public $column.attrType get${column.attrName}() {
return $column.attrname;
}
#end
}
2.3 DocumentUtil
接下来我们把目光放在lucene包下的util工具类。关于lucene,在一开始接触的时候,其实我并不知道lucene是什么东西,但是后面通过查询资料,我渐渐了解了lucene是做什么的。lucene是一个搜索引擎工具,可以用于全文搜索和检索。
而Lucene、Solr、Elasticsearch三者的关系是:lucene提供底层的API和工具包,Solr是基于Lucene开发的企业级的搜索引擎产品,Elasticsearch同样也是基于Lucene开发的企业级的搜索引擎产品。
这个类实际上是对lucene中的Document(每一条记录),进行相应的操作
首先我们看到该类包的引入
可以发现,该类引入了很多org.apache.lucene包下的类,有分析器,与搜索高亮相关的类等等
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.search.highlight.Highlighter;
import java.io.StringReader;
接下来我们查看这个类的IndexObject2Document方法
经过阅读,可以发现本类并没有构造方法,而所有的方法都是静态方法,供程序员在其他程序中调用。比如下面的这个方法,核心的操作就是通过传入的IndexObject对象,创建并返回一个Document对象。IndexObject是一个索引对象,里面实现了诸多方法,这个我们之后会分析
public static Document IndexObject2Document(IndexObject indexObject) {
Document doc = new Document();
doc.add(new StoredField("id", indexObject.getId()));
doc.add(new TextField("title",indexObject.getTitle(), Field.Store.YES));
doc.add(new TextField("summary",indexObject.getKeywords(), Field.Store.YES));
doc.add(new TextField("descripton",indexObject.getDescripton(), Field.Store.YES));
doc.add(new StoredField("postDate", indexObject.getPostDate()));
doc.add(new StoredField("url", indexObject.getUrl()));
return doc;
}
接下来我们查看document2IndexObject方法
这个方法的目的是将传入的Document对象变为IndexObject(索引)对象,作用与上面的方法类似,之后我会重点讲解这两个类
public static IndexObject document2IndexObject(Analyzer analyzer, Highlighter highlighter, Document doc,float score) throws Exception {
IndexObject indexObject = new IndexObject();
indexObject.setId(Long.parseLong(doc.get("id")));
indexObject.setTitle(stringFormatHighlighterOut(analyzer, highlighter,doc,"title"));
indexObject.setKeywords(stringFormatHighlighterOut(analyzer, highlighter,doc,"summary"));
indexObject.setDescripton(stringFormatHighlighterOut(analyzer, highlighter,doc,"descripton"));
indexObject.setPostDate(doc.get("postDate"));
indexObject.setUrl(doc.get("url"));
indexObject.setScore(score);
return indexObject;
}
接下来我们查看下一个方法
这个方法的主要作用是为关键字加高亮。
这个方法传入了一个Document对象,还有一个field字符串,使用highlighter的getBestFragment将Document对象中的有关field的字符串提供高亮效果。
/*关键字加亮*/
private static String stringFormatHighlighterOut(Analyzer analyzer, Highlighter highlighter, Document document, String field) throws Exception{
String fieldValue = document.get(field);
if(fieldValue!=null){
TokenStream tokenStream=analyzer.tokenStream(field, new StringReader(fieldValue));
return highlighter.getBestFragment(tokenStream, fieldValue);
}
return null;
}
关于IndexObject类
由于本项目中使用的搜索引擎中存在“索引”这一概念,因此本项目中使用了IndexObject这个类用于代表对应的索引。首先可以看到这个类中引入了java.io.Serializable接口,并且实现了Comparable接口,在下面的方法中,我们可以看到,这个类重写了compareTo方法,在compareTo方法中,主要对该对象的score(相似度)属性进行比较,返回1代表本对象比传入的对象小,返回0代表相等,返回-1代表本对象比传入的对象大。
本类中也为其他的属性提供了相应的get和set方法。
import java.io.Serializable;
/**
* 索引对象
**/
public class IndexObject implements Comparable<IndexObject>,Serializable{
private Long id;
private String title;
private String keywords;
private String descripton;
private String postDate;
public String getPostDate() {
return postDate;
}
public void setPostDate(String postDate) {
this.postDate = postDate;
}
public String getDescripton() {
return descripton;
}
public void setDescripton(String descripton) {
this.descripton = descripton;
}
private String url;
/*相似度*/
private float score;
public String getKeywords() {
return keywords;
}
public void setKeywords(String keywords) {
this.keywords = keywords;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public float getScore() {
return score;
}
public void setScore(float score) {
this.score = score;
}
public IndexObject() {
super();
}
public IndexObject(Long _id, String _keywords, String _descripton, String _postDate, float _score) {
super();
this.id = _id;
this.keywords = _keywords;
this.score = _score;
this.descripton=_descripton;
this.postDate=_postDate;
}
@Override
public int compareTo(IndexObject o) {
if(this.score < o.getScore()){
return 1;
}else if(this.score > o.getScore()){
return -1;
}
return 0;
}
}
三、总结
在本篇博客中,主要对本项目中的一些工具类进行了源码分析。总的来说,还是有不少收获的。在未来的项目实训中,我希望能将这种思想渗入到我的代码中,在这个过程中,感谢老师,小组成员对我的帮助,经过我们小组的讨论,才最终成功地完成了本次源码的阅读。希望在接下来的学习中还能更近一步。