个人项目的一些记录1-后端项目初始化和用户登录注册/退出登录

142 阅读4分钟

项目前端大部分都写完了, 现在补后端并和前端配合修改一部分代码。

后端环境搭建

  1. spring boot官网下载初始项目包;
  2. 配置项目编码等
  3. 数据库源设置好,并将相关配置写入配置文件
spring.datasource.url=jdbc:mysql:...
spring.datasource.username=....
spring.datasource.password=....
  1. 修改启动日志
  • 修改logback日志格式(resources下添加logbackspring.xml)
  • 增加启动成功日志; 启动类XXXXApplication修改为:
private  static final Logger LOG = LoggerFactory.getLogger(XXXXApplication.class);
public static void main(String[] args) {
   xxxxApplication app = new SpringApplication(XXXXXApplication.class);
   Environment env = app.run(args).getEnvironment();
   LOG.info(("启动成功!"));
   LOG.info(("地址:\thttp://127.0.0.1:{}"),env.getProperty("server.port"));
}
  1. hello world接口测试;HTTP Client接口测试;
  2. 接口获取实际数据库信息测试

打算获取user表的信息,首先需要创建好controller、 service和entity以及repositry,

(1)自动生成entity

(2)repositry创建(启动过程中报错,原因是忘记添加@Entity注解),需要在前面的自动生成entity脚本中加入out.println "@Entity"

(3)service 、controller和test脚本完成,然后执行测试,报错下面的内容

javax.servlet.ServletException: Circular view path [login]: would dispatch back to the current handler URL [/login] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)

原因是未加@RestController注解

  1. 解决跨域问题 后端
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //添加映射路径
        registry.addMapping("/**")
                //是否发送Cookie
                .allowCredentials(true)
                //设置放行哪些原始域   SpringBoot2.4.4下低版本使用.allowedOrigins("*")
                .allowedOriginPatterns("*")
                //放行哪些请求方式
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                //.allowedMethods("*") //或者放行全部
                //放行哪些原始请求头部信息
                .allowedHeaders("*")
                //暴露哪些原始请求头部信息
                .exposedHeaders("*");
    }
}

前端(vue官方文档有写)链接

image.png

注册功能

  1. 这部分我之前写的是用邮箱注册,这次决定改为手机号注册。毕竟手机号注册在国内用得更多。
  • 测试注册功能时报错SQL Error: 1064, SQLState: 42000 原因: 数据库有一项名字是保留字use
  • 测试时报错:"message": "Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported". 原因:测试写错
POST http://localhost:8081/api/v1/account/sms/send
Content-Type: application/json 

{
  "mobile": 123434,
  "forWhat": "R"
}
  1. 数据前端验证
  • 这块我之前把大部分内容已经写好了,但这次看竟然有点不记得了..尴尬。 重新研究了一下,我这块前端验证是这样的: 写了一个方法放入mixin中,通过验证把错误信息放入this.error里,然后直接在登录注册相关方法里调用validate这个方法。 image.png 3.其他
  • 问题:手机号输入之后后端提示手机号为空: 原因是这块数据表column名中间改过,前端忘记改
  • 问题:发送验证码后button的倒计时和禁用没达到预期效果。解决:代码有低级错误,变量写错。禁用方面在全局css为禁用的button设置鼠标效果。
  • 增加后端发送短信验证码时先检查手机号是否已存在
  • 增加前端验证码输入框
  • 增加前端密码加密功能

token的生成和后端验证拦截

参考 Token Util

package com.star.string.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class TokenUtil {
    /**
     * token过期时间
     */
    private static final long EXPIRE_TIME = 30L * 24 *  60 * 60 * 1000;

    /**
     * token秘钥
     */
    private static final String TOKEN_SECRET = "BEST_SELF";


    /**
     * 生成签名,30天后过期
     * @param mobile 手机号
     * @param loginTime 登录时间
     * @return 生成的token
     */
    public static String sign(String mobile, String loginTime) {
        try {
            // 设置过期时间
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            // 私钥和加密算法
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            // 设置头部信息
            Map<String, Object> header = new HashMap<>(2);
            header.put("Type", "Jwt");
            header.put("alg", "HS256");
            // 返回token字符串
            return JWT.create()
                    .withHeader(header)
                    .withClaim("mobile", mobile)
                    .withClaim("loginTime", loginTime)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 检验token是否正确
     * @param token 需要校验的token
     * @return 校验是否成功
     */
    public static boolean verify(String token){
        try {
            //设置签名的加密算法:HMAC256
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception e){
            return false;
        }
    }
}

Token Service

import com.star.string.util.TokenUtil;
import org.springframework.stereotype.Service;

import javax.xml.crypto.Data;
import java.util.Date;

@Service
public class TokenService {
    public String generateToken(String mobile){
        return TokenUtil.sign(mobile, String.valueOf(new Date()));
    }
}

拦截器及其配置

import com.alibaba.fastjson2.JSONObject;
import com.star.string.util.TokenUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

@Component
public class TokenInterceptor  implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if ("OPTIONS".equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return true;
        }

        response.setCharacterEncoding("utf-8");

        String token = request.getHeader("Authorization");
        if (token != null) {
            boolean result = TokenUtil.verify(token);
            if (result) {
                System.out.println("通过拦截器");
                return true;
            }
        }
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter out = null;
        try {
            JSONObject json = new JSONObject();
            json.put("success", "false");
            json.put("msg", "认证失败,未通过拦截器");
            json.put("code", "500");
            response.getWriter().append(json.toJSONString());
            System.out.println("认证失败,未通过拦截器");
        } catch (Exception e) {
            e.printStackTrace();
            response.sendError(500);
            return false;
        }
        return false;
    }
}
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    private final TokenInterceptor tokenInterceptor;

    //构造方法
    private InterceptorConfig interceptorConfig;
    public InterceptorConfig(TokenInterceptor tokenInterceptor){
        this.tokenInterceptor = tokenInterceptor;
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(tokenInterceptor)
                .addPathPatterns("/user/**");
        WebMvcConfigurer.super.addInterceptors(registry);

    }

}
  • 前端拦截相关配置

在router中为需要拦截的页面设置meta: { requireAuth: true },然后

router.beforeEach((to, from, next) => {
    // console.log(to.path)
    // console.log(store.getters.isLogged)
   
    let isAuthenticated = store.getters.isLogged
    
    //如果访问的是需要权限的页面并且用户并未处于登录状态
    if (to.matched.some(record => record.meta.requireAuth) && !isAuthenticated) {
    //如果访问的是登录后用户主页,转向游客主页,否则转向登录页面
        if (to.path === '/home') {
            next({ name: 'landingPage' })
        } else {
            next({ path: '/login' })
        }
    } else{
       next()
    }

退出登录

因为登录时把用户信息和token存在了local storage里,所以退出登录时要进行信息的清理。

登录时把信息存入

      if(res.success){
        const token = res.message
        const loginUser = res.content
        this.$store.dispatch('login',{loginUser, token})
        ....
      }

store部分

const vuexLocal = new VuexPersistence({
})

export default createStore({
    state() {
        return {
            loginUser: null,
            token: null,
        }
    },
    getters: {
        isLogged(state) {
            return state.loginUser !== null && state.token !== null
        },
        user(state) {
            return state.loginUser
        },
        token(state){
          return state.token
        },
    },
    mutations: {
        login(state, { loginUser, token }) {
            state.loginUser = loginUser
            state.token = token
        },
        update(state,user){

          state.loginUser = user
          window.localStorage.clear()
        },
        clear(state){
          state.loginUser = null
          state.token = null
        }
    },
    actions: {
       login(context,{loginUser,token}){
         context.commit('login',{loginUser,token})
      },
       logout(context){
         localStorage.clear()
         context.commit('clear')
       }
    },
    plugins: [
        vuexLocal.plugin
    ]
})

退出登录时清除信息并转回landingpage

        const res = await AccountService.logout(this.token)
        if(res.success){
          this.$router.push('/')
          this.$store.dispatch('logout')

同时后端将token数据库对应条目设置为已失效

登录

  • 1. 给登录前端加上密码加密
  • 2. 已登录后进入landingpage和login/register/找回密码页面时自动跳转到登录后首页 方法: 在router中给已登录用户不可访问的页面加上meta属性meta:{LoggedUserVisitable : false}

然后在router.beforeEach方法中添加下面内容

if(to.matched.some(record => record.meta.LoggedUserVisitable === false) && isAuthenticated){
       next({path: '/home'})
  • 3. 根据前端是否勾选保持30天登录修改后端token的有效期(改为24小时)。先用1分钟有效期来进行测试。(之后做)
  • 4. 将退出登录后跳转的页面改为landingpage(存疑,目前退出后自动就跳转到了登录页面)