博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
springmvc+shiro+freemarker实现的安全及权限管理
阅读量:7207 次
发布时间:2019-06-29

本文共 30250 字,大约阅读时间需要 100 分钟。

本文讲述了基于springmvc+shiro实现安全管理,shiro+freemarker实现权限验证。

首先我们从web.xml开始:

1 
2
5
6
contextConfigLocation
7
8 classpath:resources/tag-context.xml 9 classpath:resources/shiro-context.xml 10
11
12
13
14
log4jConfigLocation
15
classpath:resources/log4j.properties
16
17
18
log4jDelay
19
10000
20
21
22
23
24 org.springframework.web.context.ContextLoaderListener 25
26
27
28
29 com.itrip.rp.listener.InitConfigListener 30
31
32
33
34
org.springframework.web.util.Log4jConfigListener
35
36
37
38
requestLogFilter
39
40 com.itrip.rp.filter.RequestLogFilter 41
42
43
44
requestLogFilter
45
/*
46
47
48
49
encoding
50
org.springframework.web.filter.CharacterEncodingFilter
51
52
encoding
53
UTF-8
54
55
56
57
encoding
58
/*
59
60
61
62
63
shiroFilter
64
org.springframework.web.filter.DelegatingFilterProxy
65
true
66
67
targetFilterLifecycle
68
true
69
70
71
72
shiroFilter
73
/*
74
75
76
77
resourcePlatform
78
79 org.springframework.web.servlet.DispatcherServlet 80
81
82
contextConfigLocation
83
84 classpath:resources/applicationContext-*.xml 85
86
87
1
88
89
90
resourcePlatform
91
/*
92
93

按照web.xml初始化顺序依次为:context-param--->listener--->filter--->servlet

tag-context.xml中主要配置了权限验证标签,代码如下:

1 
2
6
7
8

权限验证标签实现类是基于freemarker标签的实现方式,具体请看源代码:

1 package com.itrip.rp.core.permission;  2  3 import java.io.IOException;  4 import java.util.Map;  5  6 import org.apache.shiro.SecurityUtils;  7 import org.apache.shiro.subject.Subject;  8  9 import com.itrip.rp.common.Constants; 10 import com.itrip.rp.core.freemarker.DirectiveUtils; 11 12 import freemarker.core.Environment; 13 import freemarker.template.TemplateDirectiveBody; 14 import freemarker.template.TemplateDirectiveModel; 15 import freemarker.template.TemplateException; 16 import freemarker.template.TemplateModel; 17 18 /** 19  * 后台管理员权限许可 20  * 21  * @author Benny 22  */ 23 public class PermissionDirective implements TemplateDirectiveModel {
24 25 /*** 26 * 权限验证 27 */ 28 @SuppressWarnings("unchecked") 29 public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {
30 String url = DirectiveUtils.getString(Constants.PARAM_URL, params); 31 Subject subject = SecurityUtils.getSubject(); 32 boolean pass = subject.isPermitted(url); 33 if (pass) {
34 body.render(env.getOut()); 35 } 36 } 37 }
Constants.PARAM_URL="url"; //对应的值就是取freemarker标签中的url

标签形式如:

<@perm url="/product/add">
Subject subject = SecurityUtils.getSubject(); //这一步是基于shiro获取认证用户对象
boolean pass = subject.isPermitted(url); //这一步就是进行权限验证,权限验证通过返回true,反之返回false

这里还是非常简单的。

接下来让我们看看shiro的具体配置吧,还是先看源代码:

1 
2
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 /css/** = anon 23 /img/** = anon 24 /js/** = anon 25 /favicon.ico = anon 26 /login = authc 27 /logout = logout 28 /** = user 29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
classpath:resources/ehcache-shiro.xml
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

shiro缓存配置文件:ehcache-shiro.xml

1 
2
3
4
14
22 23
27
1 /css/** = anon2 /img/** = anon3 /js/** = anon4 /favicon.ico = anon

这里是对静态资源的处理,静态资源不做认证。

重要的是以下三个拦截器,分别实现了用户认证,用户检查及退出系统过程。

1 
2
3
4
5
6
7

先看看用户认证authcFilter吧:

1 package com.itrip.rp.core.security;   2   3 import java.util.Date;   4   5 import javax.servlet.ServletRequest;   6 import javax.servlet.ServletResponse;   7 import javax.servlet.http.HttpServletRequest;   8 import javax.servlet.http.HttpServletResponse;   9  10 import org.apache.shiro.authc.AuthenticationToken;  11 import org.apache.shiro.subject.Subject;  12 import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;  13 import org.apache.shiro.web.util.WebUtils;  14 import org.slf4j.Logger;  15 import org.slf4j.LoggerFactory;  16  17 import com.itrip.rp.common.Constants;  18 import com.itrip.rp.entity.beans.UserBaseInfo;  19 import com.itrip.rp.exception.AuthenticationException;  20 import com.itrip.rp.exception.DisabledException;  21 import com.itrip.rp.exception.UsernameNotFoundException;  22 import com.itrip.rp.service.AuthenticationService;  23 import com.itrip.rp.service.LogService;  24 import com.itrip.rp.service.UserService;  25 import com.itrip.rp.session.SessionProvider;  26 import com.itrip.rp.utils.DateFormatUtils;  27 import com.itrip.rp.utils.RequestUtils;  28 import com.itrip.rp.utils.SpringContextUtil;  29  30 /**  31  * 自定义登陆认证filter  32  *  33  * @author Benny  34  */  35 public class AdminAuthenticationFilter extends FormAuthenticationFilter {
36 37 private Logger logger = LoggerFactory.getLogger("security"); 38 39 /** 40 * 执行登陆操作 41 */ 42 @Override 43 protected boolean executeLogin(ServletRequest request, ServletResponse response) {
44 AuthenticationToken token = createToken(request, response); 45 if (token == null) {
46 String msg = "create AuthenticationToken error"; 47 throw new IllegalStateException(msg); 48 } 49 String username = (String) token.getPrincipal(); 50 UserBaseInfo user = userService.login(username); 51 if (user != null) {
52 if (!user.getStatus()) {
53 // 用户禁用 54 return onLoginFailure(username, token, new DisabledException(), request, response); 55 } 56 } else {
57 // 用户名不存在 58 return onLoginFailure(username, token, new UsernameNotFoundException(), request, response); 59 } 60 try {
61 Subject subject = getSubject(request, response); 62 subject.login(token); 63 return onLoginSuccess(user, token, subject, request, response); 64 } catch (Exception e) {
65 // TODO Auto-generated catch block 66 return onLoginFailure(username, token, new AuthenticationException(), request, response); 67 } 68 } 69 70 /** 71 * 初始化service及登陆跳转 72 */ 73 @Override 74 public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
75 if (userService == null) {
76 userService = (UserService) SpringContextUtil.getBean(UserService.class); 77 } 78 if (logService == null) {
79 logService = (LogService) SpringContextUtil.getBean(LogService.class); 80 } 81 if (authService == null) {
82 authService = (AuthenticationService) SpringContextUtil.getBean(AuthenticationService.class); 83 } 84 if (session == null) {
85 session = (SessionProvider) SpringContextUtil.getBean(SessionProvider.class); 86 } 87 boolean isAllowed = isAccessAllowed(request, response, mappedValue); 88 // 登陆跳转 89 if (isAllowed && isLoginRequest(request, response)) {
90 try {
91 issueSuccessRedirect(request, response); 92 } catch (Exception e) {
93 logger.error("", e); 94 } 95 return false; 96 } 97 return isAllowed || onAccessDenied(request, response, mappedValue); 98 } 99 100 @Override 101 protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
102 HttpServletRequest req = (HttpServletRequest) request; 103 HttpServletResponse res = (HttpServletResponse) response; 104 String successUrl = getAdminIndex() != null ? getAdminIndex() : super.getSuccessUrl(); 105 WebUtils.redirectToSavedRequest(req, res, successUrl); 106 } 107 108 @Override 109 protected boolean isLoginRequest(ServletRequest req, ServletResponse resp) {
110 String loginUrl = getAdminLogin() != null ? getAdminLogin() : super.getLoginUrl(); 111 return pathsMatch(loginUrl, req); 112 } 113 114 /** 115 * 登陆成功 116 */ 117 private boolean onLoginSuccess(UserBaseInfo user, AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response)118 throws Exception {
119 HttpServletRequest req = (HttpServletRequest) request; 120 HttpServletResponse res = (HttpServletResponse) response; 121 // 记录用户登陆信息 122 authService.login(user, RequestUtils.getIpAddr(req), req, res, session); 123 // 将系统当前登陆用户信息放入session 124 session.setAttribute(req, Constants.USERNAME, user.getNickName()); 125 Date lastLogin = authService.findSecond(user.getUserId()); 126 if (lastLogin != null) {
127 session.setAttribute(req, Constants.LAST_LOGIN_TIME, DateFormatUtils.format(lastLogin, "yyyy-MM-dd HH:mm:ss")); 128 } 129 logService.loginSuccess(req, user.getUserId(), "login.log.loginSuccess");130 return super.onLoginSuccess(token, subject, request, response); 131 } 132 133 /** 134 * 登陆失败 135 */ 136 private boolean onLoginFailure(String username, AuthenticationToken token, AuthenticationException e, ServletRequest request, 137 ServletResponse response) {
138 HttpServletRequest req = (HttpServletRequest) request; 139 logService.loginFailure(req, "login.log.loginFailure", "userName=" + username); 140 request.setAttribute(Constants.MESSAGE, e.getMessage()); 141 return super.onLoginFailure(token, e, request, response); 142 } 143 144 private UserService userService; 145 private LogService logService; 146 private SessionProvider session; 147 private AuthenticationService authService; 148 149 private String adminIndex; 150 151 private String adminLogin; 152 153 public String getAdminIndex() {
154 return adminIndex; 155 } 156 157 public void setAdminIndex(String adminIndex) {
158 this.adminIndex = adminIndex; 159 } 160 161 public String getAdminLogin() {
162 return adminLogin; 163 } 164 165 public void setAdminLogin(String adminLogin) {
166 this.adminLogin = adminLogin; 167 } 168 }

需要说明的就是service的获取,在filter中获取spring自动注入bean需要通过spring上下文来获取,这里定义实现了获取spring上下文及注入bean的工具类SpringContextUtil.java稍后会详细说明这个类以及配置。

用户登录操作“/login”交由authcFilter处理,登录controller代码很简单:

1 package com.itrip.rp.controller;  2  3 import javax.servlet.http.HttpServletRequest;  4 import javax.servlet.http.HttpServletResponse;  5  6 import org.apache.commons.lang.StringUtils;  7 import org.slf4j.Logger;  8 import org.slf4j.LoggerFactory;  9 import org.springframework.stereotype.Controller; 10 import org.springframework.ui.ModelMap; 11 import org.springframework.web.bind.annotation.RequestMapping; 12 13 import com.itrip.rp.common.Constants; 14 import com.itrip.rp.controller.base.BaseController; 15 16 /** 17  * 系统登陆Controller 18  * 19  * @author Benny 20  * 21  */ 22 @Controller 23 public class LoginController extends BaseController {
24 25 protected static final Logger LOG = LoggerFactory.getLogger("run"); 26 27 /** 28 * 登陆 29 * 30 * @param request 31 * @param response 32 * @param model 33 * @return 34 */ 35 @RequestMapping(value = "/login") 36 public String login(HttpServletRequest request, HttpServletResponse response, ModelMap model) {
37 return "login"; 38 } 39 40 /** 41 * 系统首页 42 * 43 * @param message 44 * @param request 45 * @param response 46 * @param model 47 * @return 48 */ 49 @RequestMapping(value = "/index") 50 public String index(String message, HttpServletRequest request, HttpServletResponse response, ModelMap model) {
51 if (!StringUtils.isBlank(message)) {
52 model.addAttribute(Constants.MESSAGE, message); 53 } 54 return "product/index"; 55 } 56 }

登录页面代码如下,username、password不要写错了:

1   2   3 
4
5
6
User login
7
8
9
10
11
12
13
14
15
16
${message!} 17
18
19
20 21

用户认证检查userFilter:

1 package com.itrip.rp.core.security;  2  3 import java.io.IOException;  4  5 import javax.servlet.ServletRequest;  6 import javax.servlet.ServletResponse;  7 import javax.servlet.http.HttpServletRequest;  8 import javax.servlet.http.HttpServletResponse;  9 10 import org.apache.shiro.web.filter.authc.UserFilter; 11 import org.apache.shiro.web.util.WebUtils; 12 13 /** 14  * 用户认证检查filter 15  * 16  * @author Benny 17  */ 18 public class AdminUserFilter extends UserFilter {
19 // 未登陆重定向到登陆页 20 protected void redirectToLogin(ServletRequest req, ServletResponse resp) throws IOException {21 HttpServletRequest request = (HttpServletRequest) req; 22 HttpServletResponse response = (HttpServletResponse) resp; 23 WebUtils.issueRedirect(request, response, getLoginUrl()); 24 } 25 }

最后是退出系统logoutFilter:

1 package com.itrip.rp.core.security;  2  3 import javax.servlet.ServletRequest;  4 import javax.servlet.ServletResponse;  5 import javax.servlet.http.HttpServletRequest;  6  7 import org.apache.commons.lang.StringUtils;  8 import org.apache.shiro.subject.Subject;  9 import org.apache.shiro.web.filter.authc.LogoutFilter; 10 11 import com.itrip.rp.common.Constants; 12 13 /** 14  * 退出系统 filter 15  * 16  * @author Benny 17  */ 18 public class AdminLogoutFilter extends LogoutFilter {
19 20 @Override 21 protected String getRedirectUrl(ServletRequest req, ServletResponse resp, Subject subject) {
22 HttpServletRequest request = (HttpServletRequest) req; 23 String redirectUrl = request.getParameter(Constants.RETURN_URL); 24 if (StringUtils.isBlank(redirectUrl)) {
25 redirectUrl = getLogoutUrl(); 26 if (StringUtils.isBlank(redirectUrl)) {
27 redirectUrl = getRedirectUrl(); 28 } 29 } 30 return redirectUrl; 31 } 32 33 private String logoutUrl; 34 35 public void setLogoutUrl(String logoutUrl) {
36 this.logoutUrl = logoutUrl; 37 } 38 39 public String getLogoutUrl() {
40 return logoutUrl; 41 } 42 }

接下来让我们看看自定义实现的登录认证及授权Realm吧:

1 package com.itrip.rp.core.security;  2  3 import java.util.HashSet;  4 import java.util.List;  5 import java.util.Set;  6  7 import org.apache.shiro.authc.AuthenticationException;  8 import org.apache.shiro.authc.AuthenticationInfo;  9 import org.apache.shiro.authc.AuthenticationToken; 10 import org.apache.shiro.authc.SimpleAuthenticationInfo; 11 import org.apache.shiro.authc.UsernamePasswordToken; 12 import org.apache.shiro.authz.AuthorizationInfo; 13 import org.apache.shiro.authz.SimpleAuthorizationInfo; 14 import org.apache.shiro.realm.AuthorizingRealm; 15 import org.apache.shiro.subject.PrincipalCollection; 16 import org.apache.shiro.subject.SimplePrincipalCollection; 17 import org.apache.shiro.util.CollectionUtils; 18 19 import com.itrip.rp.entity.beans.UserBaseInfo; 20 import com.itrip.rp.service.UserService; 21 import com.itrip.rp.utils.SpringContextUtil; 22 23 /** 24  * 认证及授权Realm 25  * 26  * @author Benny 27  */ 28 public class AdminAuthorizingRealm extends AuthorizingRealm {29 30     /** 31      * 登陆认证 32      */ 33     @Override34     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
35 UsernamePasswordToken token = (UsernamePasswordToken) authcToken; 36 if (userService == null) {
37 userService = (UserService) SpringContextUtil.getBean(UserService.class); 38 } 39 UserBaseInfo user = userService.login(token.getUsername()); 40 if (user != null) {
41 return new SimpleAuthenticationInfo(user, user.getPassword(), getName()); 42 } else {
43 return null; 44 } 45 } 46 47 /** 48 * 授权 49 */ 50 @Override 51 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
52 // TODO Auto-generated method stub 53 UserBaseInfo user = (UserBaseInfo) principals.getPrimaryPrincipal(); 54 SimpleAuthorizationInfo auth = new SimpleAuthorizationInfo(); 55 if (user != null) {
56 if (userService == null) {
57 userService = (UserService) SpringContextUtil.getBean(UserService.class); 58 } 59 List
perms = userService.getPerms(user.getUserId()); 60 Set
set = new HashSet
(perms); 61 if (!CollectionUtils.isEmpty(perms)) {
62 // 权限加入AuthorizationInfo认证对象 63 auth.setStringPermissions(set); 64 } 65 } 66 return auth; 67 } 68 69 /** 70 * 清空用户权限缓存 71 * 72 * @param username 73 */ 74 public void removeUserAuthorizationInfoCache(String username) {
75 SimplePrincipalCollection pc = new SimplePrincipalCollection(); 76 pc.add(username, super.getName()); 77 super.clearCachedAuthorizationInfo(pc); 78 } 79 80 private UserService userService; 81 }

登录认证成功后将用户对象放入AuthenticationInfo中,以便授权过程中直接使用用户对象。

授权操作只有在第一次进行权限验证的时候才会初始化(比较重要),将用户所拥有的所有权限放入AuthorizationInfo对象。

当用户权限发生变化,就需要手动调用removeUserAuthorizationInfoCache方法去清除用户权限缓存。

最后再看看springmvc配置文件:

1 
20 21
22
23 24
25
27
28
29 30
31
32
33
34
35
36
37
38
39
40
41
42
/login
43
/logout
44
45
46
47 48
49
50
51
52 53
54
56
57
58
59
61
62
63
text/html;charset=UTF-8
64
65
66
67
68
69
70
application/json;charset=UTF-8
71
72
73
74
75
76
77
78
79
80
81
error/404
82
error/404
83
84
85
86
87
88 89
90
91 92
93
94
95
96
97
98
99
100
101
102
103
105
106
107
108
109
110
111
112
113
114
115
0
116
UTF-8
117
UTF-8
118
zh_CN
119
true,false
120
yyyy-MM-dd HH:mm:ss
121
yyyy-MM-dd
122
HH:mm:ss
123
0.######
124
true
125
126
127
128
129
130
131
132
133
134

这里要重点说明的就是之前提到过的spring上下文工具类:SpringContextUtil.java

1 package com.itrip.rp.utils;  2  3 import org.springframework.beans.BeansException;  4 import org.springframework.beans.factory.NoSuchBeanDefinitionException;  5 import org.springframework.context.ApplicationContext;  6 import org.springframework.context.ApplicationContextAware;  7  8 /**  9  * spring上下文工具类 10  * 11  * @author Benny 12  * 13  */ 14 public class SpringContextUtil implements ApplicationContextAware {
15 16 private static ApplicationContext applicationContext; // Spring应用上下文环境 17 18 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
19 SpringContextUtil.applicationContext = applicationContext; 20 } 21 22 public static ApplicationContext getApplicationContext() {
23 return applicationContext; 24 } 25 26 public static Object getBean(String name) throws BeansException {
27 return applicationContext.getBean(name); 28 } 29 30 public static Object getBean(Class
requiredType) throws BeansException {
31 return applicationContext.getBean(requiredType); 32 } 33 34 public static Object getBean(String name, Class
requiredType) throws BeansException {
35 return applicationContext.getBean(name, requiredType); 36 } 37 38 public static boolean containsBean(String name) {
39 return applicationContext.containsBean(name); 40 } 41 42 public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
43 return applicationContext.isSingleton(name); 44 } 45 46 public static Class
getType(String name) throws NoSuchBeanDefinitionException {
47 return applicationContext.getType(name); 48 } 49 50 public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
51 return applicationContext.getAliases(name); 52 } 53 }

至此,springmvc+shiro安全管理+freemarker标签权限验证就完成了。

基于URL权限验证的方式也非常简单,只需要在自定义拦截器中对所有url进行权限校验即可,同样也是使用shiro权限校验机制,跟freemarker标签式的权限校验一致,看看拦截器源代码:

1 package com.itrip.rp.interceptor;  2  3 import javax.servlet.http.HttpServletRequest;  4 import javax.servlet.http.HttpServletResponse;  5  6 import org.apache.shiro.SecurityUtils;  7 import org.apache.shiro.subject.Subject;  8 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;  9 import org.springframework.web.util.UrlPathHelper; 10 11 /** 12  * URI拦截器 用户权限验证 13  * 14  * @author Benny 15  */ 16 public class AdminContextInterceptor extends HandlerInterceptorAdapter {17 18     @Override 19     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
20 // 获取请求链接 21 String uri = getURI(request); 22 // 排除例外URI,例如:登陆、退出 23 if (exclude(uri)) {
24 return true; 25 } 26 Subject subject = SecurityUtils.getSubject(); 27 boolean pass = subject.isPermitted(uri); 28 if (pass) {
29 return true; 30 } else {
31 // 跳转至异常处理 32 throw new Exception(); 33 } 34 } 35 36 /** 37 * 判断是否例外uri 38 * 39 * @param uri 40 * @return 41 */ 42 private boolean exclude(String uri) {
43 if (excludeUrls != null) {
44 for (String exc : excludeUrls) {
45 // 允许以excludeurl结尾的请求 46 if (uri.endsWith(exc)) {
47 return true; 48 } 49 } 50 } 51 return false; 52 } 53 54 /** 55 * 获取请求URL 56 * 57 * @param request 58 * @author Benny 59 * @return 60 */ 61 private static String getURI(HttpServletRequest request) {
62 UrlPathHelper helper = new UrlPathHelper(); 63 return helper.getOriginatingRequestUri(request); 64 } 65 66 private String[] excludeUrls; 67 68 public void setExcludeUrls(String[] excludeUrls) {
69 this.excludeUrls = excludeUrls; 70 } 71 }

以上实现了shiro安全管理+freemarker标签式的权限控制+系统全局url权限控制,基本满足大部分web项目的权限管理。

到此结束!

 使用的jar包以及版本在此说明一下:

shiro相关jar包:

1 
2
3
org.apache.shiro
4
shiro-web
5
1.2.2
6
7 8
9
org.apache.shiro
10
shiro-ehcache
11
1.2.2
12
13 14
15
org.apache.shiro
16
shiro-quartz
17
1.2.2
18
19
20
org.apache.shiro
21
shiro-spring
22
1.2.2
23
24

spring使用版本为3.0.5

标签: , , , ,
+加关注
0
0
上一篇:
下一篇:

转载地址:http://itoum.baihongyu.com/

你可能感兴趣的文章
sharepoint2010问卷调查(3)-实现问卷的开始和结束时间(采用自定义字段类型)...
查看>>
java final
查看>>
【吐槽】VS2012的安装项目只能用InstallShield Limited Edition
查看>>
win7重装系统时,使用PE工具箱进入系统看到的“C盘变成0.2G,D盘变成48G左右”这是什么回事?...
查看>>
JQuery URL的GET参数值获取方法
查看>>
关于Char* ,CString ,WCHAR*之间的转换问题
查看>>
第十二天--Property List和NSUserDefaults
查看>>
JS Bin Tips and Bits • About
查看>>
Sharepoint学习笔记—习题系列--70-576习题解析 -(Q40-Q44)
查看>>
nodejs发展
查看>>
Fragment过度动画分析一
查看>>
UBI文件系统简介
查看>>
《现代操作系统》精读与思考笔记 第一章 引论
查看>>
System.out.print实现原理猜解
查看>>
每日英语:The Invasion of the Online Tutors
查看>>
codepage IMLangCodePages
查看>>
Leetcode: Valid Parentheses
查看>>
JavaScript Structure
查看>>
java 流媒体服务器Red5 FQA
查看>>
mysql--SQL编程(关于mysql中的日期) 学习笔记2
查看>>