Velocity模板语法(类似Java语法)
$!{ 变量/表达式 }
## 注释 ## #* 多行注释 *#
for
#foreach ($color in $colors)
Color$!{foreach.count}/${foreach.index}:$!{color}
#end
默认目录:templates
文件:xxx.vm
属性访问
$!{user.name}
$!{user.getName()}
HttpServletResponse
response.addCookie(new Cookie(key, value));
response.addHeader(key, value);
HttpServletRequest
request.getHeaderNames();
request.getMethod()
request.getPathInfo()
request.getQueryString()
@RequestMapping(value = {"/response"})
@ResponseBody
public String response(@CookieValue(value = "nowcoderid", defaultValue = "a") String nowcoderId,
@RequestParam(value = "key", defaultValue = "key") String key,
@RequestParam(value = "value", defaultValue = "value") String value,
HttpServletResponse response) {
response.addCookie(new Cookie(key, value));
response.addHeader(key, value);
return "NowCoderId From Cookie:" + nowcoderId;
}
重定向
301:永久转移
302:临时转移
@RequestMapping("/redirect/{code}")
public String redirect(@PathVariable("code") int code,
HttpSession session) {
/*
RedirectView red = new RedirectView("/", true);
if (code == 301) {
red.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
}
return red;*/
session.setAttribute("msg", "Jump from redirect.");
return "redirect:/";
}
redirect前缀,跳到首页,默认是302跳转。
从一个页面跳到另一个页面,所有的访问都是同一个HttpSession,可以在redirect中添加session的一些特性,返回到首页的时候,把session的信息读取出来,显示在首页。用户体验较好。
301是永久迁移,如果是301,会把信息存入浏览器,下次浏览器访问网址,会直接定位到另一个地方。
301是临时迁移。
AOP
面向切面,所有业务都要处理的业务
@Aspect
@Component
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Before("execution(* com.nowcoder.controller.IndexController.*(..))")
public void before(JoinPoint joinPoint) {
StringBuffer sb = new StringBuffer();
for (Object arg : joinPoint.getArgs()) {
sb.append("arg:" + arg.toString());
}
logger.info("before method:" + sb.toString());
}
@After("execution(* com.nowcoder.controller.*Controller.*(..))")
public void after(JoinPoint joinPoint) {
logger.info("after method:");
}
}
JoinPoint是个包装类,相当于面向切面的交汇点,通过getArgs方法可以获得所有的进入controller的输入参数
ViewObject:方便传递任何数据到Velocity
DateTool:velocity自带工具类导入
public class ViewObject {
private Map<String, Object> objs = new HashMap<String, Object>();
public void set(String key, Object value) {
objs.put(key, value);
}
public Object get(String key) {
return objs.get(key);
}
}
可以知道用户是这个用户。
注册成功后会进行自动登陆,对于登陆,在登陆操作中,在service层,进行逻辑判断,对上返回状态回到controller,对下dao去和数据库交互。在service登陆代码中,服务器会生成一个string类型的ticket,存入cookie中,key值是ticket,value值是ticket的值。通过response下发到浏览器。
下次在已经登陆的用户,进行其他点击后。在进入controller前,调用preHandle方法处理,它可以检查客户端提交的cookie中是否有服务器之前下发的ticket,如果有证明这个请求是已经登陆的用户了。把登陆的用户放到线程本地变量。在此时才进入controller,可以拿到具体的用户HostHolder类,这是线程本地变量,可以根据登陆的用户进行个性化渲染,比如关注用户的动态,个人收藏等
新建数据表login_ticket用来存储ticket字段。该字段在用户登录成功时被生成并存入数据库,并被设置为cookie,
下次用户登录时会带上这个ticket,ticket是随机的uuid,有过期时间以及有效状态。
使用拦截器interceptor来拦截所有用户请求,判断请求中是否有有有效的ticket,如果有的话则将用户信息写入Threadlocal。
所有线程的threadlocal都被存在一个叫做hostholder的实例中,根据该实例就可以在全局任意位置获取用户的信息。
该ticket的功能类似session,也是通过cookie写回浏览器,浏览器请求时再通过cookie传递,区别是该字段是存在数据库中的,并且可以用于移动端。
如下有个PassportInterceptor拦截器,它对于所有页面都进行处理
@Component
public class PassportInterceptor implements HandlerInterceptor{
@Autowired
private UserDAO userDAO;
@Autowired
private LoginTicketDAO loginTicketDAO;
@Autowired
private HostHolder hostHolder;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
//处理用户信息,判断是否有ticket,一个用户一个ticket,但是有时限
String ticket = null;
if (httpServletRequest.getCookies() != null) {
for (Cookie cookie : httpServletRequest.getCookies()) {
if (cookie.getName().equals("ticket")) {
ticket = cookie.getValue();
break;
}
}
//判断ticket是否过期和无效
if (ticket != null) {
LoginTicket loginTicket = loginTicketDAO.selectByTicket(ticket);
if (loginTicket == null || loginTicket.getExpired().before(new Date()) || loginTicket.getStatus() != 0) {
return true;
} else {
User user = userDAO.selectById(loginTicket.getUserId());
//将用户信息报错到当前请求线程中
hostHolder.setUsers(user);
return true;
}
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
//渲染之前提供的后处理方法,可以添加模型数据,自动传给前端
if (modelAndView != null && hostHolder.getUser() != null) {
modelAndView.addObject(hostHolder.getUser());
hostHolder.clear();
}
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
hostHolder.clear();
}
}
拦截器1放行,拦截器2 preHandle才会执行。
拦截器2 preHandle不放行,拦截器2 postHandle和afterCompletion不会执行。
只要有一个拦截器不放行,postHandle不会执行
用户数据安全性:
https可以防止运行商加塞广告,
公钥加密私钥解密,
用户密码salt防止破解
token有效期
单一平台的单点登陆,登陆IP异常检验
用户状态的权限判断
添加验证码机制,防止爆破和批量注册。
手机验证码,一瞬间一万个请求,0~9999都发到服务器上,总有一个对的上的。如果一个验证码失效,重新下发token
Spring Boot Dev Tools
动态加载更新的class
编译加载修改的静态文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
上传图片
一、post方法上传图片到服务器本地,返回UUID的string字符串,形成URl,浏览器可以输入127.0.0.1 + UUID生成的string 组成URL,后台会传回给浏览器response,带有图片的二进制流。浏览器的response中就可以看到接收的图片二进制流,显示出来。fiddler可以通过代理的方式,把网络的底层全部拦截下来
上传到七牛云,做云存储
官方文档:
https://developer.qiniu.com/kodo/sdk/1239/java#upload
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.1.1</version>
</dependency>
@Service
public class QiniuService {
private static final Logger logger = LoggerFactory.getLogger(QiniuService.class);
//设置好账号的ACCESS_KEY和SECRET_KEY
String ACCESS_KEY = "DEYWFFCReQqVMYESzB4qo9WYwYtxmVf-8DHU34Jr";
String SECRET_KEY = "5bFWFSYb9Gyx5v-5wYnket58cashXLw2-aZ6D3g2";
//要上传的空间
String bucketname = "toutiao";
//密钥配置
Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
//创建上传对象
UploadManager uploadManager = new UploadManager();
private static String QINIU_IMAGE_DOMAIN = "http://p8aa1ssg4.bkt.clouddn.com/";
//简单上传,使用默认策略,只需要设置上传的空间名就可以了
public String getUpToken() {
return auth.uploadToken(bucketname);
}
public String saveImage(MultipartFile file) throws IOException {
try {
int dotPos = file.getOriginalFilename().lastIndexOf(".");
if (dotPos < 0) {
return null;
}
String fileExt = file.getOriginalFilename().substring(dotPos + 1).toLowerCase();
if (!ToutiaoUtil.isFileAllowed(fileExt)) {
return null;
}
String fileName = UUID.randomUUID().toString().replaceAll("-", "") + "." + fileExt;
//调用put方法上传
Response res = uploadManager.put(file.getBytes(), fileName, getUpToken());
//打印返回的信息
if (res.isOK() && res.isJson()) {
return QINIU_IMAGE_DOMAIN + JSONObject.parseObject(res.bodyString()).get("key");
} else {
logger.error("七牛异常:" + res.bodyString());
return null;
}
} catch (QiniuException e) {
// 请求失败时打印的异常的信息
logger.error("七牛异常:" + e.getMessage());
return null;
}
}
}
云可以做实时缩图和实时切图
云实时缩图
阿里云
http://images.nowcoder.com/images/20150205/60_1423125965233_60_1423125960758_%E7
%AE%A1%E7%90%86%E5%91%98%E5%A4%B4%E5%83%8F.png@0e_100w_100h_0c_1i_1o_9
0Q_1x.png
七牛云
http://7xsetu.com1.z0.glb.clouddn.com/300_300.png?imageView2/1/w/100/h/100/
http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html