问题:
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";
}
}