当前位置:网站首页>Open source a set of minimalist front and rear end separation project scaffold

Open source a set of minimalist front and rear end separation project scaffold

2020-11-06 21:07:19 itread01

   Preface

  Fast Scaffold It's a very simple front and back separation project scaffold , Include a portal front end 、 One admin back-end , It can be used to quickly set up front and back end separation projects for secondary development

 

   Technology stack

  portal front end :vue + element-ui + avue, Use typescript Syntax encoding

  admin back-end :springboot + mybatis-plus + mysql, Adopt jwt Authentication

 

   Project structure

  

 

 

 

 

  portal front end

   Front end projects , It's us :Vue Project entry example , On this basis, I made a jump

 

   introduce avue

  avue, Based on element-ui Developed a front-end framework for many operations , We are, too test Test module Admin A simple test is done on the page

   Official website :https://avuejs.com/

 

  router To configure

  router Routing configuration , newly added test Module menu routing ,beforeEach There is no token in the judgment , Jump to the login page

router.beforeEach(async(to, from, next) => {
    console.log(" Jump start , The goal is :"+to.path);
    document.title = `${to.meta.title}`;

    // No token , Jump to the login page 
    if (to.name !== 'Login' && !TokenUtil.getToken()){
        console.log(" No token , Jump to the login page ");
        next({ name: 'Login' });
    }

    // Jump to page 
    next();
});

 

  store To configure

  store To configure , newly added user Properties ,getters Provide getUser Method , as well as mutations、actions Of setUser Method

import Vue from 'vue'
import Vuex from 'vuex'
import User from "@/vo/user";
import CommonUtil from "@/utils/commonUtil";
import {Object} from  "@/utils/commonUtil"
import AxiosUtil from "@/utils/axiosUtil";
import TokenUtil from "@/utils/tokenUtil";
import SessionStorageUtil from "@/utils/sessionStorageUtil";

Vue.use(Vuex);

/*
   Make an appointment , Components are not allowed to be changed directly  store  Example of  state, It should be carried out  action  To distribute  (dispatch)  Event notification  store  To change 
 */
export default new Vuex.Store({
  state: {
    user:User,
  },
  getters:{
    getUser: state => {
      return state.user;
    }
  },
  mutations: {
    SET_USER: (state, user) => {
      state.user = user;
    }
  },
  actions: {
    async setUser({commit}){
      let thid = this;
      console.log(" call getUserByToken Interface get login user !");
      AxiosUtil.post(CommonUtil.getAdminUrl()+"/getUserByToken",{token:TokenUtil.getToken()},function (result) {
        let data = result.data as Object;
        commit('SET_USER', new User(data.id,data.username));

        // Set to sessionStorage
        SessionStorageUtil.setItem("loginUser",thid.getters.getUser);
      });
    }
  },
  modules: {
  }
})

 

   Tool class encapsulation

 

  axiosUtil.ts

   Set global withCredentials,timeout

   Set request Intercept , Set... In the request header token token

   Set response Intercept , Set the unified response to the exception message prompt and jump to the login page when the token is invalid

   It encapsulates post、get And so on , Convenient call

 

  commonUtil.ts

   It encapsulates the common use of 、 Common methods , For example, get the back-end service address 、 Get login users, etc

 

  sessionStorageUtil.ts

   Package sessionStorage Session level caching , Easy to set up cache

 

  tokenUtil.ts

   Package token Token utility class , Easy to set token Token to cookie

 

  admin back-end

   Back end projects , Using our :SpringBoot series ——MyBatis-Plus Integrated packaging , On this basis, adjustments have been made

   Only keep tb_user Table module , Other tables and code modules don't need , Change the password to MD5 Encrypted storage

 

   Configuration file

server:
  port: 10086
spring:
  application:
    name: admin
  datasource: # Database related 
    url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&characterEncoding=utf-8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  mvc:
    format:
      date: yyyy-MM-dd HH:mm:ss

  jackson:
    date-format: yyyy-MM-dd HH:mm:ss #jackson Format the date arguments that respond back 
    time-zone: GMT+8

portal:
  url: http://172.16.35.52:10010 # Front end address ( For cross domain configuration )

token:
  secret: huanzi-qch #token Encrypt the private key ( Very important , Keep it secret )
  expire:
    time: 86400000 #token Effective time , Unit millisecond  24*60*60*1000

 

  cors Security cross domain

   establish MyConfiguration, Turn on cors Security cross domain , Details can be found in our previous blog :SpringBoot series ——CORS( Cross source resource sharing )

@Configuration
public class MyConfiguration {

    @Value("${portal.url}")
    private String portalUrl;

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOrigins(portalUrl)
                        .allowedMethods("*")
                        .allowedHeaders("*")
                        .allowCredentials(true).maxAge(3600);
            }
        };
    }
}

 

  jwt Authentication

  maven introduce jwt Rely on

        <!-- JWT -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.5.0</version>
        </dependency>

 

  JwtUtil Tool class , Package generation token, Check token, And according to token Get login users

/**
 * JWT Tool class 
 */
@Component
public class JwtUtil {

    /**
     *  Expiration time , millisecond 
     */
    private static long TOKEN_EXPIRE_TIME;
    @Value("${token.expire.time}")
    public void setExpireTime(long expireTime) {
        JwtUtil.TOKEN_EXPIRE_TIME = expireTime;
    }

    /**
     * token Private key 
     */
    private static String TOKEN_SECRET;
    @Value("${token.secret}")
    public void setSecret(String secret) {
        JwtUtil.TOKEN_SECRET = secret;
    }

    /**
     *  Generate signature 
     */
    public static String sign(String userId){
        // Expiration time 
        Date date = new Date(System.currentTimeMillis() + TOKEN_EXPIRE_TIME);
        // Private key and encryption algorithms 
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
        // Set header information 
        HashMap<String, Object> header = new HashMap<>(2);
        header.put("typ", "JWT");
        header.put("alg", "HS256");
        // With userID Generate signature 
        return JWT.create().withHeader(header).withClaim("userId",userId).withExpiresAt(date).sign(algorithm);
    }

    /**
     *  Verify signature 
     */
    public static boolean verity(String token){
        // Token is empty 
        if(StringUtils.isEmpty(token)){
            return false;
        }

        try {
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();

            // Can I decrypt 
            DecodedJWT jwt = verifier.verify(token);

            // Verification expiration time 
            if(new Date().after(jwt.getExpiresAt())){
                return false;
            }

            return true;
        } catch (IllegalArgumentException | JWTVerificationException e) {
            ErrorUtil.errorInfoToString(e);
        }
        return false;
    }

    /**
     *  According to token Get users id
     */
    public static String getUserIdByToken(String token){
        try {
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            return jwt.getClaim("userId").asString();
        } catch (IllegalArgumentException | JWTVerificationException e) {
            ErrorUtil.errorInfoToString(e);
        }
        return null;
    }
}

 

   Login interceptor

  LoginFilter Login interceptor , Do not block login requests 、 Cross domain pre check request , All other requests are intercepted to check whether there is a token

  PS: We have configured global security across domains , But in the interceptor ,PrintWriter.print Go back response, To manually add a response header tag to allow the other party to cross domain

// Mark the current request Party to allow cross domain access 
response.setHeader("Access-Control-Allow-Credentials","true");
response.setHeader("Access-Control-Allow-Headers","content-type, token");
response.setHeader("Access-Control-Allow-Methods","*");
response.setHeader("Access-Control-Allow-Origin",portalUrl);

 

/**
 *  Login interceptor 
 */
@Component
public class LoginFilter implements Filter {

    @Value("${server.servlet.context-path:}")
    private String contextPath;

    @Value("${portal.url}")
    private String portalUrl;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String method = request.getMethod();

        // Do not block login requests 、 Cross domain pre check request , All other requests are intercepted to check whether there is a token 
        if (!"/login".equals(request.getRequestURI().replaceFirst(contextPath,"")) && !"options".equals(method.toLowerCase())) {
            String token = request.getHeader("token");

            // Verify signature 
            if(!JwtUtil.verity(token)){
                String dataString = "{\"status\":401,\"message\":\" Invalid token token , Access failed , Please log in again !\"}";

                // eliminate cookie
                Cookie cookie = new Cookie("PORTAL_TOKEN", null);
                cookie.setPath("/");
                cookie.setMaxAge(0);
                response.addCookie(cookie);

                // Turn json A string is converted to Object thing , Set to Result And assign it to the return value , Remember to indicate that the current page can be accessed across domains 
                response.setHeader("Access-Control-Allow-Credentials","true");
                response.setHeader("Access-Control-Allow-Headers","content-type, token");
                response.setHeader("Access-Control-Allow-Methods","*");
                response.setHeader("Access-Control-Allow-Origin",portalUrl);

                response.setCharacterEncoding("UTF-8");
                response.setContentType("application/json; charset=utf-8");
                PrintWriter out = response.getWriter();
                out.print(dataString);
                out.flush();
                out.close();

                return;
            }
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }
}

 

   Simple controller

  IndexController controller , Provide three post Method :login Log in ,logout Log out ,getUserByToken Through token Token acquisition login user

@RestController
@RequestMapping("/")
@Slf4j
public class IndexController {

    @Autowired
    private TbUserService tbUserService;

    /**
     *  Log in 
     */
    @PostMapping("login")
    public Result<String> login(@RequestBody TbUserVo entityVo){
        // Focus only on the user name 、 code 
        if(StringUtils.isEmpty(entityVo.getUsername()) || StringUtils.isEmpty(entityVo.getPassword())){
            return Result.build(400," Account number or password cannot be empty ......","");
        }
        TbUserVo tbUserVo = new TbUserVo();
        tbUserVo.setUsername(entityVo.getUsername());
        // code MD5 After encryption, the ciphertext is stored , Match first MD5 Match after encryption 
        tbUserVo.setPassword(MD5Util.getMD5(entityVo.getPassword()));
        Result<List<TbUserVo>> listResult = tbUserService.list(tbUserVo);
        if(Result.OK.equals(listResult.getStatus()) && listResult.getData().size() > 0){
            TbUserVo userVo = listResult.getData().get(0);

            //token
            String token = JwtUtil.sign(userVo.getId()+"");

            return Result.build(Result.OK," Login successful !",token);
        }
        return Result.build(400," Account or password error ...","");
    }

    /**
     *  Log out 
     */
    @PostMapping("logout")
    public Result<String> logout(HttpServletResponse response){
        // eliminate cookie
        Cookie cookie = new Cookie("PORTAL_TOKEN", null);
        cookie.setPath("/");
        cookie.setMaxAge(0);
        response.addCookie(cookie);
        return Result.build(Result.OK," I drive this road , I planted this tree , To pass by from here on , leave token token !","");
    }

    /**
     *  Through token Token acquisition login user 
     */
    @PostMapping("getUserByToken")
    public Result<TbUserVo> getUserByToken(@RequestBody TbUserVo entityVo){
        String userId = JwtUtil.getUserIdByToken(entityVo.getToken());
        Result<TbUserVo> result = tbUserService.get(userId);
        result.getData().setPassword(null);
        return userId == null ? Result.build(500," Operation failed !",new TbUserVo()) : result;
    }
}

 

   Effect demonstration

 

   Log in

   This is a minimalist login page 、 Login function , No token , The route will block the jump to the login page

   Save after successful login token Token to cookie in , And get login user information , Store in Store in

   To solve the problem of rearranging the page Store Data loss , At the same time, store a copy of the data to sessionStorage Get it , Reading Store When there is no information , Read cache first , If there is , Set it back to Store in

   Leave blank after successful login Store、sessionStorage

 

   Home

   Minimalist project homepage , Path /, Usually used as a project home page , Now the page is a simple welcome page , It includes several router-link Routing and logout button

  

 

  test Test

   Integrated vue Simple tests such as data binding

  info Test

   Get the current active configuration environment Branch , Test the configuration of the file

  admin Test

  element-ui Match up avue, It can be built quickly admin Background management page and function

 

   Packaged deployment

  portal front end

   It's already configured package.json Archives

  "scripts": {
    "dev": "vue-cli-service serve --mode dev",
    "test": "vue-cli-service test --mode test",
    "build": "vue-cli-service build  --mode prod"
  },

 

   At the same time ,vue.config.js The build path is configured in

    publicPath: './',
    outputDir: 'dist',
    assetsDir: 'static',

  

   Execute build command , It will be in package.json Under the directory at the same level , establish dist Folder , The generated files are in it

   Put the generated files in Tomcat In containers or other containers , Execution container , front end portal Project complete deployment

  

  admin back-end

  pom The file has already set the package configuration

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <finalName>${project.artifactId}</finalName>
        <outputDirectory>package</outputDirectory>
    </configuration>
</plugin>

  maven Direct execution package command , It will be with pom File under the same level directory to create package Folder , Generated jar The bag is in it

   Use java command :java -jar admin.jar, Execute jar package , back-end admin Project complete deployment

 

   Postscript

   A set of minimalist front and back separation project scaffolding is recorded here for the time being , I'll add it later  

 

   Open source code

    notes :admin Back end database files are in admin Back end projects resources/sql Under the table of contents

 

   The code is open source 、 Trust to my GitHub、 Code cloud :

  GitHub:https://github.com/huanzi-qch/fast-scaffold

   Code cloud :https://gitee.com/huanzi-qch/fast-scaffold

版权声明
本文为[itread01]所创,转载请带上原文链接,感谢