Research on front-end and back-end separation background api interface framework

Keywords: Java JSON Spring github Mobile

Preface

I haven't written an article for a long time. Today I have time to write out what I have always wanted to say, which is a kind of summary. This article mainly talks about the background framework and the interaction with the front-end in the front-end and back-end separation mode (including app development).

The front-end and back-end are separated, and json data is generally transmitted. For parameter extraction, the current common practice is to wrap a response class, which contains three attributes: code, msg and data. Code represents the status code, msg is the message corresponding to the status code, and data is the data returned.

Such as {code":"10008","message","mobile phone number does not exist","total Rows": null", "data": null}

For participation, if there is no norm, there are many kinds, such as:

The getById method of UserController may be as follows:

    

If you put variables in url, this is the case:

    

For example, the addUser method, if it receives parameters directly with the user class, is as follows:

  

It's no problem to write your own front-end and back-end code without separating the front-end from the back-end, but in the case of separating the front-end from the back-end, if you use the user class to receive parameters like this, if you use swagger to generate interface documents, then some fields in the user class are useless for the front-end (createTime, isDe). L. updateTime....) They will also show the front-end. At this time, the front-end has to ask you which parameters are useful and which ones are useless. In fact, for each interface, it's better not to show it parameters that are not useful for the front end. So you define an AddUserRequest class and remove those useless fields to receive the parameters of the addUser method:

  

If you use json format, your method is as follows:

  

If more than one person develops a project, probably the code style is not uniform, you pass json, he is a form submission, you use rest to pass variables in the url, he uses? id=100 for reference,,,,,

Page-by-page queries are written in different ways by different people:

  

Slowly, a large number of custom requests and response objects appear in your project: (Request response objects and DTO s are still necessary and justifiable)

    

With the increase of project code, service and Controller methods become more and more. You have to find a way to find your own code. There are problems, location problems are inconvenient, team skills are uneven (all of them), and it is impossible to restrict everyone's code to write in accordance with the same routine.

Wait a minute.

Text

In view of this, I personally summarized the good design encountered in my work and developed the front-end and back-end separated api interface framework (gradually improving):

    

Technical selection: spring boot, mybatis

The framework is about this structure: the front and back end delivers messages with http json, all requests pass through a unified entry, so the project has only one Controller entry, which is equivalent to a lightweight api gateway. The difference is that there is an additional layer of business layer, which can also be called manager layer. A business only handles one interface. Request.

    

 

 

First of all, the framework is briefly introduced. First, the interface design is introduced. The front and back end interact with each other in the way of http delivering json. The message structure is as follows:

Messages are at Head and body levels:

{
    "message":{
        "head":{
            "transactionType":"10130103",
            "resCode":"",
            "message":"",
            "token":"9007c19e-da96-4ddd-84d0-93c6eba22e68",
            "timestamp":"1565500145022",
            "sign":"97d17628e4ab888fe2bb72c0220c28e3"
        },
        "body":{"userId":"10","hospitalId":"5"}
    }
}

Parameter description:

head: token, timestamp, md5 sign ature, response status code resCode, response message. Transaction Type: Number of each interface, which is regular.

body: Specific business parameters

The project is a unified entry, such as http://localhost:8888/protocol. All interfaces request this entry and transfer the json format, so it is very convenient for the front-end. Every request, just follow the interface document and change the specific business parameters in transactionType and body.

Response parameters:

{
    "message": {
        "head": {
            "transactionType": "10130103",
            "resCode": "101309",
            "message": "Timestamp timeout",
            "token": "9007c19e-da96-4ddd-84d0-93c6eba22e68",
            "timestamp": "1565500145022",
            "sign": "97d17628e4ab888fe2bb72c0220c28e3"
        },
        "body": {
            "resCode": "101309",
            "message": "Timestamp timeout"
        }
    }
}

 

Put out the code of Unified Entry:

  

@RestController
public class ProtocolController extends BaseController{

    private static final Logger LOGGER = LoggerFactory.getLogger(ProtocolController.class);



    @PostMapping("/protocol")
    public ProtocolParamDto dispatchCenter(@RequestParam("transMessage") String transMessage){
        long start = System.currentTimeMillis();
        //Request protocol parameters
        LOGGER.info("transMessage---" + transMessage);
        //Response object
        ProtocolParamDto result = new ProtocolParamDto();
        Message message = new Message();
        //Agreement No.
        String transactionType = "";

        //request header
        HeadBean head = null;
        //Response parameters body map
        Map<String, Object> body = null;

        try {
            //1-The request message is empty
            if (Strings.isNullOrEmpty(transMessage)) {
                LOGGER.info("[" + ProtocolCodeMsg.REQUEST_TRANS_MESSAGE_NULL.getMsg() + "]:transMessage---" + transMessage);
                return buildErrMsg(result,ProtocolCodeMsg.REQUEST_TRANS_MESSAGE_NULL.getCode(),
                        ProtocolCodeMsg.REQUEST_TRANS_MESSAGE_NULL.getMsg(),new HeadBean());
            }
            // Request parameters json Conversion to Object
            ProtocolParamDto paramDto = JsonUtils.jsonToPojo(transMessage,ProtocolParamDto.class);
            //2-json Parsing error
            if(paramDto == null){
                return buildErrMsg(result,ProtocolCodeMsg.JSON_PARS_ERROR.getCode(),
                        ProtocolCodeMsg.JSON_PARS_ERROR.getMsg(),new HeadBean());
            }

            // Check data
            ProtocolParamDto validParamResult = validParam(paramDto, result);
            if (null != validParamResult) {
                return validParamResult;
            }

            head = paramDto.getMessage().getHead();
            //Message business parameters
            Map reqBody = paramDto.getMessage().getBody();


            //Determine whether you need to log in
            //Agreement No.
            transactionType = head.getTransactionType();

            //from spring Container acquisition bean
            BaseBiz baseBiz = SpringUtil.getBean(transactionType);
            if (null == baseBiz) {
                LOGGER.error("[" + ProtocolCodeMsg.TT_NOT_ILLEGAL.getMsg() + "]:Agreement No.---" + transactionType);
                return buildErrMsg(result, ProtocolCodeMsg.TT_NOT_ILLEGAL.getCode(), ProtocolCodeMsg.TT_NOT_ILLEGAL.getMsg(), head);
            }
            //Get whether login annotations are required
            Authentication authentication = baseBiz.getClass().getAnnotation(Authentication.class);
            boolean needLogin = authentication.value();
            System.err.println("Obtain Authentication Note: Do you need to log in?"+needLogin);
            if(authentication != null && needLogin){
                ProtocolParamDto validSignResult = validSign(head, reqBody, result);
                if(validSignResult != null){
                    return  validSignResult;
                }
            }
            // Calibration of parameters
            final Map<String, Object>  validateParams = baseBiz.validateParam(reqBody);
            if(validateParams != null){
                // Request parameters(body)Check failure
                body = validateParams;
            }else {
                //Request parameters body Check success, execute business logic
                body = baseBiz.processLogic(head, reqBody);
                if (null == body) {
                    body = new HashMap<>();
                    body.put("resCode", ProtocolCodeMsg.SUCCESS.getCode());
                    body.put("message", ProtocolCodeMsg.SUCCESS.getMsg());
                }
                body.put("message", "Success");
            }
            // Update the request header to the return object to update the timestamp
            head.setTimestamp(String.valueOf(System.currentTimeMillis()));
            //
            head.setResCode(ProtocolCodeMsg.SUCCESS.getCode());
            head.setMessage(ProtocolCodeMsg.SUCCESS.getMsg());
            message.setHead(head);
            message.setBody(body);
            result.setMessage(message);

        }catch (Exception e){
            LOGGER.error("[" + ProtocolCodeMsg.SERVER_BUSY.getMsg() + "]:Agreement No.---" + transactionType, e);
            return buildErrMsg(result, ProtocolCodeMsg.SERVER_BUSY.getCode(), ProtocolCodeMsg.SERVER_BUSY.getMsg(), head);
        }finally {
            LOGGER.error("[" + transactionType + "] Call End Returns Message Body:" + JsonUtils.objectToJson(result));
            long currMs = System.currentTimeMillis();
            long interval = currMs - start;
            LOGGER.error("[" + transactionType + "] Protocol time-consuming: " + interval + "ms-------------------------protocol time consuming----------------------");
        }
        return result;
    }



}

token authentication in BaseController:

/**
     * Login Check
     * @param head
     * @return
     */
    protected ProtocolParamDto validSign(HeadBean head,Map reqBody,ProtocolParamDto result){
        //Check signature
        System.err.println("Check your signature here: ");
        //The method is blacklist, need to login, check signature
        String accessToken = head.getToken();
        //token Empty
        if(StringUtils.isBlank(accessToken)){
            LOGGER.warn("[{}]:token ---{}",ProtocolCodeMsg.TOKEN_IS_NULL.getMsg(),accessToken);
            return buildErrMsg(result,ProtocolCodeMsg.TOKEN_IS_NULL.getCode(),ProtocolCodeMsg.TOKEN_IS_NULL.getMsg(),head);
        }
        //Blacklist interface, verification token And signature

        // 2.Use MD5 Encryption, in capitalization
        Token token = tokenService.findByAccessToken(accessToken);
        if(token == null){
            LOGGER.warn("[{}]:token ---{}",ProtocolCodeMsg.SIGN_ERROR.getMsg(),accessToken);
            return buildErrMsg(result,ProtocolCodeMsg.SIGN_ERROR.getCode(),ProtocolCodeMsg.SIGN_ERROR.getMsg(),head);
        }
        //token Expired
        if(new Date().after(token.getExpireTime())){
            //token Has expired
            System.err.println("token Expired");
            LOGGER.warn("[{}]:token ---{}",ProtocolCodeMsg.TOKEN_EXPIRED.getMsg(),accessToken);
            return buildErrMsg(result,ProtocolCodeMsg.TOKEN_EXPIRED.getCode(),ProtocolCodeMsg.TOKEN_EXPIRED.getMsg(),head);
        }
        //Signature Rules: 1.Specified sequential concatenation of strings secret+method+param+token+timestamp+secret
        String signStr = token.getAppSecret()+head.getTransactionType()+JsonUtils.objectToJson(reqBody)+token.getAccessToken()+head.getTimestamp()+token.getAppSecret();
        System.err.println("Unsigned string:"+signStr);
        String sign = Md5Util.md5(signStr);
        System.err.println("md5 Signature:"+sign);
        if(!StringUtils.equals(sign,head.getSign())){
            LOGGER.warn("[{}]:token ---{}",ProtocolCodeMsg.SIGN_ERROR.getMsg(),sign);
            return buildErrMsg(result,ProtocolCodeMsg.SIGN_ERROR.getCode(),ProtocolCodeMsg.SIGN_ERROR.getMsg(),head);
        }
        return null;
    }

 

business code is divided into two parts

 

BaseBiz: All business implements this interface. This interface only does two things: 1-parameter checking and 2-business processing. It feels that this step can regulate the behavior of every developer, so everyone writes the same code and looks clean.

  

/**
 * All biz classes implement this interface
 */
public interface BaseBiz {

    /**
     * Calibration of parameters
     * @param paramMap
     * @return
     */
    Map<String, Object> validateParam(Map<String,String> paramMap) throws BusinessException;


    /**
     * Processing business logic
     * @param head
     * @param body
     * @return
     * @throws BusinessException
     */
    Map<String, Object> processLogic(HeadBean head,Map<String,String> body) throws BusinessException;
}

 

Business implementation class: Business only does two things, parameter checking and business logic execution, so there are more business classes in the project, but those request classes are omitted.

@ Authentication(value = true) is a comment I defined to identify whether the interface needs to be logged in. For the time being, this is the only way to do it. It's not good to see two comments on a business. Consider customizing a comment later, considering the function of making business a spring bean, you can omit the @Component comment. Now.

/**
 * To get membership information, you need to log in
 */
@Authentication(value = true)
@Component("10130102")
public class MemberInfoBizImpl implements BaseBiz {


    @Autowired
    private IMemberService memberService;

    @Autowired
    private ITokenService tokenService;


    @Override
    public Map<String, Object> validateParam(Map<String, String> paramMap) throws BusinessException {
        Map<String, Object> resultMap = new HashMap<>();

        // Calibration Membership id
        String memberId = paramMap.get("memberId");
        if(Strings.isNullOrEmpty(memberId)){
            resultMap.put("resCode", ProtocolCodeMsg.REQUEST_USER_MESSAGE_ERROR.getCode());
            resultMap.put("message", ProtocolCodeMsg.REQUEST_USER_MESSAGE_ERROR.getMsg());
            return resultMap;
        }
        return null;
    }

    @Override
    public Map<String, Object> processLogic(HeadBean head, Map<String, String> body) throws BusinessException {
        Map<String, Object> map = new HashMap<>();
        String memberId = body.get("memberId");
        Member member = memberService.selectById(memberId);
        if(member == null){
            map.put("resCode", ProtocolCodeMsg.USER_NOT_EXIST.getCode());
            map.put("message", ProtocolCodeMsg.USER_NOT_EXIST.getMsg());
            return map;
        }
        map.put("memberId",member.getId());//Member id
        map.put("username",member.getUsername());//User name
        return map;
    }
}

With regard to interface security:

1. Token-based security mechanism authentication
a. Landing Authentication
b. Preventing tampering with business parameters
c. Protecting User Sensitive Information
d. Anti-forgery of signatures
2. The overall framework of Token authentication mechanism
The overall architecture is divided into two parts: Token generation and Token authentication.
1. Token generation refers to the generation of Token and key after successful login and storage to Token together with user privacy information and client information.
Tables, returning Token and Secret to the client at the same time.
2. Token authentication means that when client requests blacklist interface, the authentication center generates signature based on Token

Token table structure description:

github: If you feel that it is of some use to you, thank you for giving it to a little star.

  https://github.com/lhy1234/NB-api

Posted by nashruddin on Sun, 11 Aug 2019 01:44:20 -0700