Love! Share a spring boot based API, restful API project seed (skeleton)

Keywords: Java JSON Spring Mybatis Druid

Preface

Recently, I have used Spring Boot with MyBatis, general Mapper plug-in and PageHelper paging plug-in to do several small and medium-sized API projects. I feel that it is very comfortable to develop this project with this set of framework and tools, and the team's response is also good. In the process of project construction and development, I also summarized some small experience and shared it with you.

Before developing an API project, it's needless to say that building a project, introducing dependency, and configuring a framework are the basic activities. In order to speed up the development of the project (go home early), some common classes and tools need to be encapsulated, such as unified response result encapsulation, unified exception handling, interface signature and authentication, basic addition, deletion, modification and difference method encapsulation, and basic code generation Tools and so on. We can start with these projects.

However, the next time you do similar projects, you may have to go through the above steps again. Although you usually take them to change, you still waste time. Therefore, we can use the idea of object-oriented abstraction and encapsulation to extract the commonalities of these projects and encapsulate them into a seed project (it is estimated that most companies will have many similar seed projects), so that the next time we develop similar projects, we can directly iterate on the seed project, reducing meaningless repetitive work.

Features & offers

Best practice project structure, profiles, streamlined POM


Note: model, dao, service, web and other packages will be created after using code generator to generate code.

Unified response result encapsulation and generation tool

/**
* Unified API response result encapsulation
*/
public class Result {
   private int code;
   private String message;
   private Object data;
   public Result setCode(ResultCode resultCode) {
       this.code = resultCode.code;
       return this;
     }
  //Omit getter and setter methods
}
/**
* Enumeration of response codes, referring to the semantics of HTTP status codes
*/
public enum ResultCode {
   SUCCESS(200),//Success
   FAIL(400),//fail
   UNAUTHORIZED(401),//Not authenticated (signature error)
   NOT_FOUND(404),//Interface does not exist
   INTERNAL_SERVER_ERROR(500);//Server internal error

   public int code;

   ResultCode(int code) {
       this.code = code;
   }
}
/**
* Response result generation tool
*/
public class ResultGenerator {
   private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";

   public static Result genSucce***esult() {
       return new Result()
               .setCode(ResultCode.SUCCESS)
               .setMessage(DEFAULT_SUCCESS_MESSAGE);
   }

   public static Result genSucce***esult(Object data) {
       return new Result()
               .setCode(ResultCode.SUCCESS)
               .setMessage(DEFAULT_SUCCESS_MESSAGE)
               .setData(data);
   }

   public static Result genFailResult(String message) {
       return new Result()
               .setCode(ResultCode.FAIL)
               .setMessage(message);
   }
}

Unified exception handling

  public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
       exceptionResolvers.add(new HandlerExceptionResolver() {
           public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
               Result result = new Result();
               if (e instanceof ServiceException) {//Exception of business failure, such as "wrong account or password"
                   result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
                   logger.info(e.getMessage());
               } else if (e instanceof NoHandlerFoundException) {
                   result.setCode(ResultCode.NOT_FOUND).setMessage("Interface [" + request.getRequestURI() + "] non-existent");
               } else if (e instanceof ServletException) {
                   result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
               } else {
                   result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("Interface [" + request.getRequestURI() + "] Internal error, please contact administrator");
                   String message;
                   if (handler instanceof HandlerMethod) {
                       HandlerMethod handlerMethod = (HandlerMethod) handler;
                       message = String.format("Interface [%s] Exception occurred, method:%s.%s,Exception summary:%s",
                               request.getRequestURI(),
                               handlerMethod.getBean().getClass().getName(),
                               handlerMethod.getMethod().getName(),
                               e.getMessage());
                   } else {
                       message = e.getMessage();
                   }
                   logger.error(message, e);
               }
               responseResult(response, result);
               return new ModelAndView();
           }

       });
   }

Abstract encapsulation of common basic methods

public interface Service<T> {
   void save(T model);//Persistence
   void save(List<T> models);//Batch persistence
   void deleteById(Integer id);//Delete by primary key
   void deleteByIds(String ids);//Batch delete eg: IDS - > 1,2,3,4
   void update(T model);//To update
   T findById(Integer id);//Find by ID
   T findBy(String fieldName, Object value) throws TooManyResultsException; //Through the name of a member variable in the Model (not the name of column in the data table), the value must conform to the unique constraint
   List<T> findByIds(String ids);//Find by multiple IDS / / eg: IDS - > 1,2,3,4
   List<T> findByCondition(Condition condition);//Find by criteria
   List<T> findAll();//Get all
}

Provide code generator to generate basic code

public abstract class CodeGenerator {
  ...
   public static void main(String[] args) {
       genCode("Enter table name");
   }
   public static void genCode(String... tableNames) {
       for (String tableName : tableNames) {
           //It is generated according to the requirements. You can edit the template if there is any problem.
           genModelAndMapper(tableName);
           genService(tableName);
           genController(tableName);
       }
   }
 ...
}

CodeGenerator can generate corresponding Model, Mapper, mapperlml, Service, ServiceImpl and Controller according to the table name (two sets of Controller templates, POST and RESTful, are provided by default, which can be selected by itself in the genController(tableName) method according to needs, and the code template can be customized according to the needs of the actual project, so as to reduce repeated work.

Because each company's business is different, only some simple general method templates are provided, mainly to provide an idea to reduce the duplication of code writing. In our company's actual use, we actually wrote a lot of code templates according to the business abstraction.

Provide simple interface signature authentication

public void addInterceptors(InterceptorRegistry registry) {
   //Interface signature authentication interceptor, which is relatively simple, can be replaced by Json Web Token or other better methods in actual projects.
   if (!"dev".equals(env)) { //Signature authentication ignored in development environment
       registry.addInterceptor(new HandlerInterceptorAdapter() {
           @Override
           public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
               //Verify signature
               boolean pass = validateSign(request);
               if (pass) {
                   return true;
               } else {
                   logger.warn("Signature authentication failed, request interface:{},request IP: {},Request parameters:{}",
                           request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));

                   Result result = new Result();
                   result.setCode(ResultCode.UNAUTHORIZED).setMessage("Signature authentication failed");
                   responseResult(response, result);
                   return false;
               }
           }
       });
   }
}
/**
* A simple signature authentication, rules:
* 1. Sort request parameters by ascii code
* 2. String concatenated as a = value & B = value (excluding sign)
* 3. The md5 signature of the hybrid secret is compared with the requested signature
*/
private boolean validateSign(HttpServletRequest request) {
       String requestSign = request.getParameter("sign");//Obtain the request signature, such as sign=19e907700db7ad91318424a97c54ed57
       if (StringUtils.isEmpty(requestSign)) {
           return false;
       }
       List<String> keys = new ArrayList<String>(request.getParameterMap().keySet());
       keys.remove("sign");//Exclude sign parameter
       Collections.sort(keys);//sort

       StringBuilder sb = new StringBuilder();
       for (String key : keys) {
           sb.append(key).append("=").append(request.getParameter(key)).append("&");//Concatenate string
       }
       String linkString = sb.toString();
       linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);//Remove last '&'

       String secret = "Potato";//Key, modify by yourself
       String sign = DigestUtils.md5Hex(linkString + secret);//Mixed key md5

       return StringUtils.equals(sign, requestSign);//compare
}

Integrate MyBatis, general Mapper plug-in and PageHelper paging plug-in to realize zero SQL for single table business

Using Druid Spring Boot Starter to integrate Druid database connection pool and monitoring

Use FastJsonHttpMessageConverter to improve JSON serialization speed


Posted by northk on Tue, 05 May 2020 03:08:29 -0700