Super detailed front-end and back-end practical projects (Spring series plus vue3) front-end and back-end articles (4) (step-by-step implementation + source code)

Super detailed front-end and back-end practical projects (Spring series plus vue3) front-end and back-end articles (4) (step-by-step implementation + source code)

Brothers, following yesterday’s code, continue to complete the final user module development,

Yesterday I completed the user information editing page. I will improve it today.

Let’s start from the backend. Make an interceptor to verify the user’s identity.

Interceptor

Let me explain it first:

Let’s use the interview questions to elaborate:

**Interceptor related interview questions: What is the difference between filters and interceptors? **

answer:

  • 1: The order of operation is different

  • The filter is run after the servlet container receives the request and before the servlet is called.

  • The interceptor runs after the servlet is called, but before the response is sent to the client.

  • Two: Different configuration methods

  • The filter is configured in web.xml

  • The interceptor is configured in the spring configuration file or based on annotations.

  • Three: Dependencies
  • Filter depends on the Servlet container, while Interceptor does not depend on the Servlet container
  • Four: Ability

  • Filter can only operate on request and response in the filter.

  • Interceptor can handle request, response, handler, modelAndView, and exception, which is equivalent to Interceptor having the ability to operate components in the SpringMvc ecosystem.

  • Different interface specifications
  • Filters need to implement the Filter interface, and interceptors need to implement the HandlerInterceptor interface.
  • The interception range is different
  • Filter Filter will intercept all resources, while Interceptor will only intercept resources in the Spring environment

OK, let’s make an interceptor:

Create an interceptors package and create the LoginInterceptor class

The class of the interceptor must be inherited

HandlerInterceptor

Note:

First, when creating an interceptor class, you need to implement the HandlerInterceptor interface. This interface defines three methods: preHandle(), postHandle() and afterCompletion(). The preHandle() method is called before the controller method is executed. It returns a Boolean value indicating whether to continue to perform subsequent operations; the postHandle() method is called after the controller method is executed and before the view is parsed. You can Used to further process the response; the afterCompletion() method is called after the entire request is processed and is usually used for resource cleanup.

Secondly, in addition to implementing the HandlerInterceptor interface, you can also choose to inherit the HandlerInterceptorAdapter class to simplify the implementation of the interceptor. HandlerInterceptorAdapter provides an empty implementation of the HandlerInterceptor interface, so that developers only need to rewrite the methods they care about.

Finally, in order for the interceptor to take effect, corresponding configuration needs to be made in the Spring configuration file. This usually involves defining a configuration class, implementing the WebMvcConfigurer interface, and overriding the addInterceptors() method. In this method, you can use addPathPatterns() to specify interception paths and excludePathPatterns() to specify excluded paths.

In actual development, it is very important to choose the appropriate interceptor type according to your needs. For example, if you need to perform permission verification at the Controller layer, then it is appropriate to use the HandlerInterceptor interface; if you only need to perform simple preprocessing and post-processing of the request, you can consider using the WebRequestInterceptor interface. Choosing the right interceptor type ensures clean and maintainable code.

The code is given below

import org.example.cetidenet.utils.JwtUtil;
import org.example.cetidenet.utils.ThreadLocalUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

            return false;
}

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

Here, after inheriting HandlerInterceptor, press the shortcut key Ctrl+o, and then inherit three methods. The first type of preHandle is commonly used here. It will be intercepted and processed by preHandle before the request is sent to the Controller layer. If the return is not true, it will be Interception.

After creating the interceptor, you need to register the interceptor.

Here we operate under the config package created before.

Add code under the class that previously loaded Swagger resources:

@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //The login interface and registration interface are not intercepted
        registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/user/login","/user/register","/doc.html#/home");
    }

There is a small pit here (I previously created a separate class to inherit WebMvcConfigurer, but the interceptor did not work. If so, inherit WebMvcConfigueationSupport)

After adding the intercepted path and the released path, you can check it.

OK, after using the interceptor here, the login interface can be used normally.

Using the update interface returns 401 No Permission. That would be successful.

JWT token:

So after using the interceptor, it intercepts other interfaces except login and registration. How can it be used? Identity verification is necessary. JWT tokens can be used here.

That is, after the user logs in, a JWT token is issued and then fed back to the front end. The front end carries it to the back end, and the interceptor verifies it. If it contains a JWT token, it is released.

Introducing JWT token dependency on the backend

 <!--java-jwtcoordinate-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtil {

    private static final String KEY = "CeTide";

    //Receive business data,generatetokenand return
    public static String genToken(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 ))
                .sign(Algorithm.HMAC256(KEY));
    }

    //take overtoken,verifytoken,and return business data
    public static Map<String, Object> parseToken(String token) {
        return JWT.require(Algorithm.HMAC256(KEY))
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }

} 

Then add code here in the login interface:

 public Result<String> login(UserLoginDTO userLoginDTO) {
        //Get firstDTOAccount mask in
        String username = userLoginDTO.getUserName();
        String password = userLoginDTO.getPassword();

        //First check whether there is this person in the database
        User user = userMapper.findByUsername(username);
        //Determine whether it exists
        if(user == null){
            return Result.error("this user does not exist");
        }
        String salt = password + "ceTide";
        String pwd = DigestUtils.md5Hex(salt.getBytes()).toUpperCase();
        //exist,Determine whether the password is correct
        if(!user.getPassword().equals(pwd)){
            return Result.error("Wrong user password");
        }
//        boolean isLog = logService.addUserLogin(user);
//        if(!isLog){
//            return Result.error("User login logging failed");
//        }
        //login successful
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", user.getId());
        claims.put("username", user.getUserName());
        String token = JwtUtil.genToken(claims);
        //Exists and the password is correct
        return Result.success(token);
    }

Put the user's id and username into the token (although, try not to put the password in)

Then perform verification here in the interceptor:

 @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //Token verification
        System.out.println("Start verification");
        String token = request.getHeader("Authorization");
        //Verification token
        try{
            Map<String,Object> claims = JwtUtil.parseToken(token);

            //release
            return true;
        }catch (Exception e) {
            //Feedback response code401
            response.setStatus(401);
            return false;
        }
    }

If it can be parsed here, it means that you have a JWT token, and it can be released.

Now back to the front end:

Let’s take a look at the modified front-end code first

<template>
  <div id="userCenter">
    <a-layout style="height: 100vh">
      <a-layout-header>
        <a-page-header title="User Center" subtitle="CeTidenet" @click="returnPage"/>
        <div class="header">
          <div class="user-introduce">
            <img
              :src="userNum.userImg"
              width="70px"
              height="70px"
              class="user-img"
            />
            <div>
              <div class="personal-introduce">
                <div style="margin-left: 10px">
                  <span class="name">{{ userNum.userName }}</span>
                  <span class="sex-icon"></span>
                </div>
                <a-space class="btn">
                  <a-button type="dashed" shape="round" @click="handleClick"
                    ><icon-pen-fill />edit information</a-button
                  >
                  <a-button type="dashed" shape="round"
                    ><icon-settings />set up</a-button
                  >
                  <a-button type="dashed" shape="round"
                    ><icon-list />Article management</a-button
                  >
                </a-space>
              </div>
            </div>
          </div>
          <div class="user-follow">
            {{ userNum.attention }}
            <icon-star />
            <span class="follow-num">focus on</span>
            {{ userNum.fans }}
            <icon-heart />
            <span class="follow-num">fan</span>
            {{ userNum.article }}
            <icon-select-all />
            <span class="follow-num">article</span>
          </div>
          <div class="user-follow">Personal profile:{{ userSelfIntroduce }}</div>
        </div>
      </a-layout-header>
      <a-layout style="margin: 24px 180px">
        <a-layout-sider :resize-directions="['right']">
          <a-layout
            style="
              height: 100%;
              text-align: left;
              padding-left: 20px;
              background-color: #c4c4c4;
            "
          >
            <a-layout-content style="height: 20%">
              <h3>CeTidegrade</h3>
            </a-layout-content>
            <a-layout-content style="height: 20%">
              <h3>Personal achievements</h3>
            </a-layout-content>
            <a-layout-content style="height: 60%">
              <h3>Personal updates</h3>
            </a-layout-content>
          </a-layout>
        </a-layout-sider>
        <a-layout-content class="content">
          <h3>User Center</h3>
        </a-layout-content>
      </a-layout>
      <a-layout-footer>Footer</a-layout-footer>
    </a-layout>

    <!-- Edit personal information drawer -->
    <a-drawer
      :visible="visible"
      :width="500"
      @ok="handleOk"
      @cancel="handleCancel"
      unmountOnClose
    >
      <template #title> Edit personal information </template>
      <div :style="{ marginBottom: '20px' }">
        <div >
          <img :src="userNum.userImg" width="70px" height="70px" class="user-img"/>
      <a-button type="primary" @click="handleNestedClick" style="float: right;margin-top: 20px"
        >Change avatar</a-button
      >
        </div>
            <a-divider />
        <div> username:<a-input :style="{width:'320px'}" allow-clear v-model="userNum.userName"/></div>
            <a-divider />
        <div> gender:<a-input :style="{width:'320px'}" v-model="numSex" /></div>
            <a-divider />
        <div> Telephone:<a-input :style="{width:'320px'}" v-model="userNum.phone"/></div>
            <a-divider />
        <div> Birthday:<a-input :style="{width:'320px'}" v-model="userNum.birthday" /></div>
            <a-divider />
        <div> City:<a-input :style="{width:'320px'}" v-model="userNum.county" /></div>
            <a-divider />
        <div> address:<a-input :style="{width:'320px'}" v-model="userNum.address" /></div>
            <a-divider />
        <div> CeTidenetID:<a-input :style="{width:'320px'}" v-model="userNum.id" disabled/></div>
            <a-divider />
        <div> Personal profile: <a-textarea v-model="userSelfIntroduce" allow-clear style="height: 100px"/></div>
      </div>

    </a-drawer>
    <a-drawer
      :visible="nestedVisible"
      @ok="handleNestedOk"
      @cancel="handleNestedCancel"
      unmountOnClose
    >


      <template #title> File operations </template>
            <a-space direction="vertical" :style="{ width: '100%' }" class="picture">
    <a-upload
      action="/"
      :fileList="file ? [file] : []"
      :show-file-list="false"
      @change="onChange"
      @progress="onProgress"
    >
      <template #upload-button>
        <div
          :class="`arco-upload-list-item${
            file && file.status === 'error' ? ' arco-upload-list-item-error' : ''
          }`"
        >
          <div
            class="arco-upload-list-picture custom-upload-avatar"
            v-if="file && file.url"
          >
            <img :src="file.url" />
            <div class="arco-upload-list-picture-mask">
              <IconEdit />
            </div>
            <a-progress
              v-if="file.status === 'uploading' && file.percent < 100"
              :percent="file.percent"
              type="circle"
              size="mini"
              :style="{
                position: 'absolute',
                left: '50%',
                top: '50%',
                transform: 'translateX(-50%) translateY(-50%)',
              }"
            />
          </div>
          <div class="arco-upload-picture-card" v-else>
            <div class="arco-upload-picture-card-text">
              <IconPlus />
              <div style="margin-top: 10px; font-weight: 600">Upload</div>
            </div>
          </div>
        </div>
      </template>
    </a-upload>
  </a-space>
    </a-drawer>
  </div>
</template>

<script setup>
import {userGetInfo} from '../api/user';
import { ref } from "vue";
import avatar from "../assets/userbg.png";
import { useRouter } from "vue-router";
const router = useRouter();
const userInfoList= async()=>{
  let result = await userGetInfo();
  userNum.value = result.data;
}
userInfoList();
const userSelfIntroduce = ref("This person is lazy,nothing left");
const userNum = ref({
  id: "007",
  county: "Sichuan",
  address: "Chengdu",
  phone: "12345678910",
  birthday: "1999-09-09",
  gender: "female",
  email: "123@qq.com",
  userImg: avatar,
  userName: "I&#39;m a clown",
  attention: 0,
  fans: 0,
  article: 0,
});
const numSex = ref(userNum.value.gender === 'F' ? 'male' : 'female');
//Drawer show hide
const visible = ref(false);
const nestedVisible = ref(false);

const handleClick = () => {
  visible.value = true;
};
const handleOk = () => {
  visible.value = false;
};
const handleCancel = () => {
  visible.value = false;
};
const handleNestedClick = () => {
  nestedVisible.value = true;
};
const handleNestedOk = () => {
  nestedVisible.value = false;
};
const handleNestedCancel = () => {
  nestedVisible.value = false;
};

//Return method
const returnPage = () =>{
  router.push('/')
}

//
</script>

<style lang="scss" scoped>
#userCenter {
  background: url("../assets/image.png") no-repeat bottom center / 100% 100%;
}
.header {
  font-family: "Satisfy", cursive;
  margin: 2% 100px;
  height: 20vh;
  background: url("../assets/back.png") no-repeat center / 100% 100%;
  position: relative;
}

.personal-introduce {
  display: flex;
  justify-content: center;
  align-items: flex-end;
  margin-top: 10px;
  text-shadow: 0px 0px 4px rgba(0, 0, 0, 0.31);
  .name {
    line-height: 29px;
    font-size: 26px;
  }
  .sex-icon {
    display: inline-block;
    width: 16px;
    height: 16px;
    margin: 0px 8px;
    margin-bottom: 4px;
    background: url(../assets/user-images/sex-icon.png) no-repeat center;
    background-size: contain;
    border-radius: 50%;
  }
  .level-icon {
    display: inline-block;
    width: 16px;
    height: 16px;
    margin-bottom: 4px;
    background: url(../assets/user-images/leval-icon.png) no-repeat center;
    background-size: contain;
    border-radius: 50%;
  }
}

.user-introduce {
  display: flex;
  justify-items: left;
  padding: 10px;
}
.user-img {
  border-radius: 50%;
  margin-left: 20px;
}
.user-follow {
  margin-left: 30px;
  font-size: 16px;
  display: flex;
  justify-items: left;
}
.follow-num {
  font-size: 16px;
  padding-right: 20px;
}
.content {
  margin-left: 70px;
  background-color: #c4c4c4;
}
.btn {
  position: absolute;
  right: 40px;
}

</style>

After logging in, the JWT value was obtained

“eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiaWQiOjE1LCJ1c2VybmFtZSI6IueUqOaIt1VTRERBRCJ9LCJleHAiOjE3MTY4MDQ4ODB9._9GDxuux5wmoV5CsZCd0QI3wByESKWGGZCKmDaZVlbc”

Such a large string of words, and then the front end places it in the Authorization attribute of the request header

for example

export const userGetInfo= () =>{

    return request.get('/user/getInfo',{

        headers : {

            'Authorization' : 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiaWQiOjE1LCJ1c2VybmFtZSI6IueUqOaIt1VTRERBRCJ9LCJleHAiOjE3MTY4MDQ4ODB9._9GDxuux5wmoV5CsZCd0QI3wByESKWGGZCKmDaZVlbc',

        }

    });

} 

When you visit the page at this time, you can get the user name and other information.

Then even if the request interception and user identity verification are successfully implemented,