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.