技术要点 注册登录
密码不是原密码加上salt,再MD5加密存入数据库
Token Token是一个任意字符串。只有服务器和微信后台知道这个字符串,token成了这两台服务器之间的密钥,可以防止恶意请求, Token机制在服务端不需要存储session信息 有了token,就可以实现免密码登录 对Token认证机制注意的地方:
一个Token就是一些信息的集合;
在Token中包含足够多的信息,以便在后续请求中减少查询数据库的几率;
服务端需要对cookie和HTTP Authrorization Header进行Token信息的检查;
基于上一点,你可以用一套token认证代码来面对浏览器类客户端和非浏览器类客户端
token结构
每个用户设置一个token,服务器端token关联userid
1
2
3
4
5
private int id;
private int userId;
private Date expired;
private int status;
private String ticket;
服务器端还有一张表,用来记录token,这个思想类似于session共享。 可应用在免密码登录的场景中,电脑登录了网站,现在用手机扫码登录网站,扫码的过程就是讲这个用户的token从数据库取出,然后传递给手机端,这样无需密码登陆了。 token存在数据中,这样所有的服务就可以获取到,再比如登录淘宝就能直接登录支付宝
如何保存token
对于浏览器端 只需要保存在cookie中,用户登录的时候就把token与用户关联起来,同时使用HttpServletResponse设置cookie
对于服务器端 首先需要数据库保存各种token 另一方面,由于token把用户关联起来了,因此我们在得到token的同时也可以得到该用户的信息。 这个用户信息在网页间请求是需要的,因此服务器应该保存所有在线的用户信息,这就用到了ThreadLocal 同时这个用户信息在网页间请求是需要验证的,因此需要拦截器interceptor
ThreadLocal ThreadLocal本意是1
2
3
This class provides thread-local variables.
These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable.
{@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID)
ThreadLocal类用来提供线程内部的局部变量。这些变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量,ThreadLocal实例通常来说都是private static类型. ThreadLocal的主要应用场景为按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。例如:同一个网站登录用户,每个用户服务器会为其开一个线程,每个线程中创建一个ThreadLocal,里面存用户基本信息等,在很多页面跳转时,会显示用户信息或者得到用户的一些信息等频繁操作,这样多线程之间并没有联系而且当前线程也可以及时获取想要的数据。
ThreadLocal可以看做是一个容器,容器里面存放着属于当前线程的变量。ThreadLocal类提供了四个对外开放的接口方法,这也是用户操作ThreadLocal类的基本方法: (1) void set(Object value)设置当前线程的线程局部变量的值。 值得注意的是,set方法中设置的值应该是new出来的对象,而不是在外面new好的对象指针! (2) public Object get()该方法返回当前线程所对应的线程局部变量。 (3) public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。 (4) protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次,ThreadLocal中的缺省实现直接返回一个null。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Returns the value in the current thread's copy of this
thread-local variable. If the variable has no value for thecurrent thread, it is first initialized to the value returned by an invocation of the {@link #initialValue} method.
@return the current thread's value of this thread-local
*/
public T get () {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null ) {
ThreadLocalMap.Entry e = map.getEntry(this );
if (e != null ) {
@SuppressWarnings ("unchecked" )
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set (T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null )
map.set(this , value);
else
createMap(t, value);
}
private T setInitialValue () {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null )
map.set(this , value);
else
createMap(t, value);
return value;
}
为当前线程创建一个ThreadLocalMap的threadlocals,并将第一个值存入到当前map中
@param t the current thread
@param firstValue value for the initial entry of the map
*/
void createMap (Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this , firstValue);
}
public void remove () {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null )
m.remove(this );
}
从线程Thread的角度来看,每个线程内部都会持有一个对ThreadLocalMap实例的引用ThreadLocals,ThreadLocalMap实例相当于线程的局部变量空间,存储着线程各自的数据。 其实在ThreadLocal类中有一个静态内部类ThreadLocalMap(其类似于Map),用键值对的形式存储每一个线程的变量副本, ThreadLocalMap中元素的key为当前ThreadLocal对象,而value对应线程的变量副本,每个线程可能存在多个ThreadLocal。
ThredLocal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static class ThreadLocalMap {
static class Entry extends WeakReference <ThreadLocal <?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super (k);
value = v;
}
* 初始化容量为16,以为对其扩充也必须是2的指数
*/
private static final int INITIAL_CAPACITY = 16 ;
* 真正用于存储线程的每个ThreadLocal的数组,将ThreadLocal和其对应的值包装为一个Entry。
*/
private Entry[] table;
}
http://www.iteye.com/topic/1141743 http://www.jianshu.com/p/33c5579ef44f
每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal. 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.
http://www.cnblogs.com/onlywujun/p/3524675.html http://www.importnew.com/22039.html https://my.oschina.net/xianggao/blog/392440?fromerr=CLZtT4xC
实现 HostHolder通过aop可以在服务器端任何一个位置使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class HostHolder {
private static ThreadLocal<User> users = new ThreadLocal<User>();
public User getUser() {
return users.get();
}
public void setUser(User user) {
users.set(user);
}
public void clear() {
users.remove();;
}
}
拦截器Interceptor
HandlerInterceptor
一般情况下,一个拦截器功能
注册拦截器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class WendaWebConfiguration extends WebMvcConfigurerAdapter {
@Autowired
PassportInterceptor passportInterceptor;
@Autowired
LoginRequiredInterceptor loginRequiredInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截器有顺序问题,注意先后
registry.addInterceptor(passportInterceptor);
//第二个拦截器
registry.addInterceptor(loginRequiredInterceptor).addPathPatterns("/user/*");
super.addInterceptors(registry);
}
}
未登录跳转 再注册一个拦截器,访问/user/ *这样的页面,就回执行该拦截器registry.addInterceptor(loginRequiredInterceptor).addPathPatterns("/user/ *"); 同时,能返回原来要访问的网址,在request中添加一个参数,记录当前访问的页面,然后进拦截器跳转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class LoginRequredInterceptor implements HandlerInterceptor {
@Autowired
HostHolder hostHolder;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
if (hostHolder.getUser() == null) {
//next字段记录该值
httpServletResponse.sendRedirect("/reglogin?next=" + httpServletRequest.getRequestURI());
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
同样地功能还有,查看付费内容等
邮件注册 技术细节 简单服务器 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//访问的网址
@RequestMapping(path = {"/", "/index"})
@ResponseBody
public String Index() {
return "hello, world!";
}
@RequestMapping(path = {"/profile/{groupId}/{userId}"})
@ResponseBody
public String Profile(@PathVariable("userId") int userId,
@PathVariable("groupId") String groupId,
@RequestParam("key") int key,
//看文档时,函数名字是填写的字段的名称,这种默认字段可以用在查询某个值是没有指定页数,可以默认为第一页
@RequestParam(name = "value", defaultValue = "default" , required = false) String value) {
return String.format("profile: groupId - %s, userId - %d , key = %d, value = %s", groupId , userId ,key, value);
}
Http Method
get 获取接口信息
post 提交数据到服务器
put 支持幂等性的POST,就是提交很多次post结果都一样,只产生一次效果
delete 删除服务器的资源
head 紧急查看http的头
opitions 查看支持的方法
velocity相关 语法规则
$!表示变量
#表示一个函数
foreach遍历
parse解析变量,include不解析变量
macro定义宏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#*
注释块
*#
types:$!{types}
#foreach($t in $types)
this is type-> index:$!{foreach.index}, type name :$t, count:$!{foreach.count}
#end
#foreach($key in $map.keySet())
key = $key, value = $map.get($key)
#end
#foreach($key in $map.entrySet())
key = $key.key, value = $key.value
#end
传递对象
User : $!{u.name},
$!{u.des},
$!{u.getDes()}
设置变量
#set($title = "home")
title is $!{title}
引入其他页面
parse:#parse("head.html")
include:#include("head.html")
区别:parse会将变量进行解析,而include不会对变量进行解析,他们主要用在网页的头尾引入
设置宏,就是定义一个函数,很多地方都可以使用
#macro(render_type, $index, $type)
type rendering $index, $type
#end
使用宏
#foreach($t in $types)
#render_type($foreach.index, $t)
#end
双引号解析,单引号不解析
#set($hello = "hello")
#set($hw1 = "$!{hello} world")
#set($hw2 = '$!{hello} world')
$!{hello}
$!{hw1}
$!{hw2}
response 和 request 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@RequestMapping("/req")
@ResponseBody
public String Request(HttpServletRequest request,
HttpServletResponse response,
HttpSession session,
Model model,
@CookieValue("JSESSIONID") String cookieId) {
StringBuffer sb = new StringBuffer();
sb.append("COOKIEID="+cookieId);
sb.append(request.getMethod() + "<br>");
sb.append(request.getPathInfo() + "<br>");
sb.append(request.getQueryString() + "<br>");
sb.append("<br>headers<br>");
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = (String) headerNames.nextElement();
sb.append(name + " : " + request.getHeader(name) + "<br>");
}
response.addCookie(new Cookie("user", "amy"));
response.addHeader("tttt", "sss");
sb.append("<br>cookie<br>");
Cookie[] cookies = request.getCookies();
for (Cookie cc : cookies
) {
sb.append(cc.getName() + " : " + cc.getValue() + "<br>");
}
return sb.toString();
}
重定向和error
301 永久转移与302临时转移
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//302
@RequestMapping(value = {"/redirect/{code}"}, method = {RequestMethod.GET})
public String redirect(@PathVariable("code") int code) {
return "redirect:/";
}
//301
@RequestMapping(value = {"/redirect/{code}"},method = {RequestMethod.GET})
public RedirectView redirect(@PathVariable("code") int code){
RedirectView red = new RedirectView("/",true);
if(code == 301){
red.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
}
return red;
}
异常处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//异常处理
@RequestMapping("/admin")
@ResponseBody
public String admin(@RequestParam("key") String key) {
if (key.equals("admin")) {
return "hello, admin!";
} else {
throw new IllegalArgumentException("参数错误");
}
}
@ExceptionHandler()
@ResponseBody
public String error(Exception e) {
return "error: " + e.getMessage();
}
IoC 在编程过程中,有很多对象及其依赖的对象,他们都需要初始化和相互调用,这样很多对象就会形成一张巨大的网,里面包含了各种调用关系。 传统模式中:通过main函数对每个对象进行初始化,赋值等操作。 而在IoC中,通过Autowired依赖注入的方式完成这个过程,也就是说对象主要在一个地方产生,在剩余的其他地方需要调用该对象时候,只需要通过注入方式声明该对象,即可完成对其初始化的过程。1
2
@Autowired
ExampleService service;
同时有个类就是ExampleService
类似于享元模式
AOP 面向切面编程,就是在不改变原有程序的基础上,在执行某个方法的之前或者之后完成一些功能 比如拦截器、记日志