Browse Source

Merge branch 'master' of https://gitee.com/jmxd/ssssssss

kangjie 5 years ago
parent
commit
b9ccb0968d
35 changed files with 778 additions and 341 deletions
  1. 0 36
      README.en.md
  2. 5 12
      pom.xml
  3. 0 13
      src/main/java/com/ssssssss/TestApplication.java
  4. 0 56
      src/main/java/com/ssssssss/configuration/S8Configuration.java
  5. 16 0
      src/main/java/com/ssssssss/context/RequestContext.java
  6. 6 0
      src/main/java/com/ssssssss/dialect/Dialect.java
  7. 7 0
      src/main/java/com/ssssssss/dialect/DialectUtils.java
  8. 1 1
      src/main/java/com/ssssssss/enums/SqlMode.java
  9. 104 8
      src/main/java/com/ssssssss/executor/RequestExecutor.java
  10. 86 37
      src/main/java/com/ssssssss/executor/SqlExecutor.java
  11. 24 5
      src/main/java/com/ssssssss/executor/StatementExecutor.java
  12. 15 0
      src/main/java/com/ssssssss/model/JsonBean.java
  13. 3 0
      src/main/java/com/ssssssss/model/Page.java
  14. 9 0
      src/main/java/com/ssssssss/model/PageResult.java
  15. 3 0
      src/main/java/com/ssssssss/provider/PageProvider.java
  16. 17 1
      src/main/java/com/ssssssss/provider/impl/DefaultPageProvider.java
  17. 35 10
      src/main/java/com/ssssssss/scripts/ForeachSqlNode.java
  18. 5 0
      src/main/java/com/ssssssss/scripts/IfSqlNode.java
  19. 29 4
      src/main/java/com/ssssssss/scripts/SqlNode.java
  20. 8 0
      src/main/java/com/ssssssss/scripts/TextSqlNode.java
  21. 49 6
      src/main/java/com/ssssssss/session/Configuration.java
  22. 34 0
      src/main/java/com/ssssssss/session/SqlStatement.java
  23. 37 0
      src/main/java/com/ssssssss/session/ValidateStatement.java
  24. 34 5
      src/main/java/com/ssssssss/session/XMLStatement.java
  25. 14 8
      src/main/java/com/ssssssss/utils/Assert.java
  26. 35 0
      src/main/java/com/ssssssss/utils/DomUtils.java
  27. 70 44
      src/main/java/com/ssssssss/utils/S8XMLFileParser.java
  28. 27 14
      src/main/java/com/ssssssss/utils/XmlFileLoader.java
  29. 22 0
      src/main/java/com/ssssssss/validator/IValidator.java
  30. 21 0
      src/main/java/com/ssssssss/validator/MaxLenValidator.java
  31. 21 0
      src/main/java/com/ssssssss/validator/MinLenValidator.java
  32. 15 0
      src/main/java/com/ssssssss/validator/NotNullValidator.java
  33. 26 0
      src/main/java/com/ssssssss/validator/RegxValidator.java
  34. 0 10
      src/main/resources/application.properties
  35. 0 71
      src/main/resources/ssssssss/role/test.xml

+ 0 - 36
README.en.md

@@ -1,36 +0,0 @@
-# ssssssss
-
-#### Description
-{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
-
-#### Software Architecture
-Software architecture description
-
-#### Installation
-
-1.  xxxx
-2.  xxxx
-3.  xxxx
-
-#### Instructions
-
-1.  xxxx
-2.  xxxx
-3.  xxxx
-
-#### Contribution
-
-1.  Fork the repository
-2.  Create Feat_xxx branch
-3.  Commit your code
-4.  Create Pull Request
-
-
-#### Gitee Feature
-
-1.  You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
-2.  Gitee blog [blog.gitee.com](https://blog.gitee.com)
-3.  Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
-4.  The most valuable open source project [GVP](https://gitee.com/gvp)
-5.  The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
-6.  The most popular members  [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

+ 5 - 12
pom.xml

@@ -10,9 +10,12 @@
         <version>2.1.5.RELEASE</version>
         <relativePath/>
     </parent>
-    <groupId>com.mxd</groupId>
-    <artifactId>sql-mvc</artifactId>
+    <groupId>com.ssssssss</groupId>
+    <artifactId>ssssssss-core</artifactId>
     <version>0.0.1</version>
+    <packaging>jar</packaging>
+    <name>ssssssss</name>
+    <url>https://gitee.com/jmxd/ssssssss</url>
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
@@ -25,16 +28,6 @@
             <version>1.6</version>
         </dependency>
 
-        <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-jdbc</artifactId>
-        </dependency>
-
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>

+ 0 - 13
src/main/java/com/ssssssss/TestApplication.java

@@ -1,13 +0,0 @@
-package com.ssssssss;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-@SpringBootApplication
-public class TestApplication {
-
-
-    public static void main(String[] args) {
-        SpringApplication.run(TestApplication.class);
-    }
-}

+ 0 - 56
src/main/java/com/ssssssss/configuration/S8Configuration.java

@@ -1,56 +0,0 @@
-package com.ssssssss.configuration;
-
-import com.ssssssss.executor.RequestExecutor;
-import com.ssssssss.executor.SqlExecutor;
-import com.ssssssss.executor.StatementExecutor;
-import com.ssssssss.expression.ExpressionEngine;
-import com.ssssssss.provider.PageProvider;
-import com.ssssssss.provider.impl.DefaultPageProvider;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.sql.DataSource;
-
-@Configuration
-public class S8Configuration {
-
-    @Bean
-    public RequestExecutor requestExecutor() {
-        return new RequestExecutor();
-    }
-
-    @Bean
-    public ExpressionEngine expressionEngine() {
-        return new ExpressionEngine();
-    }
-
-    @ConditionalOnMissingBean(PageProvider.class)
-    @Bean
-    public PageProvider pageProvider(){
-        return new DefaultPageProvider("page","size");
-    }
-
-    @Bean
-    public StatementExecutor statementExecutor(DataSource dataSource,PageProvider pageProvider) {
-        SqlExecutor sqlExecutor = new SqlExecutor(dataSource);
-        sqlExecutor.setMapUnderscoreToCamelCase(true);
-        return new StatementExecutor(sqlExecutor, pageProvider);
-    }
-
-    @Bean
-    public com.ssssssss.session.Configuration configuration(StatementExecutor statementExecutor, ExpressionEngine expressionEngine, RequestExecutor requestExecutor, RequestMappingHandlerMapping requestMappingHandlerMapping) throws NoSuchMethodException {
-        com.ssssssss.session.Configuration configuration = new com.ssssssss.session.Configuration();
-        configuration.setRequestMappingHandlerMapping(requestMappingHandlerMapping);
-        configuration.setRequestHandler(requestExecutor);
-        configuration.setXmlLocations("classpath*:ssssssss/*/**.xml");
-        configuration.setEnableRefresh(true);
-        configuration.setRequestHandleMethod(RequestExecutor.class.getDeclaredMethod("invoke", HttpServletRequest.class));
-        requestExecutor.setConfiguration(configuration);
-        requestExecutor.setExpressionEngine(expressionEngine);
-        requestExecutor.setStatementExecutor(statementExecutor);
-        return configuration;
-    }
-}

+ 16 - 0
src/main/java/com/ssssssss/context/RequestContext.java

@@ -29,18 +29,34 @@ public class RequestContext extends HashMap<String,Object> {
         put("session",new SessionContext(request.getSession()));
     }
 
+    /**
+     * 获取HttpServletRequest对象
+     */
     public HttpServletRequest getRequest() {
         return request;
     }
 
+    /**
+     * 追加SQL参数
+     *
+     * @param value
+     */
     public void addParameter(Object value){
         this.parameters.add(value);
     }
 
+    /**
+     * 获取SQL参数
+     * @return
+     */
     public List<Object> getParameters() {
         return parameters;
     }
 
+    /**
+     * 执行表达式
+     * @param expression    表达式
+     */
     public Object evaluate(String expression){
         return engine.executeWrap(expression,this);
     }

+ 6 - 0
src/main/java/com/ssssssss/dialect/Dialect.java

@@ -2,9 +2,15 @@ package com.ssssssss.dialect;
 
 public interface Dialect {
 
+    /**
+     * 获取查总数的sql
+     */
     default String getCountSql(String sql) {
         return "select count(1) from (" + sql + ") count_";
     }
 
+    /**
+     * 获取分页sql
+     */
     String getPageSql(String sql, long offset, long limit);
 }

+ 7 - 0
src/main/java/com/ssssssss/dialect/DialectUtils.java

@@ -5,11 +5,18 @@ import java.util.concurrent.ConcurrentHashMap;
 
 public class DialectUtils {
 
+    /**
+     * 缓存已解析的方言
+     */
     private static Map<String, Dialect> dialectMap = new ConcurrentHashMap<>();
 
+    /**
+     * 获取数据库方言
+     */
     public static Dialect getDialectFromUrl(String fromUrl) {
         Dialect dialect = dialectMap.get(fromUrl);
         if (dialect == null) {
+            //判断mysql
             if (fromUrl.startsWith("jdbc:mysql:") || fromUrl.startsWith("jdbc:cobar:") || fromUrl.startsWith("jdbc:log4jdbc:mysql:")) {
                 dialect = new MySqlDialect();
             }

+ 1 - 1
src/main/java/com/ssssssss/enums/SqlMode.java

@@ -1,5 +1,5 @@
 package com.ssssssss.enums;
 
 public enum SqlMode {
-    SELECT_LIST,SELECT_ONE,SELECT_NUMBER,UPDATE,INSERT,DELETE
+    SELECT_LIST,SELECT_ONE,UPDATE,INSERT,DELETE
 }

+ 104 - 8
src/main/java/com/ssssssss/executor/RequestExecutor.java

@@ -4,10 +4,30 @@ import com.ssssssss.context.RequestContext;
 import com.ssssssss.expression.ExpressionEngine;
 import com.ssssssss.model.JsonBean;
 import com.ssssssss.session.Configuration;
+import com.ssssssss.session.SqlStatement;
+import com.ssssssss.session.ValidateStatement;
+import com.ssssssss.session.XMLStatement;
+import com.ssssssss.utils.Assert;
+import com.ssssssss.utils.DomUtils;
+import com.ssssssss.validator.IValidator;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.servlet.HandlerMapping;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
 
 import javax.servlet.http.HttpServletRequest;
-import java.sql.SQLException;
+import javax.xml.xpath.XPathConstants;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 public class RequestExecutor {
 
@@ -17,6 +37,13 @@ public class RequestExecutor {
 
     private ExpressionEngine expressionEngine;
 
+    private static Logger logger = LoggerFactory.getLogger(RequestExecutor.class);
+    private Map<String, IValidator> validators = new HashMap<>();
+
+    public void addValidator(IValidator validator) {
+        this.validators.put(validator.support(), validator);
+    }
+
     public void setConfiguration(Configuration configuration) {
         this.configuration = configuration;
     }
@@ -29,14 +56,83 @@ public class RequestExecutor {
         this.expressionEngine = expressionEngine;
     }
 
+    /**
+     * http请求入口
+     */
     @ResponseBody
-    public Object invoke(HttpServletRequest request) throws SQLException {
-        RequestContext requestContext = new RequestContext(request,expressionEngine);
-        String requestMapping = request.getServletPath();
-        if (requestMapping.endsWith("/")) {
-            requestMapping = requestMapping.substring(0, requestMapping.length() - 1);
+    public Object invoke(HttpServletRequest request, @RequestBody(required = false) Object requestBody) {
+        try {
+            NativeWebRequest webRequest = new ServletWebRequest(request);
+            // 解析requestMapping
+            String requestMapping = (String) webRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
+            // 解析pathVariable
+            Map<String, String> pathVariables = (Map<String, String>) webRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
+            // 创建RequestContex对象,供后续使用
+            RequestContext requestContext = new RequestContext(request, expressionEngine);
+            if (!requestContext.containsKey("body")) {
+                requestContext.put("body", requestBody);
+            }
+            if (pathVariables != null) {
+                requestContext.putAll(pathVariables);
+            }
+            SqlStatement sqlStatement = configuration.getStatement(requestMapping);
+            // 执行校验
+            Object value = validate(sqlStatement, requestContext);
+            if (value != null) {
+                return value;
+            }
+            // 执行SQL
+            value = statementExecutor.execute(sqlStatement, requestContext);
+            return new JsonBean<>(value);
+        } catch (Exception e) {
+            logger.error("系统出现错误", e);
+            return new JsonBean<>(-1, e.getMessage());
+        }
+    }
+
+    private JsonBean<Void> validate(SqlStatement sqlStatement, RequestContext requestContext) {
+        List<String> validates = sqlStatement.getValidates();
+        XMLStatement xmlStatement = sqlStatement.getXmlStatement();
+        for (String validateId : validates) {
+            ValidateStatement validateStatement = xmlStatement.getValidateStatement(validateId);
+            NodeList nodeList = validateStatement.getNodes();
+            for (int i = 0, len = nodeList.getLength(); i < len; i++) {
+                Node node = nodeList.item(i);
+                // 获取name值
+                String name = DomUtils.getNodeAttributeValue(node, "name");
+                Object value = null;
+                // 如果name值填了,则取其表达式值
+                if (StringUtils.isNotBlank(name)) {
+                    value = requestContext.evaluate(name);
+                }
+                NodeList ruleList = (NodeList) DomUtils.evaluate("*", node, XPathConstants.NODESET);
+                for (int j = 0, l = ruleList.getLength(); j < l; j++) {
+                    Node rule = ruleList.item(j);
+                    // 如果验证失败,返回自定义code
+                    String nodeName = rule.getNodeName();
+                    IValidator validator = validators.get(nodeName);
+                    Assert.isNotNull(validator, String.format("找不到验证器:%s", nodeName));
+                    if (!validator.validate(value, rule)) {
+                        // rule->param->validate
+                        int defaultCode = NumberUtils.toInt(DomUtils.getNodeAttributeValue(node, "code"), validateStatement.getCode());
+                        int code = NumberUtils.toInt(DomUtils.getNodeAttributeValue(rule, "code"), defaultCode);
+                        String message = rule.getTextContent();
+                        if (StringUtils.isNotBlank(message)) {
+                            message = message.trim();
+                        } else {
+                            message = DomUtils.getNodeAttributeValue(node, "message");
+                            if (StringUtils.isNotBlank(message)) {
+                                message = message.trim();
+                            } else {
+                                message = validateStatement.getMessage();
+                            }
+                        }
+                        return new JsonBean<>(code, message);
+                    }
+                }
+            }
         }
-        Object value = statementExecutor.execute(configuration.getStatement(requestMapping), requestContext);
-        return new JsonBean<>(value);
+        // 验证通过
+        return null;
     }
 }

+ 86 - 37
src/main/java/com/ssssssss/executor/SqlExecutor.java

@@ -10,6 +10,9 @@ import java.sql.*;
 import java.util.Date;
 import java.util.*;
 
+/**
+ * SQL执行器
+ */
 public class SqlExecutor {
 
     private DataSource dataSource;
@@ -29,15 +32,28 @@ public class SqlExecutor {
         this.mapUnderscoreToCamelCase = mapUnderscoreToCamelCase;
     }
 
+    /**
+     * 执行SQL
+     *
+     * @param mode       SQL模式
+     * @param sql        SQL
+     * @param parameters SQL参数
+     * @param returnType 返回值类型
+     * @return
+     * @throws SQLException
+     */
     public Object execute(SqlMode mode, String sql, List<Object> parameters, Class<?> returnType) throws SQLException {
         if (SqlMode.SELECT_LIST == mode) {
             return queryForList(sql, parameters, returnType == null ? Map.class : returnType);
         } else if (SqlMode.UPDATE == mode || SqlMode.INSERT == mode || SqlMode.DELETE == mode) {
-            return update(sql, parameters);
+            int value = update(sql, parameters);
+            // 当设置返回值是boolean类型时,做>0比较
+            if(returnType == Boolean.class){
+                return value > 0;
+            }
+            return value;
         } else if (SqlMode.SELECT_ONE == mode) {
             return queryForOne(sql, parameters, returnType);
-        } else if (SqlMode.SELECT_NUMBER == mode) {
-            return queryForNumber(sql, parameters, returnType);
         } else {
             throw new S8Exception("暂时不支持[" + mode + "]模式");
         }
@@ -47,6 +63,10 @@ public class SqlExecutor {
         return dataSource.getConnection();
     }
 
+    /**
+     * 获取Connection并调用回调函数执行
+     * @param connectionCallback    回调函数
+     */
     public <T> T doInConnection(ConnectionCallback<T> connectionCallback) throws SQLException {
         Connection connection = getConnection();
         try {
@@ -68,22 +88,25 @@ public class SqlExecutor {
         }
     }
 
-    private <T> T queryForNumber(String sql, List<Object> params, Class<T> returnType) throws SQLException {
-        Connection conn = getConnection();
-        try {
-            return queryForNumber(conn, sql, params, returnType);
-        } finally {
-            closeConnection(conn);
-        }
-    }
-
-    public <T> T queryForNumber(Connection connection, String sql, List<Object> params, Class<T> returnType) throws SQLException {
+    /**
+     * 查询一条
+     * @param connection    连接对象
+     * @param sql   SQL
+     * @param params   SQL参数
+     * @param returnType    返回值类型
+     */
+    public <T> T queryForOne(Connection connection, String sql, List<Object> params, Class<T> returnType) throws SQLException {
         PreparedStatement stmt = null;
         ResultSet rs = null;
         try {
             stmt = createPreparedStatement(connection, sql, params);
             rs = stmt.executeQuery();
-            if (rs.next()) {
+            if (returnType == null || returnType == Map.class) {
+                if (rs.next()) {
+                    return (T) fetchResultSet(rs);
+                }
+            } else if (rs.next()) {
+                // 返回值不是Map时,只取第一行第一列
                 return rs.getObject(1, returnType);
             }
         } finally {
@@ -93,30 +116,28 @@ public class SqlExecutor {
         return null;
     }
 
-    private Object queryForOne(String sql, List<Object> params, Class<?> returnType) throws SQLException {
+    /**
+     * 查询一条
+     * @param sql   SQL
+     * @param params   SQL参数
+     * @param returnType    返回值类型
+     */
+    private <T> T queryForOne(String sql, List<Object> params, Class<T> returnType) throws SQLException {
         Connection connection = getConnection();
-        PreparedStatement stmt = null;
-        ResultSet rs = null;
         try {
-            stmt = createPreparedStatement(connection, sql, params);
-            rs = stmt.executeQuery();
-            if (returnType == null || returnType == Map.class) {
-                ResultSetMetaData rsd = rs.getMetaData();
-                int columnCount = rsd.getColumnCount();
-                if (rs.next()) {
-                    return fetchResultSet(rs);
-                }
-            } else if (rs.next()) {
-                return rs.getObject(1, returnType);
-            }
+            return queryForOne(connection, sql, params, returnType);
         } finally {
-            closeResultSet(rs);
-            closeStatement(stmt);
             closeConnection(connection);
         }
-        return null;
     }
 
+    /**
+     * 查询List
+     * @param connection    连接对象
+     * @param sql   SQL
+     * @param params   SQL参数
+     * @param returnType    返回值类型
+     */
     public List<Object> queryForList(Connection connection, String sql, List<Object> params, Class<?> returnType) throws SQLException {
         PreparedStatement stmt = null;
         ResultSet rs = null;
@@ -130,6 +151,7 @@ public class SqlExecutor {
                 }
             } else {
                 while (rs.next()) {
+                    // 返回值不是Map时,只取第一列
                     list.add(rs.getObject(1, returnType));
                 }
             }
@@ -140,18 +162,26 @@ public class SqlExecutor {
         }
     }
 
-    private Map<String,Object> fetchResultSet(ResultSet rs) throws SQLException {
+    /**
+     * 从ResultSet中提取map对象
+     */
+    private Map<String, Object> fetchResultSet(ResultSet rs) throws SQLException {
         ResultSetMetaData rsd = rs.getMetaData();
         int columnCount = rsd.getColumnCount();
-        Map<String,Object> row = new HashMap<>(columnCount);
+        Map<String, Object> row = new HashMap<>(columnCount);
         for (int i = 1; i <= columnCount; i++) {
             row.put(underscoreToCamelCase(rsd.getColumnName(i)), rs.getObject(i));
         }
         return row;
     }
 
-    private String underscoreToCamelCase(String columnName){
-        if(mapUnderscoreToCamelCase){
+    /**
+     * 下划线转驼峰命名
+     * @param columnName 列名
+     * @return
+     */
+    private String underscoreToCamelCase(String columnName) {
+        if (mapUnderscoreToCamelCase) {
             columnName = columnName.toLowerCase();
             boolean upperCase = false;
             StringBuilder sb = new StringBuilder();
@@ -171,6 +201,7 @@ public class SqlExecutor {
         return columnName;
     }
 
+
     private List<Object> queryForList(String sql, List<Object> params, Class<?> returnType) throws SQLException {
         Connection connection = getConnection();
         try {
@@ -180,16 +211,25 @@ public class SqlExecutor {
         }
     }
 
+    /**
+     * 统一创建PrepareStatement对象
+     * @param conn  连接对象
+     * @param sql   SQL
+     * @param params SQL参数
+     */
     private PreparedStatement createPreparedStatement(Connection conn, String sql, List<Object> params) throws SQLException {
         PreparedStatement stmt = conn.prepareStatement(sql);
-        logger.debug("执行SQL:{}",sql);
+        logger.debug("执行SQL:{}", sql);
         setStatementParams(stmt, params);
         return stmt;
     }
 
+    /**
+     * 设置SQL参数
+     */
     private void setStatementParams(PreparedStatement stmt, List<Object> params) throws SQLException {
         if (params != null) {
-            logger.debug("sql参数:{}",params);
+            logger.debug("sql参数:{}", params);
             for (int i = 0; i < params.size(); i++) {
                 Object val = params.get(i);
                 if (val instanceof Date) {
@@ -202,6 +242,9 @@ public class SqlExecutor {
     }
 
 
+    /**
+     * 关闭连接
+     */
     private void closeConnection(Connection connection) {
         if (connection != null) {
             try {
@@ -211,6 +254,9 @@ public class SqlExecutor {
         }
     }
 
+    /**
+     * 关闭ResultSet
+     */
     private void closeResultSet(ResultSet resultSet) {
         if (resultSet != null) {
             try {
@@ -220,6 +266,9 @@ public class SqlExecutor {
         }
     }
 
+    /**
+     * 关闭PrepareStatement
+     */
     private void closeStatement(Statement statement) {
         if (statement != null) {
             try {

+ 24 - 5
src/main/java/com/ssssssss/executor/StatementExecutor.java

@@ -10,10 +10,16 @@ import com.ssssssss.session.SqlStatement;
 
 import java.sql.SQLException;
 
+/**
+ * SqlStatement执行器
+ */
 public class StatementExecutor {
 
     private SqlExecutor sqlExecutor;
 
+    /**
+     * 分页提取器
+     */
     private PageProvider pageProvider;
 
     public StatementExecutor(SqlExecutor sqlExecutor, PageProvider pageProvider) {
@@ -21,24 +27,37 @@ public class StatementExecutor {
         this.pageProvider = pageProvider;
     }
 
+    /**
+     * 执行SqlStatement
+     */
     public Object execute(SqlStatement sqlStatement, RequestContext context) throws SQLException {
+        // 获取要执行的SQL
         String sql = sqlStatement.getSqlNode().getSql(context).trim();
-        if(sqlStatement.isPagination()){
+        if (sqlStatement.isPagination()) {  //判断是否是分页语句
+            // 从Request中提取Page对象
             Page page = pageProvider.getPage(context.getRequest());
+            // 执行分页逻辑
             return sqlExecutor.doInConnection(connection -> {
                 PageResult<Object> pageResult = new PageResult<>();
+                // 获取数据库方言
                 Dialect dialect = DialectUtils.getDialectFromUrl(connection.getMetaData().getURL());
-                long total = sqlExecutor.queryForNumber(connection,dialect.getCountSql(sql),context.getParameters(),Long.class);
+                // 获取总条数
+                long total = sqlExecutor.queryForOne(connection, dialect.getCountSql(sql), context.getParameters(), Long.class);
                 pageResult.setTotal(total);
-                if(total > 0){
+                // 当条数>0时,执行查询语句,否则不查询以提高性能
+                if (total > 0) {
+                    // 获取分页语句
                     String pageSql = dialect.getPageSql(sql, page.getOffset(), page.getLimit());
+                    // 设置分页参数
                     context.addParameter(page.getLimit());
                     context.addParameter(page.getOffset());
-                    pageResult.setList(sqlExecutor.queryForList(connection,pageSql,context.getParameters(),sqlStatement.getReturnType()));
+                    // 执行查询
+                    pageResult.setList(sqlExecutor.queryForList(connection, pageSql, context.getParameters(), sqlStatement.getReturnType()));
                 }
                 return pageResult;
             });
-        }else{
+        } else {
+            // 普通SQL执行
             return sqlExecutor.execute(sqlStatement.getSqlMode(), sql, context.getParameters(), sqlStatement.getReturnType());
         }
     }

+ 15 - 0
src/main/java/com/ssssssss/model/JsonBean.java

@@ -1,13 +1,28 @@
 package com.ssssssss.model;
 
+/**
+ * 统一返回值对象
+ */
 public class JsonBean<T> {
 
+    /**
+     * 状态码
+     */
     private int code = 1;
 
+    /**
+     * 状态说明
+     */
     private String message = "success";
 
+    /**
+     * 实际逻辑
+     */
     private T data;
 
+    /**
+     * 服务器时间
+     */
     private long timestamp = System.currentTimeMillis();
 
     public JsonBean(int code, String message) {

+ 3 - 0
src/main/java/com/ssssssss/model/Page.java

@@ -1,5 +1,8 @@
 package com.ssssssss.model;
 
+/**
+ * 分页对象
+ */
 public class Page {
 
     private long limit;

+ 9 - 0
src/main/java/com/ssssssss/model/PageResult.java

@@ -2,10 +2,19 @@ package com.ssssssss.model;
 
 import java.util.List;
 
+/**
+ * 分页执行结果
+ */
 public class PageResult<T> {
 
+    /**
+     * 总条数
+     */
     private long total;
 
+    /**
+     * 数据项
+     */
     private List<T> list;
 
     public PageResult(long total, List<T> list) {

+ 3 - 0
src/main/java/com/ssssssss/provider/PageProvider.java

@@ -4,6 +4,9 @@ import com.ssssssss.model.Page;
 
 import javax.servlet.http.HttpServletRequest;
 
+/**
+ * 分页对象提取接口
+ */
 public interface PageProvider {
 
     public Page getPage(HttpServletRequest request);

+ 17 - 1
src/main/java/com/ssssssss/provider/impl/DefaultPageProvider.java

@@ -6,14 +6,29 @@ import org.apache.commons.lang3.math.NumberUtils;
 
 import javax.servlet.http.HttpServletRequest;
 
+/**
+ * 分页对象默认提取接口
+ */
 public class DefaultPageProvider implements PageProvider {
 
+    /**
+     * page参数名
+     */
     private String pageName;
 
+    /**
+     * pageSize参数名
+     */
     private String pageSize;
 
+    /**
+     * 默认分页大小
+     */
     private long defaultPageSize = 10;
 
+    /**
+     * 默认页数
+     */
     private long defaultPage = 1;
 
     public DefaultPageProvider(String pageName, String pageSize) {
@@ -29,11 +44,12 @@ public class DefaultPageProvider implements PageProvider {
     }
 
 
-
     @Override
     public Page getPage(HttpServletRequest request) {
+        // 从Request中提取page以及pageSize
         long page = NumberUtils.toLong(request.getParameter(this.pageName), this.defaultPage);
         long pageSize = NumberUtils.toLong(request.getParameter(this.pageSize), this.defaultPageSize);
+        // 计算limit以及offset
         return new Page((page - 1) * pageSize, pageSize);
 
     }

+ 35 - 10
src/main/java/com/ssssssss/scripts/ForeachSqlNode.java

@@ -11,14 +11,29 @@ import java.util.Collection;
  */
 public class ForeachSqlNode extends SqlNode{
 
+    /**
+     * 数据集合,支持Collection、数组
+     */
     private String collection;
 
+    /**
+     * item 变量名
+     */
     private String item;
 
+    /**
+     * 拼接起始SQL
+     */
     private String open;
 
+    /**
+     * 拼接结束SQL
+     */
     private String close;
 
+    /**
+     * 分隔符
+     */
     private String separator;
 
     public void setCollection(String collection) {
@@ -43,25 +58,35 @@ public class ForeachSqlNode extends SqlNode{
 
     @Override
     public String getSql(RequestContext context) {
+        // 提取集合
         Object value = context.evaluate(this.collection);
+        // 如果集合为空,则过滤该节点
         if(value == null){
             return "";
         }
+        // 开始拼接SQL,
         String sql = StringUtils.defaultString(this.open);
+        // 如果集合是Collection对象或其子类,则转成数组
         if(value instanceof Collection){
             value = ((Collection) value).toArray();
         }
-        if(value.getClass().isArray()){
-            int len = Array.getLength(value);
-            for (int i = 0; i < len; i++) {
-                context.put(this.item,Array.get(value,i));
-                sql += executeChildren(context);
-                if(i + 1 < len){
-                    sql += StringUtils.defaultString(this.separator);
-                }
+        // 判断不是数组,则过滤子节点并返回
+        if (!value.getClass().isArray()) {
+            return "";
+        }
+        // 获取数组长度
+        int len = Array.getLength(value);
+        for (int i = 0; i < len; i++) {
+            // 存入item对象
+            context.put(this.item, Array.get(value, i));
+            // 拼接子节点
+            sql += executeChildren(context);
+            // 拼接分隔符
+            if (i + 1 < len) {
+                sql += StringUtils.defaultString(this.separator);
             }
         }
-        sql+=StringUtils.defaultString(this.close);
-        return sql;
+        // 拼接结束SQL
+        return sql + StringUtils.defaultString(this.close);
     }
 }

+ 5 - 0
src/main/java/com/ssssssss/scripts/IfSqlNode.java

@@ -9,6 +9,9 @@ import java.util.Objects;
  */
 public class IfSqlNode extends SqlNode{
 
+    /**
+     * 判断表达式
+     */
     private String test;
 
     public IfSqlNode(String test) {
@@ -17,7 +20,9 @@ public class IfSqlNode extends SqlNode{
 
     @Override
     public String getSql(RequestContext context) {
+        // 执行表达式
         Object value = context.evaluate(test);
+        // 判断表达式返回结果是否是true,如果不是则过滤子节点
         if(Objects.equals(value,true)){
             return executeChildren(context);
         }

+ 29 - 4
src/main/java/com/ssssssss/scripts/SqlNode.java

@@ -8,20 +8,39 @@ import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+/**
+ * sql节点
+ */
 public abstract class SqlNode {
 
-    List<SqlNode> nodes = new ArrayList<>();
-
+    /**
+     * 提取#{}的正则
+     */
     final Pattern expressionRegx = Pattern.compile("#\\{(.*?)\\}");
-
+    /**
+     * 提取${}的正则
+     */
     final Pattern replaceRegx = Pattern.compile("\\$\\{(.*?)\\}");
+    /**
+     * 子节点
+     */
+    List<SqlNode> nodes = new ArrayList<>();
 
+    /**
+     * 追加子节点
+     */
     public void addChildNode(SqlNode node){
         this.nodes.add(node);
     }
 
+    /**
+     * 获取该节点的SQL
+     */
     public abstract String getSql(RequestContext context);
 
+    /**
+     * 获取子节点SQL
+     */
     public String executeChildren(RequestContext context){
         String sql = "";
         for (SqlNode node : nodes) {
@@ -30,7 +49,13 @@ public abstract class SqlNode {
         return sql;
     }
 
-    public List<String> extractParameter(Pattern pattern,String sql) {
+    /**
+     * 根据正则表达式提取参数
+     *
+     * @param pattern 正则表达式
+     * @param sql     SQL
+     */
+    public List<String> extractParameter(Pattern pattern, String sql) {
         Matcher matcher = pattern.matcher(sql);
         List<String> results = new ArrayList<>();
         while (matcher.find()) {

+ 8 - 0
src/main/java/com/ssssssss/scripts/TextSqlNode.java

@@ -6,8 +6,14 @@ import org.apache.commons.lang3.StringUtils;
 import java.util.List;
 import java.util.Objects;
 
+/**
+ * 普通SQL节点
+ */
 public class TextSqlNode extends SqlNode{
 
+    /**
+     * SQL
+     */
     private String text;
 
     public TextSqlNode(String text) {
@@ -18,8 +24,10 @@ public class TextSqlNode extends SqlNode{
     public String getSql(RequestContext context) {
         String sql = text;
         if(StringUtils.isNotBlank(text)){
+            // 提取#{}表达式
             List<String> expressions = extractParameter(expressionRegx,text);
             for (String expression : expressions) {
+                // 执行表达式
                 Object val = context.evaluate(expression);
                 context.addParameter(val);
                 sql = sql.replaceFirst(expressionRegx.pattern(), "?");

+ 49 - 6
src/main/java/com/ssssssss/session/Configuration.java

@@ -16,28 +16,48 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
+/**
+ * S8配置类
+ */
 public class Configuration implements InitializingBean {
 
     private RequestMappingHandlerMapping requestMappingHandlerMapping;
 
+    /**
+     * Http请求处理器
+     */
     private Object requestHandler;
 
+    /**
+     * Http请求处理方法
+     */
     private Method requestHandleMethod;
 
     /**
      * xml位置
      */
-    private String xmlLocations;
+    private String[] xmlLocations;
 
     /**
      * 是否自动刷新
      */
     private boolean enableRefresh;
 
+    /**
+     * 是否打印banner
+     */
+    private boolean banner;
+
+    /**
+     * 缓存已加载的SqlStatement
+     */
     private Map<String,SqlStatement> statementMap = new ConcurrentHashMap<>();
 
     private static Logger logger = LoggerFactory.getLogger(Configuration.class);
 
+    /**
+     * 根据RequestMapping获取SqlStatement对象
+     */
     public SqlStatement getStatement(String requestMapping){
         return statementMap.get(requestMapping);
     }
@@ -47,16 +67,23 @@ public class Configuration implements InitializingBean {
      */
     public void addStatement(SqlStatement sqlStatement){
         RequestMappingInfo requestMappingInfo = getRequestMappingInfo(sqlStatement);
+        // 如果已经注册过,则先取消注册
         if(statementMap.containsKey(sqlStatement.getRequestMapping())){
             logger.debug("刷新接口:{}",sqlStatement.getRequestMapping());
+            // 取消注册
             requestMappingHandlerMapping.unregisterMapping(requestMappingInfo);
         }else{
             logger.debug("注册接口:{}",sqlStatement.getRequestMapping());
         }
+        // 添加至缓存
         statementMap.put(sqlStatement.getRequestMapping(),sqlStatement);
+        // 注册接口
         requestMappingHandlerMapping.registerMapping(requestMappingInfo,requestHandler,requestHandleMethod);
     }
 
+    /**
+     * 获取RequestMappingInfo对象
+     */
     private RequestMappingInfo getRequestMappingInfo(SqlStatement sqlStatement){
         String requestMapping = sqlStatement.getRequestMapping();
         Assert.isNotBlank(requestMapping,"request-mapping 不能为空!");
@@ -81,7 +108,7 @@ public class Configuration implements InitializingBean {
         this.requestHandleMethod = requestHandleMethod;
     }
 
-    public void setXmlLocations(String xmlLocations) {
+    public void setXmlLocations(String[] xmlLocations) {
         this.xmlLocations = xmlLocations;
     }
 
@@ -89,12 +116,28 @@ public class Configuration implements InitializingBean {
         this.enableRefresh = enableRefresh;
     }
 
+    public void setBanner(boolean banner) {
+        this.banner = banner;
+    }
+
     @Override
     public void afterPropertiesSet() {
-        XmlFileLoader loader = new XmlFileLoader(xmlLocations, this);
-        loader.run();
-        if(enableRefresh){
-            Executors.newScheduledThreadPool(1).scheduleAtFixedRate(loader,3,3, TimeUnit.SECONDS);
+        if(this.banner){
+            System.out.println("  ____    ____    ____    ____    ____    ____    ____    ____  ");
+            System.out.println(" / ___|  / ___|  / ___|  / ___|  / ___|  / ___|  / ___|  / ___| ");
+            System.out.println("\\___ \\  \\___ \\  \\___ \\  \\___ \\  \\___ \\  \\___ \\  \\___ \\  \\___ \\ ");
+            System.out.println("  ___) |  ___) |  ___) |  ___) |  ___) |  ___) |  ___) |  ___) |");
+            System.out.println(" |____/  |____/  |____/  |____/  |____/  |____/  |____/  |____/       " + Configuration.class.getPackage().getImplementationVersion());
+        }
+        if(this.xmlLocations == null){
+            logger.error("ssssssss.xml-locations不能为空");
+        }else{
+            XmlFileLoader loader = new XmlFileLoader(xmlLocations, this);
+            loader.run();
+            // 如果启动刷新则定时重新加载
+            if(enableRefresh){
+                Executors.newScheduledThreadPool(1).scheduleAtFixedRate(loader,3,3, TimeUnit.SECONDS);
+            }
         }
     }
 }

+ 34 - 0
src/main/java/com/ssssssss/session/SqlStatement.java

@@ -3,20 +3,46 @@ package com.ssssssss.session;
 import com.ssssssss.enums.SqlMode;
 import com.ssssssss.scripts.SqlNode;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class SqlStatement {
 
+    /**
+     * 请求路径
+     */
     private String requestMapping;
 
+    /**
+     * 请求方法
+     */
     private String requestMethod;
 
+    /**
+     * SQL模式
+     */
     private SqlMode sqlMode;
 
+    /**
+     * SQL节点
+     */
     private SqlNode sqlNode;
 
+    /**
+     * 是否开启分页
+     */
     private boolean pagination;
 
+    /**
+     * 返回值类型
+     */
     private Class<?> returnType;
 
+    private List<String> validates = new ArrayList<>();
+
+    /**
+     * XMLStatement对象
+     */
     private XMLStatement xmlStatement;
 
     public String getRequestMapping() {
@@ -74,4 +100,12 @@ public class SqlStatement {
     public void setPagination(boolean pagination) {
         this.pagination = pagination;
     }
+
+    public List<String> getValidates() {
+        return validates;
+    }
+
+    public void addValidate(String id) {
+        this.validates.add(id);
+    }
 }

+ 37 - 0
src/main/java/com/ssssssss/session/ValidateStatement.java

@@ -0,0 +1,37 @@
+package com.ssssssss.session;
+
+import org.w3c.dom.NodeList;
+
+public class ValidateStatement {
+
+    private String id;
+
+    private Integer code;
+
+    private String message;
+
+    private NodeList nodes;
+
+    public ValidateStatement(String id, Integer code, String message, NodeList nodes) {
+        this.id = id;
+        this.code = code;
+        this.message = message;
+        this.nodes = nodes;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public NodeList getNodes() {
+        return nodes;
+    }
+}

+ 34 - 5
src/main/java/com/ssssssss/session/XMLStatement.java

@@ -1,7 +1,9 @@
 package com.ssssssss.session;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * xml文件对应的实体
@@ -14,7 +16,12 @@ public class XMLStatement {
     private String requestMapping;
 
     /**
-     * xml文件中sql语句,包括select/insert/update/delete
+     * 缓存验证节点
+     */
+    private Map<String, ValidateStatement> validateStatements = new HashMap<>();
+
+    /**
+     * xml文件中sql语句,包括select-list/select-one/insert/update/delete
      */
     private List<SqlStatement> sqlStatements = new ArrayList<>();
 
@@ -30,11 +37,33 @@ public class XMLStatement {
         return sqlStatements;
     }
 
-    public void setSqlStatements(List<SqlStatement> sqlStatements) {
-        this.sqlStatements = sqlStatements;
+    /**
+     * 添加一个SQL节点
+     *
+     * @param sqlStatements
+     */
+    public void addSqlStatement(List<SqlStatement> sqlStatements) {
+        this.sqlStatements.addAll(sqlStatements);
     }
 
-    public void addSqlStatement(List<SqlStatement> sqlStatements){
-        this.sqlStatements.addAll(sqlStatements);
+    /**
+     * 添加一个验证节点
+     */
+    public void addValidateStatement(ValidateStatement validateStatement) {
+        this.validateStatements.put(validateStatement.getId(), validateStatement);
+    }
+
+    /**
+     * 获取验证节点
+     */
+    public ValidateStatement getValidateStatement(String id) {
+        return this.validateStatements.get(id);
+    }
+
+    /**
+     * 判断是否有验证节点
+     */
+    public boolean containsValidateStatement(String id) {
+        return this.validateStatements.containsKey(id);
     }
 }

+ 14 - 8
src/main/java/com/ssssssss/utils/Assert.java

@@ -7,11 +7,18 @@ public class Assert {
 
     /**
      * 断言值不能为空
-     * @param value
-     * @param message
      */
-    public static void isNotNull(Object value,String message){
-        if(value == null){
+    public static void isNotNull(Object value, String message) {
+        if (value == null) {
+            throw new S8Exception(message);
+        }
+    }
+
+    /**
+     * 验证值必须是true
+     */
+    public static void isTrue(boolean value, String message) {
+        if (!value) {
             throw new S8Exception(message);
         }
     }
@@ -19,12 +26,11 @@ public class Assert {
 
     /**
      * 断言值不能为空字符串
-     * @param value
-     * @param message
      */
-    public static void isNotBlank(String value,String message){
-        if(StringUtils.isBlank(value)){
+    public static void isNotBlank(String value, String message) {
+        if (StringUtils.isBlank(value)) {
             throw new S8Exception(message);
         }
     }
+
 }

+ 35 - 0
src/main/java/com/ssssssss/utils/DomUtils.java

@@ -0,0 +1,35 @@
+package com.ssssssss.utils;
+
+import org.w3c.dom.Node;
+
+import javax.xml.namespace.QName;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathFactory;
+
+public class DomUtils {
+
+    private static final XPath XPATH = XPathFactory.newInstance().newXPath();
+
+    /**
+     * 获取节点属性
+     *
+     * @param node         节点
+     * @param attributeKey 属性名
+     * @return 节点属性值,未设置时返回null
+     */
+    public static String getNodeAttributeValue(Node node, String attributeKey) {
+        Node item = node.getAttributes().getNamedItem(attributeKey);
+        return item != null ? item.getNodeValue() : null;
+    }
+
+    /**
+     * xpath提取
+     */
+    public static Object evaluate(String xpath, Object item, QName qName) {
+        try {
+            return XPATH.compile(xpath).evaluate(item, qName);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

+ 70 - 44
src/main/java/com/ssssssss/utils/S8XMLFileParser.java

@@ -6,8 +6,10 @@ import com.ssssssss.scripts.IfSqlNode;
 import com.ssssssss.scripts.SqlNode;
 import com.ssssssss.scripts.TextSqlNode;
 import com.ssssssss.session.SqlStatement;
+import com.ssssssss.session.ValidateStatement;
 import com.ssssssss.session.XMLStatement;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
@@ -17,11 +19,13 @@ import org.xml.sax.SAXException;
 
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPathConstants;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 
 /**
  * xml文件解析
@@ -30,20 +34,22 @@ public class S8XMLFileParser {
 
     private static Logger logger = LoggerFactory.getLogger(S8XMLFileParser.class);
 
-    private static final List<String> TAG_NAMES = Arrays.asList("select", "insert", "update", "delete");
+    private static final List<String> TAG_NAMES = Arrays.asList("select-list", "select-one", "insert", "update", "delete");
 
     /**
      * 解析xml文件
      */
-    public static XMLStatement parse(File file) {
+    static XMLStatement parse(File file) {
         XMLStatement statement = null;
         try {
             Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file);
-            //解析根节点
+            // 解析根节点
             statement = parseRoot(document);
+            // 解析验证节点
+            parseValidateStatement(document.getElementsByTagName("validate"), statement);
             // 解析select/insert/update/delete节点
             for (String tagName : TAG_NAMES) {
-                statement.addSqlStatement(parseSqlStatement(statement, document.getElementsByTagName(tagName)));
+                statement.addSqlStatement(parseSqlStatement(statement, tagName, document));
             }
         } catch (SAXException | IOException | ParserConfigurationException e) {
             logger.error("解析S8XML文件出错", e);
@@ -51,6 +57,21 @@ public class S8XMLFileParser {
         return statement;
     }
 
+    /**
+     * 解析Validate节点
+     */
+    private static void parseValidateStatement(NodeList nodeList, XMLStatement xmlStatement) {
+        for (int i = 0, len = nodeList.getLength(); i < len; i++) {
+            Node node = nodeList.item(i);
+            String id = DomUtils.getNodeAttributeValue(node, "id");
+            Assert.isNotBlank(id, "validate节点必须要有id属性");
+            String code = DomUtils.getNodeAttributeValue(node, "code");
+            String message = DomUtils.getNodeAttributeValue(node, "message");
+            message = StringUtils.isBlank(message) ? "参数校验失败" : message;
+            xmlStatement.addValidateStatement(new ValidateStatement(id, NumberUtils.toInt(code, 0), message, (NodeList) DomUtils.evaluate("param", node, XPathConstants.NODESET)));
+        }
+    }
+
     /**
      * 解析根节点
      */
@@ -64,40 +85,51 @@ public class S8XMLFileParser {
     /**
      * 解析节点
      */
-    private static List<SqlStatement> parseSqlStatement(XMLStatement xmlStatement, NodeList nodeList) {
+    private static List<SqlStatement> parseSqlStatement(XMLStatement xmlStatement, String tagName, Document document) {
         List<SqlStatement> sqlStatements = new ArrayList<>();
+        NodeList nodeList = document.getElementsByTagName(tagName);
         for (int i = 0, len = nodeList.getLength(); i < len; i++) {
             Node item = nodeList.item(i);
             SqlStatement sqlStatement = new SqlStatement();
             sqlStatement.setXmlStatement(xmlStatement);
-            String requestMapping = getNodeAttributeValue(item, "request-mapping");
-            Assert.isNotBlank(requestMapping,"请求方法不能为空!");
+            String validate = DomUtils.getNodeAttributeValue(item, "validate");
+            if (StringUtils.isNotBlank(validate)) {
+                // 支持多个验证
+                for (String validateId : validate.split(",")) {
+                    Assert.isTrue(xmlStatement.containsValidateStatement(validateId), String.format("找不到验证节点[%s]", validateId));
+                    sqlStatement.addValidate(validateId);
+                }
+            }
+            // 设置SqlMode
+            sqlStatement.setSqlMode(SqlMode.valueOf(item.getNodeName().toUpperCase().replace("-", "_")));
+
+            String requestMapping = DomUtils.getNodeAttributeValue(item, "request-mapping");
+            Assert.isNotBlank(requestMapping, "请求方法不能为空!");
             // 设置请求路径
             sqlStatement.setRequestMapping(StringUtils.defaultString(xmlStatement.getRequestMapping()) + requestMapping);
             // 设置请求方法
-            sqlStatement.setRequestMethod(getNodeAttributeValue(item, "request-method"));
-            String returnType = getNodeAttributeValue(item, "return-type");
-            if("int".equalsIgnoreCase(returnType)){
+            sqlStatement.setRequestMethod(DomUtils.getNodeAttributeValue(item, "request-method"));
+            String returnType = DomUtils.getNodeAttributeValue(item, "return-type");
+            if ("int".equalsIgnoreCase(returnType)) {
                 sqlStatement.setReturnType(Integer.class);
-                sqlStatement.setSqlMode(SqlMode.SELECT_NUMBER);
-            }else if("double".equalsIgnoreCase(returnType)){
-                sqlStatement.setSqlMode(SqlMode.SELECT_NUMBER);
+            } else if ("double".equalsIgnoreCase(returnType)) {
                 sqlStatement.setReturnType(Double.class);
-            }else if("long".equalsIgnoreCase(returnType)){
-                sqlStatement.setSqlMode(SqlMode.SELECT_NUMBER);
+            } else if ("long".equalsIgnoreCase(returnType)) {
                 sqlStatement.setReturnType(Long.class);
-            }else if("string".equalsIgnoreCase(returnType)){
+            } else if ("string".equalsIgnoreCase(returnType)) {
                 sqlStatement.setReturnType(String.class);
-            }else if("map".equalsIgnoreCase(returnType)){
-                sqlStatement.setSqlMode(SqlMode.SELECT_ONE);
-            }else{
-                sqlStatement.setSqlMode(SqlMode.SELECT_LIST);
+            } else if ("boolean".equalsIgnoreCase(returnType)) {
+                sqlStatement.setReturnType(Boolean.class);
+            } else {
+                sqlStatement.setReturnType(Map.class);
+            }
+            if (SqlMode.SELECT_LIST == sqlStatement.getSqlMode()) {
                 //设置是否是分页
-                sqlStatement.setPagination("true".equalsIgnoreCase(getNodeAttributeValue(item,"page")));
+                sqlStatement.setPagination("true".equalsIgnoreCase(DomUtils.getNodeAttributeValue(item, "page")));
             }
             SqlNode root = new TextSqlNode("");
             // 解析sql语句
-            parseNodeList(root, item.getChildNodes());
+            parseNodeList(root, document, item.getChildNodes());
             sqlStatement.setSqlNode(root);
             sqlStatements.add(sqlStatement);
         }
@@ -107,52 +139,46 @@ public class S8XMLFileParser {
     /**
      * 递归解析子节点
      */
-    private static void parseNodeList(SqlNode sqlNode, NodeList nodeList) {
+    private static void parseNodeList(SqlNode sqlNode, Document document, NodeList nodeList) {
         for (int i = 0, len = nodeList.getLength(); i < len; i++) {
             Node node = nodeList.item(i);
             if (node.getNodeType() == Node.TEXT_NODE) {
                 sqlNode.addChildNode(new TextSqlNode(node.getNodeValue().trim()));
-            } else if(node.getNodeType() != Node.COMMENT_NODE){
+            } else if (node.getNodeType() != Node.COMMENT_NODE) {
                 String nodeName = node.getNodeName();
-                SqlNode childNode = null;
+                SqlNode childNode;
                 if ("foreach".equals(nodeName)) {
                     childNode = parseForeachSqlNode(node);
                 } else if ("if".equals(nodeName)) {
                     childNode = parseIfSqlNode(node);
+                } else if ("include".equalsIgnoreCase(nodeName)) {
+                    String refId = DomUtils.getNodeAttributeValue(node, "refid");
+                    Assert.isNotBlank(refId, "refid 不能为空!");
+                    Node refSqlNode = (Node) DomUtils.evaluate(String.format("//sql[@id=\"%s\"]", refId), document, XPathConstants.NODE);
+                    Assert.isNotNull(refSqlNode, "找不到sql[" + refId + "]");
+                    childNode = new TextSqlNode(refSqlNode.getTextContent().trim());
                 } else {
                     logger.error("不支持的标签:[{}]", nodeName);
                     return;
                 }
                 sqlNode.addChildNode(childNode);
                 if (node.hasChildNodes()) {
-                    parseNodeList(childNode, node.getChildNodes());
+                    parseNodeList(childNode, document, node.getChildNodes());
                 }
             }
         }
     }
 
-    /**
-     * 获取节点属性
-     *
-     * @param node         节点
-     * @param attributeKey 属性名
-     * @return 节点属性值,未设置时返回null
-     */
-    private static String getNodeAttributeValue(Node node, String attributeKey) {
-        Node item = node.getAttributes().getNamedItem(attributeKey);
-        return item != null ? item.getNodeValue() : null;
-    }
-
     /**
      * 解析foreach节点
      */
     private static ForeachSqlNode parseForeachSqlNode(Node node) {
         ForeachSqlNode foreachSqlNode = new ForeachSqlNode();
-        foreachSqlNode.setCollection(getNodeAttributeValue(node, "collection"));
-        foreachSqlNode.setSeparator(getNodeAttributeValue(node, "separator"));
-        foreachSqlNode.setClose(getNodeAttributeValue(node, "close"));
-        foreachSqlNode.setOpen(getNodeAttributeValue(node, "open"));
-        foreachSqlNode.setItem(getNodeAttributeValue(node, "item"));
+        foreachSqlNode.setCollection(DomUtils.getNodeAttributeValue(node, "collection"));
+        foreachSqlNode.setSeparator(DomUtils.getNodeAttributeValue(node, "separator"));
+        foreachSqlNode.setClose(DomUtils.getNodeAttributeValue(node, "close"));
+        foreachSqlNode.setOpen(DomUtils.getNodeAttributeValue(node, "open"));
+        foreachSqlNode.setItem(DomUtils.getNodeAttributeValue(node, "item"));
         return foreachSqlNode;
     }
 
@@ -160,6 +186,6 @@ public class S8XMLFileParser {
      * 解析if节点
      */
     private static IfSqlNode parseIfSqlNode(Node node) {
-        return new IfSqlNode(getNodeAttributeValue(node, "test"));
+        return new IfSqlNode(DomUtils.getNodeAttributeValue(node, "test"));
     }
 }

+ 27 - 14
src/main/java/com/ssssssss/utils/XmlFileLoader.java

@@ -12,40 +12,53 @@ import java.io.File;
 import java.util.HashMap;
 import java.util.Map;
 
+/**
+ * XML文件加载器
+ */
 public class XmlFileLoader implements Runnable{
 
-    private String pattern;
+    /**
+     * 路径表达式
+     */
+    private String[] patterns;
 
     private Configuration configuration;
 
     private static Logger logger = LoggerFactory.getLogger(XmlFileLoader.class);
 
+    /**
+     * 缓存xml文件修改时间
+     */
     private Map<String,Long> fileMap = new HashMap<>();
 
     private ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
 
-    public XmlFileLoader(String pattern, Configuration configuration) {
-        this.pattern = pattern;
+    public XmlFileLoader(String[] patterns, Configuration configuration) {
+        this.patterns = patterns;
         this.configuration = configuration;
     }
 
     @Override
     public void run() {
         try {
-            Resource[] resources = resourceResolver.getResources(this.pattern);
-            for (int i = 0; i < resources.length; i++) {
-                Resource resource = resources[i];
-                File file = resource.getFile();
-                Long lastModified = fileMap.get(resource.getDescription());
-                //判断是否更新
-                if(lastModified == null || lastModified < file.lastModified()){
-                    XMLStatement xmlStatement = S8XMLFileParser.parse(file);
-                    xmlStatement.getSqlStatements().forEach(configuration::addStatement);
+            for (String pattern : this.patterns) {
+                // 提取所有符合表达式的XML文件
+                Resource[] resources = resourceResolver.getResources(pattern);
+                for (Resource resource : resources) {
+                    File file = resource.getFile();
+                    // 获取上次修改时间
+                    Long lastModified = fileMap.get(resource.getDescription());
+                    // 修改缓存
+                    fileMap.put(resource.getDescription(), file.lastModified());
+                    //判断是否更新
+                    if (lastModified == null || lastModified < file.lastModified()) {
+                        XMLStatement xmlStatement = S8XMLFileParser.parse(file);
+                        xmlStatement.getSqlStatements().forEach(configuration::addStatement);
+                    }
                 }
-                fileMap.put(resource.getDescription(),file.lastModified());
             }
         } catch (Exception e) {
-            logger.error("读取失败",e);
+            logger.error("加载XML失败",e);
         }
     }
 }

+ 22 - 0
src/main/java/com/ssssssss/validator/IValidator.java

@@ -0,0 +1,22 @@
+package com.ssssssss.validator;
+
+import org.w3c.dom.Node;
+
+public interface IValidator {
+
+    /**
+     * 支持的节点名称
+     *
+     * @return
+     */
+    public String support();
+
+    /**
+     * 验证方法
+     *
+     * @param input 输入值
+     * @param node  节点
+     * @return
+     */
+    public boolean validate(Object input, Node node);
+}

+ 21 - 0
src/main/java/com/ssssssss/validator/MaxLenValidator.java

@@ -0,0 +1,21 @@
+package com.ssssssss.validator;
+
+import com.ssssssss.utils.DomUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.w3c.dom.Node;
+
+public class MaxLenValidator implements IValidator {
+    @Override
+    public String support() {
+        return "max-len";
+    }
+
+    @Override
+    public boolean validate(Object input, Node node) {
+        if (input instanceof String) {
+            int len = NumberUtils.toInt(DomUtils.getNodeAttributeValue(node, "value"), 0);
+            return len <= 0 || input.toString().length() <= len;
+        }
+        return false;
+    }
+}

+ 21 - 0
src/main/java/com/ssssssss/validator/MinLenValidator.java

@@ -0,0 +1,21 @@
+package com.ssssssss.validator;
+
+import com.ssssssss.utils.DomUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.w3c.dom.Node;
+
+public class MinLenValidator implements IValidator {
+    @Override
+    public String support() {
+        return "min-len";
+    }
+
+    @Override
+    public boolean validate(Object input, Node node) {
+        if (input instanceof String) {
+            int len = NumberUtils.toInt(DomUtils.getNodeAttributeValue(node, "value"), 0);
+            return len <= 0 || input.toString().length() >= len;
+        }
+        return false;
+    }
+}

+ 15 - 0
src/main/java/com/ssssssss/validator/NotNullValidator.java

@@ -0,0 +1,15 @@
+package com.ssssssss.validator;
+
+import org.w3c.dom.Node;
+
+public class NotNullValidator implements IValidator {
+    @Override
+    public String support() {
+        return "not-null";
+    }
+
+    @Override
+    public boolean validate(Object input, Node node) {
+        return input != null;
+    }
+}

+ 26 - 0
src/main/java/com/ssssssss/validator/RegxValidator.java

@@ -0,0 +1,26 @@
+package com.ssssssss.validator;
+
+import com.ssssssss.utils.DomUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.w3c.dom.Node;
+
+import java.util.regex.Pattern;
+
+public class RegxValidator implements IValidator {
+
+    @Override
+    public String support() {
+        return "regx";
+    }
+
+    @Override
+    public boolean validate(Object input, Node node) {
+        if (input instanceof String) {
+            String regx = DomUtils.getNodeAttributeValue(node, "value");
+            if (StringUtils.isNotBlank(regx)) {
+                return Pattern.compile(regx.trim()).matcher(input.toString()).matches();
+            }
+        }
+        return false;
+    }
+}

+ 0 - 10
src/main/resources/application.properties

@@ -1,10 +0,0 @@
-server.port=9999
-server.servlet.context-path=/ssssssss
-logging.level.com.ssssssss=debug
-
-spring.datasource.url=jdbc:mysql://localhost/test?useSSL=false&useUnicode=true&characterEncoding=UTF8
-spring.datasource.username=root
-spring.datasource.password=123456789
-spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-spring.datasource.initialSize=5
-spring.datasource.minIdle=5

+ 0 - 71
src/main/resources/ssssssss/role/test.xml

@@ -1,71 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<ssssssss request-mapping="/user" >
-    <!-- select/update/insert/delete -->
-    <select request-mapping="/list" request-method="get" page="true">
-        select name from sys_role where name in (
-        <foreach collection="names.split(',')" item="item" separator=",">
-            #{item}
-        </foreach>
-        )
-    </select>
-    <select request-mapping="/one" request-method="get" return-type="map">
-        select name from sys_role
-        <if test="name != null and name.length() > 0">
-            where name like concat('%','${name}','%')
-        </if>
-        limit 1
-    </select>
-    <select request-mapping="/test" page="true">
-        select
-        u.id,
-        u.name,
-        u.login_name,
-        u.unit_name,
-        u.user_type,
-        u.email,
-        u.phone,
-        u.mobile,
-        u.car_id,
-        u.USER_REGISTER_TYPE,
-        o.name office_name
-        from sys_user u
-        left join sys_office o on o.id = u.office_id
-        where u.del_flag = 0
-        <if test="officeId != null and officeId !='' and officeId != '0'.toString()">
-            and find_in_set(u.office_id,getOfficeChildList(#{officeId}))
-        </if>
-        <if test="name != null and name !=''">
-            and u.name like concat('%',#{name},'%')
-        </if>
-        <if test="unitName != null and unitName !=''">
-            and u.unit_name like concat('%',#{unitName},'%')
-        </if>
-        <if test="userRegisterType != null and userRegisterType !=''">
-            and u.user_register_type = #{userRegisterType}
-        </if>
-        <if test="loginName != null and loginName !=''">
-            and u.login_name like concat('%',#{loginName},'%')
-        </if>
-        <if test="carId != null and carId !=''">
-            and u.car_id like concat('%',#{carId},'%')
-        </if>
-        <if test="createBy != null and createBy !=''">
-            and u.create_by = #{createBy}
-        </if>
-        <if test="isRegisterFinish != null and isRegisterFinish != ''">
-            and u.is_register_finish = #{isRegisterFinish}
-        </if>
-        <if test="examineState != null and examineState != ''">
-            and u.EXAMINE_STATE = #{examineState}
-        </if>
-        <if test="roleIds != null and roleIds.length() > 0">
-            and u.id in (
-            select user_id from sys_user_role where role_id in
-            <foreach collection="roleIds.split(',')" item="roleId" open="(" separator="," close=")">
-                #{roleId}
-            </foreach>
-            )
-        </if>
-        order by u.create_date desc
-    </select>
-</ssssssss>