需求描述
在maven编译过程中,检查配置文件是否包含某个字段
设计思路
技术方案
maven自定义插件:解决maven在compile过程中执行
mybatis:自定义解析xml,来判断是否包含字段
变量
xml所在的文件夹,所检查的字段
核心代码
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>my-plugin</groupId>
<artifactId>my-maven-plugin</artifactId>
<packaging>maven-plugin</packaging>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.3</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>
<!--my:checkField -->
<build>
<plugins>
<plugin>
<groupId>my-plugin</groupId>
<artifactId>my-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<configuration>
<mapperLocations>
<mapperLocation>${basedir}/src/main/resources</mapperLocation>
</mapperLocations>
<fieldName>parent_hospital_id</fieldName>
<filterElements>
<filterElement>Criteria_Clause</filterElement>
</filterElements>
<filterNames>
<filterName>a.xml</filterName>
</filterNames>
</configuration>
</plugin>
</plugins>
</build>
</project>
java代码
public class SqlContext {
private String sqlText;//sql内容
private String fragmentName;//片段名称
private String fragmentQualifiedName;//片段全限定名称
private String fileName;//文件名
private boolean filter = false;//是否过滤
}
/**
* xml的where条件是否包含某个字段
*/
@Mojo(name = "checkField", defaultPhase = LifecyclePhase.COMPILE)
public class XmlWhereFieldCheck extends AbstractMojo {
@Parameter
private List<String> mapperLocations = new ArrayList<>();
@Parameter
private String fieldName;
@Parameter
private List<String> filterElements = new ArrayList<>();
@Parameter
private List<String> filterNames = new ArrayList<>();
//命令空间+id --> sql
Map<String, SqlContext> queryUpdateDeleteSqlMap = Maps.newHashMap();
//命令空间+id --> sql片段
Map<String, SqlContext> sqlNodeFragmentMap = Maps.newHashMap();
//命令空间+id --> sql片段
Map<String, SqlContext> queryUpdateDeleteNodeFragmentMap = Maps.newHashMap();
// 创建saxReader对象
SAXReader reader = new SAXReader();
{
try {
//不解析DOCTYPE,防止破坏标准的解析文件
reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
} catch (SAXException e) {
e.printStackTrace();
}
}
@Override
public void execute() {
System.out.println("**************参数***************");
for (String mapperLocation : mapperLocations) {
System.out.println("路径:" + mapperLocation);
}
for (String filterElement : filterElements) {
System.out.println("片段名:" + filterElement);
}
for (String filterName : filterNames) {
System.out.println("文件名:" + filterName);
}
System.out.println("匹配字段:" + fieldName);
//解析文件
System.out.println("**************解析文件***************");
this.parseFiles();
//解析片段
System.out.println("********************解析片段********************");
this.parseFragment();
//获取未匹配的ids
System.out.println("********************字段匹配********************");
List<SqlContext> notMatcIds = this.notMatchField();
//按照名称正序排序
System.out.println("************************************");
for (SqlContext sqlContext : notMatcIds) {
System.out.println("未匹配的文件名: " + sqlContext.getFileName() + ",片段名:" + sqlContext.getFragmentName());
}
System.out.println("************************************");
}
/**
* 未匹配的字段
*/
private List<SqlContext> notMatchField(){
String regex = "where.*" + fieldName + "(=|in)";
Pattern pattern = Pattern.compile(regex);
List<SqlContext> notMatchIds = new ArrayList<>();
for (String key : queryUpdateDeleteSqlMap.keySet()) {
SqlContext sqlContext = queryUpdateDeleteSqlMap.get(key);
String value = sqlContext.getSqlText().toLowerCase();
if(!pattern.matcher(value).find()){
notMatchIds.add(sqlContext);
}
}
return notMatchIds;
}
/**
* 解析文件
*/
private void parseFiles(){
for (String location : mapperLocations) {
File file = new File(location);
if (file.isFile()) {
this.parseFile(file);
} else {
this.scannerFiles(location);
}
}
}
/**
* 扫描所有的文件
*/
private void scannerFiles(String location){
//替换多个/或\为1个
location = location.replaceAll("(/+|\\+)", Matcher.quoteReplacement(File.separator));
System.out.println("文件位置: " + location);
File file = new File(location);
if(!file.exists()){
System.out.println("文件不存在:" + location);
return;
}
File[] files = file.listFiles();
for (File fileItem : files) {
String filePath = location + File.separator + fileItem.getName();
if(fileItem.isDirectory()){
this.scannerFiles(filePath);
}else {
this.parseFile(fileItem);
}
}
}
/**
* 解析文件
*/
private void parseFile(File file) {
try{
// 通过read方法读取一个文件 转换成Document对象
Document document = reader.read(file);
System.out.println("文件名:" + file.getName());
// 获取根节点元素对象
Element node = document.getRootElement();
String namespace = node.attribute("namespace").getValue();
for (Object elementItem : node.elements()) {
if(!(elementItem instanceof DefaultElement)){
continue;
}
Element element = (DefaultElement) elementItem;
if("resultMap".equalsIgnoreCase(element.getName()) || "insert".equalsIgnoreCase(element.getName())){
continue;
}
String idName = element.attributeValue("id");
String sqlKey = namespace + "." + idName;
String sqlText = this.getAllText(namespace, element).replaceAll("\\s", "");
SqlContext sqlContext = new SqlContext()
.setSqlText(sqlText)
.setFileName(file.getName())
.setFragmentName(idName)
.setFragmentQualifiedName(sqlKey)
;
sqlContext.setFilter(this.filter(sqlContext));//是否过滤
if("sql".equalsIgnoreCase(element.getName())){
sqlNodeFragmentMap.put(sqlKey,sqlContext);
}else{
queryUpdateDeleteNodeFragmentMap.put(sqlKey, sqlContext);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 是否过滤
*/
private boolean filter(SqlContext sqlContext){
//若名字包含,则过滤
if(null != filterNames && filterNames.contains(sqlContext.getFileName())){
return true;
}
//若片段包含,则过滤
if(null != filterElements && filterElements.contains(sqlContext.getFragmentName())){
return true;
}
return false;
}
/**
* 解析片段
*/
private void parseFragment(){
Pattern pattern = Pattern.compile("¥\\{[A-Za-z0-9\\._]+\\}");
for (String key : queryUpdateDeleteNodeFragmentMap.keySet()) {
SqlContext sqlContext = queryUpdateDeleteNodeFragmentMap.get(key);
//过滤
if(sqlContext.isFilter()){
continue;
}
String sqlFragment = sqlContext.getSqlText();//sql片段
Matcher matcher = pattern.matcher(sqlFragment);
boolean filter = false;//是否过滤
while(matcher.find()){
String attributeName = matcher.group();
attributeName = attributeName.substring(2, attributeName.length() - 1);
//替换为<sql></sql>的sql片段
String sqlNodeFragment = null;
if(sqlNodeFragmentMap.containsKey(attributeName)){
SqlContext sqlNodeContext = sqlNodeFragmentMap.get(attributeName);
if(sqlNodeContext.isFilter()){
filter = true;
break;
}
sqlNodeFragment = Matcher.quoteReplacement(sqlNodeContext.getSqlText());
}else{
System.out.println("没有找到片段:" + attributeName);
sqlNodeFragment = "";
}
sqlFragment = sqlFragment.replaceAll("¥\\{" + attributeName + "\\}", sqlNodeFragment);
matcher = pattern.matcher(sqlFragment);
}
if(!filter){//没有过滤,则添加
queryUpdateDeleteSqlMap.put(key, sqlContext.setSqlText(sqlFragment));
}
}
}
/**
* 获取结点和子节点拼接的结点内容
*/
public String getAllText(String namespace, Element element){
StringBuilder sb = new StringBuilder();
String nodeName = element.getName().toLowerCase();
if("include".equalsIgnoreCase(nodeName)){
String refidValue = element.attributeValue("refid");
//若包含.则使用使用refidValue,否则使用namespace + refidValue的值
String sqlKey = Pattern.compile("\\.+").matcher(refidValue).find() ? refidValue : (namespace + "." + refidValue);
return sb.append("¥{")
.append(sqlKey)
.append("}").toString();
}
if("trim".equalsIgnoreCase(nodeName)){
sb.append(element.attributeValue("prefix", ""));
}
if("where".equalsIgnoreCase(nodeName)){
sb.append("where");
}
for (Object elementItem : element.content()) {
if (elementItem instanceof FlyweightText) {//文本
sb.append(((FlyweightText) elementItem).getText());
} else if (elementItem instanceof DefaultElement) {//元素
sb.append(getAllText(namespace, (DefaultElement) elementItem));
} else if(elementItem instanceof FlyweightCDATA){//
sb.append(((FlyweightCDATA) elementItem).getText());
} else if(elementItem instanceof FlyweightComment) {//注释,不增加
} else {
throw new RuntimeException("不合法的元素类型");
}
}
return sb.toString();
}
public static void main(String[] args) {
XmlWhereFieldCheck xmlWhereFieldCheck = new XmlWhereFieldCheck();
File file = new File("src\\main\\resources\\OperateLog_sql.xml");
xmlWhereFieldCheck.parseFile(file);
xmlWhereFieldCheck.parseFragment();
List<SqlContext> notMatcIds = xmlWhereFieldCheck.notMatchField();
for (String key : xmlWhereFieldCheck.queryUpdateDeleteSqlMap.keySet()) {
String value = xmlWhereFieldCheck.queryUpdateDeleteSqlMap.get(key).getSqlText();
String regex = "(where|WHERE).*group_hospital_id(=|in|IN)";
Pattern pattern = Pattern.compile(regex);
System.out.println(pattern.matcher(value).find());
System.out.println(key + " === " + value);
}
}
}
执行
自动执行
加executions
<build>
<plugins>
<plugin>
<groupId>my-plugin</groupId>
<artifactId>my-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<!--在执行package时候自动执行自定义插件 将插件挂到 phase 中 -->
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>checkField</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
手动执行
my:checkField
网友评论