毕设 - 用户登录 用cookie来实现会话跟踪

问题:

1、我用的是postman进行后端接口测试,异步传值的时候出现了错误,先开始是不识别前端传来的json数据,是因为我的jackson版本太低了,重新导入了三个jackson包,后面又出现传值不正确的提示,根据源码看了一下是反射到方法时出现了错误,不应该在前台包装好json,直接传入即可;

2、乱码问题,前台页面乱码,这个问题应该好解决;

解决方案:我用的是thyemleaf集成的html页面,问题出在配置的时候只是配置了请求是utf-8编码,回应没有配置,默认还是iso-8859-1,具体解决代码在spring的xml文件下添加:

  <bean id="templateResolver"
          class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
        <property name="prefix" value="" />
        <property name="suffix" value=".html" />
        <!-- HTML is the default value, added here for the sake of clarity.          -->
        <property name="templateMode" value="HTML" />
        
        <!-- 重中之重 -->
        <property name="characterEncoding" value="UTF-8"/>
        
        <!-- Template cache is true by default. Set to false if you want             -->
        <!-- templates to be automatically updated when modified.                    -->
        <property name="cacheable" value="false" />
    </bean>
    <!-- SpringTemplateEngine automatically applies SpringStandardDialect and      -->
    <!-- enables Spring's own MessageSource message resolution mechanisms.         -->
    <bean id="templateEngine"
          class="org.thymeleaf.spring5.SpringTemplateEngine">
        <property name="templateResolver" ref="templateResolver" />

        <!-- Enabling the SpringEL compiler with Spring 4.2.4 or newer can speed up  -->
        <!-- execution in most scenarios, but might be incompatible with specific    -->
        <!-- cases when expressions in one template are reused across different data -->
        <!-- ypes, so this flag is "false" by default for safer backwards            -->
        <!-- compatibility.                                                          -->
        <property name="enableSpringELCompiler" value="true" />
    </bean>
    <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="templateEngine" ref="templateEngine" />
        <!-- NOTE 'order' and 'viewNames' are optional -->
        <property name="order" value="1" />
        <!-- 重中之重 -->
        <property name="characterEncoding" value="UTF-8"/>

        <property name="viewNames" value="*.html,*.xhtml" />
    </bean>

3、异步传值,报错

org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of java.lang.String out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.lang.String out of START_OBJECT token,

原因是在stringify()方法中多写了一个引号,

接着又遇到了 Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported 错误(POST方法默认就是这个类型),后端可以接收数据,但是反不回去结果,后端获取不到前端传来的contentType数据,原因是我自己为了省事在POST中加入了请求类型,他并没有指定的请求类型,必须一个参数一个参数进行获取,

在这个问题中有一个点,这个点就是前台传来的值,后台接收可以为一个对象接收吗?答案是可以,但是必须规定controller为restcontroller风格,但是如果这样两个方法不兼容,导致程序产生错误;当然可以解决这个问题,通过thyemleaf将其缓存,通过json格式直接将整个页面返回,也可以解决;

4、今天发现idea也过期了,这次通过在 http://idea.lanyus.com/  下载一个破解jar包 ,并修改idea64位的启动文件idea64.exe.vmoptions,-javaagent:下载的jar包的地址直接copy上来,然后打开的时候再Active code 将下面代码粘贴:

ThisCrackLicenseId-{
"licenseId":"ThisCrackLicenseId",
"licenseeName":"shr",
"assigneeName":"",
"assigneeEmail":"357396211@qq.com",
"licenseRestriction":"For This Crack, Only Test! Please support genuine!!!",
"checkConcurrentUse":false,
"products":[
{"code":"II","paidUpTo":"2099-12-31"},
{"code":"DM","paidUpTo":"2099-12-31"},
{"code":"AC","paidUpTo":"2099-12-31"},
{"code":"RS0","paidUpTo":"2099-12-31"},
{"code":"WS","paidUpTo":"2099-12-31"},
{"code":"DPN","paidUpTo":"2099-12-31"},
{"code":"RC","paidUpTo":"2099-12-31"},
{"code":"PS","paidUpTo":"2099-12-31"},
{"code":"DC","paidUpTo":"2099-12-31"},
{"code":"RM","paidUpTo":"2099-12-31"},
{"code":"CL","paidUpTo":"2099-12-31"},
{"code":"PC","paidUpTo":"2099-12-31"}
],
"hash":"2911276/0",
"gracePeriodDays":7,
"autoProlongated":false}

搞定;

5、接下来的问题是,如果每次跳向别的链接,我都要从redis手动获取一次cookie代码重复性太多,是不是应该考虑用拦截器来实现这个功能,首先拦截到用户信息,没有则提示登录,有的话把登录信息放到当前线程,这样在其他接口都可以直接获取到用户信息,顺便还实现了接口限流的注解,自定义注解有两种实现方式,一种是基于aop实现,另一种是基于拦截器实现,我用的是拦截器实现的方法,具体流程如下:

定义注解:

package com.caoporn.merchants.access;

import java.lang.annotation.*;

/**
 * @Author: 史皓燃
 * @CreateDate 2019/1/19 14:34
 * <h1>规定每个用户几秒内能点击几次</h1>
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface AccessLimit {

	/** 几秒 */
	int seconds();

	/** 点击次数 */
	int maxCount();

	/** 是否需要登录 */
	boolean needLogin() default true;
}

编写当前线程储存信息类:

package com.caoporn.merchants.access;

import com.caoporn.merchants.entity.User;

/**
 * @Author: 史皓燃
 * @CreateDate 2019/1/19 14:36
 * <h1>将用户登录数据放到每个线程里面</h1>
 */

public class UserContext {

	/** 存放用户的线程 */
	private static ThreadLocal<User> userHolder = new ThreadLocal<User>();

	/** 将user信息防止于当前线程 */
	public static void setUser(User user) {
		userHolder.set(user);
	}

	/** 从当前线程获取user */
	public static User getUser() {
		return userHolder.get();
	}
}

编写拦截器类:

package com.caoporn.merchants.access;

import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.caoporn.merchants.constant.Constant;
import com.caoporn.merchants.entity.User;
import com.caoporn.merchants.redis.RedisService;
import com.caoporn.merchants.redis.normalkey.AccessKey;
import com.caoporn.merchants.result.CodeMsg;
import com.caoporn.merchants.result.Result;
import com.caoporn.merchants.service.ILoginServ;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;

/**
 * @Author: 史皓燃
 * @CreateDate 2019/1/19 14:40
 * <h1>拦截器,拦截用户信息</h1>
 */
public class AccessInterceptor extends HandlerInterceptorAdapter {

	/**
	 * 注入登录服务
	 */
	@Autowired
	private final ILoginServ serv;

	/**
	 * 注入redis
	 */
	@Autowired
	private final RedisService redisService;

	public AccessInterceptor(ILoginServ serv, RedisService redisService) {
		this.serv = serv;
		this.redisService = redisService;
	}

	/**
	 * <h2>前置拦截器,方法执行之前</h2>
	 */
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

		if ((handler instanceof HandlerMethod)) {
			User user = getUser(request, response);
			UserContext.setUser(user);
			HandlerMethod handlerMethod = (HandlerMethod) handler;
			AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);

			if (accessLimit == null) {
				return true;
			}

			int seconds = accessLimit.seconds();
			int maxCount = accessLimit.maxCount();
			boolean needLogin = accessLimit.needLogin();
			String key = request.getRequestURI();

			if (needLogin) {
				user = UserContext.getUser();
				if (user == null) {
					render(response, CodeMsg.SESSION_ERROE);
					return false;
				}
				key += "_" + user.getId();
			} else {
				//do nothing
			}
			AccessKey accessKey = AccessKey.withExpire(seconds);
			Integer count = redisService.get(accessKey, key, Integer.class);

			if (count == null) {
				redisService.set(accessKey, key, 1);
			} else if (count < maxCount) {
				redisService.incr(accessKey, key);
			} else {
				render(response, CodeMsg.ACCESS_LIMIT_REACHED);
				return false;
			}
		}
		return true;
	}

	/**
	 * <h2>通过请求和回应获取cookie</h2>
	 * @param request 请求
	 * @param response 回应
	 * @return {@link User}
	 */
	private User getUser(HttpServletRequest request, HttpServletResponse response) {

		String paramToken = request.getParameter(Constant.TOKEN);
		String cookieToken = getCookieValue(request, Constant.TOKEN);

		if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
			return null;
		}
		String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;
		return serv.getByToken(token, response);
	}

	/**
	 * <h2>通过将请求的所有cookie与回应cookie相比对,获取一致的cookie</h2>
	 * @param token Cookie的名字
	 */
	private String getCookieValue(HttpServletRequest request,String token) {

		Cookie[] cookies = request.getCookies();
		if(cookies == null || cookies.length <= 0){
			return null;
		}

		for (Cookie cookie: cookies) {
			if (cookie.getName().equals(token)) {
				return cookie.getValue();
			}
		}
		return null;
	}

	/**
	 * <h2>将错误抛给前台</h2>
	 *
	 * @param response 回应
	 * @param codeMsg  错误码
	 */
	private void render(HttpServletResponse response, CodeMsg codeMsg) throws IOException {

		response.setContentType("application/json;charset=UTF-8");
		OutputStream out = response.getOutputStream();
		String str = JSON.toJSONString(Result.error(codeMsg));
		out.write(str.getBytes());
		out.flush();
		out.close();
	}
}

在mvc中定义拦截器:

 <!-- 拦截器-->
    <mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.caoporn.merchants.access.AccessInterceptor"></bean>
    </mvc:interceptor>
    </mvc:interceptors>

接下来就可以在controller写这个注解使用了

package com.caoporn.merchants.controller;

import com.caoporn.merchants.access.AccessLimit;
import com.caoporn.merchants.access.UserContext;
import com.caoporn.merchants.entity.User;
import com.caoporn.merchants.service.ILoginServ;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Author: 史皓燃
 * @CreateDate 2019/1/19 15:22
 * <h1>goods controller</h1>
 */
@Slf4j
@Controller
@RequestMapping("/goods")
public class GoodsController {

	@Autowired
	private final ILoginServ serv;

	public GoodsController(ILoginServ serv) {
		this.serv = serv;
	}

	@RequestMapping("/to_list")
	@AccessLimit(seconds=5, maxCount=5, needLogin=true)
	public String list(HttpServletRequest request, HttpServletResponse response, Model model, User user) {

		user = UserContext.getUser();
		log.info("now user is user : {}", user);
		model.addAttribute("user", user);
		return "goods_list.html";
	}
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值