[springboot development monomer web shop] 5. User login and homepage display

Time:2019-11-17

User login

In the previous article, we implemented the function of user registration and authentication, and then we continued to implement its login, as well as the information to be displayed on the page after successful login.
Next, let’s write the code.


Implement service

staycom.liferunner.service.IUserServiceAdd user login method in interface:

public interface IUserService {
    ...
    /**
     *User login
     *@ param userrequestdto request dto
     *@ return login user information
     * @throws Exception
     */
    Users userLogin(UserRequestDTO userRequestDTO) throws Exception;
}

Then, incom.liferunner.service.impl.UserServiceImplImplementation in the implementation class:

@Service
@Slf4j
public class UserServiceImpl implements IUserService {
    ...
    @Override
    public Users userLogin(UserRequestDTO userRequestDTO) throws Exception {
        Log.info ("======== user login request: {}", userrequestdto);
        Example example = new Example(Users.class);
        val condition = example.createCriteria();
        condition.andEqualTo("username", userRequestDTO.getUsername());
        condition.andEqualTo("password", MD5GeneratorTools.getMD5Str(userRequestDTO.getPassword()));
        val user = this.usersMapper.selectOneByExample(example);
        Log.info ("======== user login processing result: {}", user);
        return user;
    }
}

Error Tips:
Here’s a little onePit pointYou must pay attention to the use ofselectOneByExample()When querying, the parameters passed in by this method must betk.mybatis.mapper.entity.ExampleInstance, nottk.mybatis.mapper.entity.Example.CriteriaOtherwise, a dynamic SQL generation query error will be reported. The information is as follows:

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'distinct' in 'class tk.mybatis.mapper.entity.Example$Criteria'
  at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:92)
  at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)
  at com.sun.proxy.$Proxy106.selectOne(Unknown Source)
  at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:159)
  at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:87)
  at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:93)
  at com.sun.proxy.$Proxy109.selectOneByExample(Unknown Source)
  at com.liferunner.service.impl.UserServiceImpl.userLogin(UserServiceImpl.java:80)
  ...

When new people write code, it is particularly easy to write query variables in the previous line, and the next line will be used directly. The simpler the error, the more difficult it is for people to start.

Implement controller

@RestController
@RequestMapping(value = "/users")
@Slf4j
@API (tags = "user management")
public class UserController {
    ...
    @Apioperation (value = user login, notes = user login interface)
    @PostMapping("/login")
    public JsonResponse userLogin(@RequestBody UserRequestDTO userRequestDTO,
                                  HttpServletRequest request,
                                  HttpServletResponse response) {
        try {
            if (StringUtils.isBlank(userRequestDTO.getUsername()))
                Return jsonresponse.errormsg ("user name cannot be empty");
            if (StringUtils.isBlank(userRequestDTO.getPassword()) ||
                    userRequestDTO.getPassword().length() < 8) {
                Return jsonresponse.errormsg ("password is empty or length is less than 8 bits");
            }
            val user = this.userService.userLogin(userRequestDTO);
            UserResponseDTO userResponseDTO = new UserResponseDTO();
            BeanUtils.copyProperties(user, userResponseDTO);
            log.info("BeanUtils copy object {}", userResponseDTO);
            if (null != userResponseDTO) {
                //Set cookie information stored in the front end
                CookieTools.setCookie(request, response, "user",
                        JSON.toJSONString(userResponseDTO), true);
                return JsonResponse.ok(userResponseDTO);
            }
        } catch (Exception e) {
            e.printStackTrace();
            Log. Error ("user login failed, {}, exception = {}", userrequestdto, e.getmessage());
        }
        Return jsonresponse.errormsg ("user login failed");
    }
}

In the above code, the basic verification problem will not be discussed in detail. We mainly focus on some new feature information:

  • com.liferunner.dto.UserResponseDTOEncapsulate the data we need to show to the front end into a new return object, which we can query from the databaseUsersPOJO contains all the user’s data, such aspasswordmobileAnd so on, some users’ private data should not be displayed to the front-end. Even if it is to be displayed, it needs to be desensitized and encrypted. Therefore, it is a common practice to encapsulate a new return object, which only needs to contain the data fields required by the front end.
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Apimodel (value = dto returned from user information, description = return object required after the user logs in successfully)
public class UserResponseDTO {
    /**
     * primary key ID
     */
    private String id;

    /**
     * user name
     */
    private String username;

    /**
     *Nickname nickname
     */
    private String nickname;

    /**
     * head portrait
     */
    private String face;

    /**
     *Gender 1: male 0: female 2: Confidential
     */
    private Integer sex;
}

Here we suggest that you useCtrl+COurscom.liferunner.pojo.UsersObject, and then delete the fields we don’t need. WhyproposalWell, it’s because of the next benefit.

  • org.springframework.beans.BeanUtils.copyProperties(user, userResponseDTO);
    As you can see, what is directly used here isSpring BeanUtilsThe tool class copies the values, which reduces our loop through each field to assign values one by one(SetValue)Work. (it’s also a kind of lazy little skill. It’s not right.)
  • CookieTools.setCookie();
    As we mentioned before, in general, after our users log in, the data will be stored in the local browserCookieFor example, I logged inbaidu.com:
    [springboot development monomer web shop] 5. User login and homepage display

At this point, the mouse is on the left side of the pictureCookies => www.baidu.comRight clickclear, and then refresh our current interface again, the effect is as follows:
[springboot development monomer web shop] 5. User login and homepage display
We can see that the login status has changed to the exit status, andCookiesThe content in is also much less, which shows that Baidu encrypts our user login information and stores it in the browser cookie.
You can see jd.com, taobao.com and so on. They are also implemented in this way. At the beginning of the article, we said that our system is a demo based on production, so we use the mainstream implementation methods. Of course, some students will say that we should transfer the data to the front end and let the front end realize it!!! Of course, you’re right, but it’s not bad for us to master a way of realization, is it?
Here you need a tool class. You can download the relevant code in GitHub portal. Catalogcom.liferunner.utils.CookieTools.

  • com.alibaba.fastjson.JSON.toJSONString(userResponseDTO)
    Because we’re going to return an object, butcookieWhat we need to put inString, here we introduce the JSON tool of Alibaba, andmscx-shop-common/pom.xml, join dependency:

        <dependencies>
          <dependency>
              <groupId>com.alibaba</groupId>
              <artifactId>fastjson</artifactId>
              <version>1.2.56</version>
          </dependency>
      </dependencies>

User logout

After the user operation, we need to log out the user from the system, because our user login information will be stored in the browser cookie, so we need to delete the relevant user cache according to the user’s login operation:

@Apioperation (value = user logout, notes = user logout, httpmethod = post)
    @PostMapping("/logout")
    public JsonResponse userLogout(@RequestParam String uid,
        HttpServletRequest request,HttpServletResponse response){
        // clear front's user cookies
        CookieTools.deleteCookie(request,response,"user");
        // return operational result
        return JsonResponse.ok();
    }

Development and commissioning benefits

Java log tracking

Generally, in e-commerce scenarios, there are extremely strict requirements for the response time of requests. For example, when you buy goods on a website, if you have to wait every time you click a button, or the system feels stuck, you will not hesitate to choose the top right cornerLittle red fork, get rid of it. Therefore, in the development process of our system, we need to monitor the response time of our requests, even through stress testing. However, it’s obviously unreasonable or even uncomfortable for developers to implement this request in every method, so let’s implement a common approach, that is, throughAOP, which is implemented in the aspect oriented way. For the basic use of facets, you can refer to AOP portal. Next, start our coding.
according tospringbootFunction realization Trilogy:
Setp 1. Add dependency

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

Step 2. Start configuration (ignore this step if not)
Setp 3. Annotate
In ourmscx-shop-apiProjects, creatingcom.liferunner.api.aspectPackage, and then createcom.liferunner.api.aspect.CommonLogAspect, the code is as follows:

package com.liferunner.api.aspect;

import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 *Commonlogaspect for: AOP aspect implements log confirmation
 *
 *@ author < a href = "mailto: magicianisaac @ gmail. Com" > isaac.zhang</a>
 * @since 2019/11/11
 */
@Component
@Aspect
@Slf4j
public class CommonLogAspect {

    @Around("execution(* com.liferunner.api.controller..*.*(..))")
    public void recordLogTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("----------- {}.{} process log time started.---------------",
                proceedingJoinPoint.getTarget().getClass(),
                proceedingJoinPoint.getSignature().getName());

        val startTime = System.currentTimeMillis();
        proceedingJoinPoint.proceed();
        val afterTime = System.currentTimeMillis();
        if (afterTime - startTime > 1000) {
            log.warn("cost : {}", afterTime - startTime);
        } else {
            log.info("cost : {}", afterTime - startTime);
        }

        log.info("----------- {}.{} process log time ended.---------------",
                proceedingJoinPoint.getSourceLocation().getClass(),
                proceedingJoinPoint.getSignature().getName());
    }
}
  • The first line of logs represents the class and method we want to monitor
  • proceedingJoinPoint.proceed();Representation execution
  • When the request has been checked for 1000ms, we uselog.warn(...)Log alarm

Step 4. Effect demonstration

  • Time consuming to query users

[springboot development monomer web shop] 5. User login and homepage display

  • Time consuming to register users

[springboot development monomer web shop] 5. User login and homepage display

From the above figure, we can clearly see the time-consuming of each request, and then we can optimize each method accordingly!!!

SQL log tracking

In the process of our development, we often encounter theCRUDBecause we use themybatisDynamic generation of a simple SQL query, rather than manually written, such asUserServiceImpl.javaUser query implemented in andtk.mybatis.mapper.entity.Exampleas well asthis.usersMapper.insertSelective(user);

public Users findUserByUserName(String username) {
        //Build query criteria
        Example example = new Example(Users.class);
        val condition = example.createCriteria()
                .andEqualTo("username", username);
        return this.usersMapper.selectOneByExample(example);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Users createUser(UserRequestDTO userRequestDTO) throws Exception {
        log.info("======begin create user : {}=======", userRequestDTO);
        val user = Users.builder()
                . ID (SID. Next()) // generate distributed ID
                .username(userRequestDTO.getUsername())
                .password(MD5GeneratorTools.getMD5Str(userRequestDTO.getPassword()))
                .birthday(DateUtils.parseDate("1970-01-01", "yyyy-MM-dd"))
                .nickname(userRequestDTO.getUsername())
                .face(this.FACE_IMG)
                .sex(SexEnum.secret.type)
                .createdTime(new Date())
                .updatedTime(new Date())
                .build();
        this.usersMapper.insertSelective(user);
        log.info("======end create user : {}=======", userRequestDTO);
        return user;
    }

Once there is a problem, we often don’t know where the error occurred. At this time, ourSQLWe don’t know if there’s a problem, so let’s configure aseeSmall implementation to SQL:

1. Set the log configuration (as shown in the figure)
[springboot development monomer web shop] 5. User login and homepage display
2. Modify mybatis configuration(log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
[springboot development monomer web shop] 5. User login and homepage display
3.SELECTEffect demonstration
[springboot development monomer web shop] 5. User login and homepage display
4.INSERTEffect demonstration
[springboot development monomer web shop] 5. User login and homepage display
As can be seen from the above figure, the console JDBC operation has been carried out twice. In fact, the first time is to verify our user name. The second timeINSERTIt’s a real insert.

Through the above demonstration results, you can think that it is necessary for us to solve the problems in our daily development. But remember, in the production, the log must be closed, otherwise once the data volume is large, it will cause serious damage to the system performance!!!

Source download

GitHub portal
Gitee portal

Notice of next quarter


In the next section, we will continue to develop the core part of our e-commerce – display of goods and advertisements. I will introduce any development components used in the process through a special section. Brothers, don’t panic!

gogogo!