牛客中级项目学习:
Controller 解析web请求
Service 业务层
DAO(data access object)数据处理层
database 底层数据库
重定向
代码如下:
@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和302的区别
301是终究迁移,如果是301,会把信息存入浏览器,下次浏览器访问网址,会直接定位到另一个地方。
301是临时迁移。
Spring IOC
IOC控制反转 spring 体现在代码中就是用依赖注入的方法定义一个service,
不用再Controller层中去创建一个service 能够把实现写在单独的地方,把用到的地方在注入进来,没有初始化顺序,类与类之间的依赖这些烦恼
通过配置互相引入,通过注解方式或者配置文件方式注入类。
两种方式:
一、注解
二、xml配置文件 实现类与类之间的关系
AOP
面向切面,适用于不是特定任务的,所有服务都会用到的任务。
日志业务部门,所有服务需要打log。
需求:所有机器在运行,肯定会调用很多controller的入口,统计每个入口情况,如每个页面打开次数,每次打开时间。现在有很多服务,用log将所有服务切下来。
Spring AOP底层Aspectj
代码如下:
@Aspect
@Component
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Before("execution(* com.nowcoder.controller.*Controller.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
StringBuilder sb = new StringBuilder();
for (Object arg : joinPoint.getArgs()) {
sb.append("arg:" + arg.toString() + "|");
}
logger.info("before method: " + sb.toString());
}
@After("execution(* com.nowcoder.controller.IndexController.*(..))")
public void afterMethod(JoinPoint joinPoint) {
logger.info("after method: ");
}
}
JoinPoint是个包装类,相当于面向切面的交汇点,通过getArgs方法可以获得所有的进入controller的输入参数。
可以在afterMethod和beforeMethod中记录时间,相减就得到了服务的运行时间。 可以做优化,日志统计,报警。
数据库基本操作
MyBatis集成
MyBatis 可以通过注解和xml的方式操作数据库,只关心读取写入,不用关心数据库如何连接的。
xml方式操作数据库比注解的好处是可以进行逻辑复杂的操作
MyBatis框架的好处,只关注sql语句获取数据,前面jdbc数据库的连接,释放都不用我们考虑了。
JDBC数据库操作
1. 加载驱动
2. 创建数据库链接
3. 创建Statement对象
4. 执行SQL获取数据(关注这里)
5. 数据转化
6. 资源释放
注解方式操作数据库代码
@Mapper
public interface UserDAO {
String TABLE_NAME = "user";
String INSET_FIELDS = " name, password, salt, head_url ";
String SELECT_FIELDS = " id, name, password, salt, head_url";
@Insert({"insert into ", TABLE_NAME, "(", INSET_FIELDS,
") values (#{name},#{password},#{salt},#{headUrl})"})
int addUser(User user);
@Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where id=#{id}"})
User selectById(int id);
@Update({"update ", TABLE_NAME, " set password=#{password} where id=#{id}"})
void updatePassword(User user);
@Delete({"delete from ", TABLE_NAME, " where id=#{id}"})
void deleteById(int id);
}
MyBatis读取xml配置文件操作数据库,xml文件如下
比注解的优势是可以做复杂的逻辑操作,如下面如果传入的userId!= null
表明选择的是特定用户的资讯,这个逻辑符合登陆用户,进入界面,显示的资讯是和自己相关的。
如果没有登陆,显示的就是首页。
<mapper namespace="com.nowcoder.dao.NewsDAO">
<sql id="table">newssql>
<sql id="selectFields">id,title, link, image, like_count, comment_count,created_date,user_id
sql>
<select id="selectByUserIdAndOffset" resultType="com.nowcoder.model.News">
SELECT
<include refid="selectFields"/>
FROM
<include refid="table"/>
<if test="userId != 0">
WHERE user_id = #{userId}
if>
ORDER BY id DESC
LIMIT #{offset},#{limit}
select>
mapper>
ViewObject和DataTool
自定义类ViewObject,是个map的包装类,方便把每一条资讯的信息(包括新闻信息,发帖的用户信息)放到一起。传到moder,方便前端拿到,做展示。
类定义如下
package com.nowcoder.model;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* Created by rainday on 16/6/30.
*/
public class ViewObject {
private Map objs = new HashMap();
public void set(String key, Object value) {
objs.put(key, value);
}
public Object get(String key) {
return objs.get(key);
}
}
下面是进入首页,要读取最新的十条资讯,展示在页面上。后台要做的是从数据库中查得十条资讯,返回的是十条news类,根据news类中的userId,去user表中查的发帖人的信息,图片链接等。每一条条news,user信息包在一个ViewObject类中,十条组成一个ViewObject类的数组。下面的代码就是具体实现
private List getNews(int userId, int offset, int limit) {
List newsList = newsService.getLatestNews(userId, offset, limit);
List vos = new ArrayList<>();
for (News news : newsList) {
ViewObject vo = new ViewObject();
vo.set("news", news);
vo.set("user", userService.getUser(news.getUserId()));
vos.add(vo);
}
return vos;
}
@RequestMapping(path = {"/", "/index"}, method = {RequestMethod.GET, RequestMethod.POST})
public String index(Model model) {
model.addAttribute("vos", getNews(0, 0, 10));
return "home1";
}
这段代码是进入网站主页的显示。读取数据库的前十条新闻
第四节课
注册
• 登陆
• 浏览
• Interceptor
• 未登录跳转
• 数据安全性
• AJAX
• DEV-TOOL
Json是种数据格式,类似的有xml。
填写注册用户名和密码的时候,不光前端要检测合法性,后端也要检测。很多攻击不是通过网页,像postman可以绕过前端,直接访问后台。
Spring Boot Dev Tools
动态加载更新的class
编译加载修改的静态文件
拦截器 链路回调思想
如下有个PassportInterceptor拦截器,它对于所有页面都进行处理
一、可以知道用户是这个用户。
注册成功后会进行自动登陆,对于登陆,在登陆操作中,在service层,进行逻辑判断,对上返回状态回到controller,对下dao去和数据库交互。在service登陆代码中,服务器会生成一个string类型的ticket,存入cookie中,key值是ticket,value值是ticket的值。通过response下发到浏览器。
下次在已经登陆的用户,进行其他点击后。在进入controller前,调用preHandle方法处理,它可以检查客户端提交的cookie中是否有服务器之前下发的ticket,如果有证明这个请求是已经登陆的用户了。把登陆的用户放到线程本地变量。在此时才进入controller,可以拿到具体的用户HostHolder类,这是线程本地变量,可以根据登陆的用户进行个性化渲染,比如关注用户的动态,个人收藏等。
二、可以进行权限管理
比如,在浏览某些页面时,要保证用户登陆,或者用户是某个等级的。可以现在preHandler中判断,这个可以在设置一个特定范围的拦截器,如下的拦截器LoginRequiredInterceptor,在访问setting页面时才会进入到该拦截器,如果验证没有HostHolder,说明用户没登陆,就跳转到主页或者给出登陆页面。拦截器也先后执行顺序。在配置类中ToutiaoWebConfiguration中先后决定。
三、可以自动登陆
我们平时在浏览网页的时候会碰到这样的情况,昨天登陆了某个网站,关机,第二天再登陆,自动跳转到我的用户了。这里是服务器会把浏览器的sessionId和服务器的数据库关联,在提交请求的时候服务器会设置拦截器去找sessionId,如果这个sessionId和我服务器上的ticket关联了起来,并且设置的过期时间没有过期,那在登陆主页前我就可以知道是某个用户,在controller层中可以进行渲染。
可以做到自动登陆的功能。
在controller结束的时候,通常会把结果返回给view视图,在拦截器中postHandle方法做。
全部完成后,进入拦截器的afterCompletion方法,进行扫尾工作,如把线程中的本地变量删除。
将拦截器配置到spring的类是ToutiaoWebConfiguration,继承WebMvcConfigurerAdapter,代码如下。
@Component
public class ToutiaoWebConfiguration extends WebMvcConfigurerAdapter{
@Autowired
PassportInterceptor passportInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(passportInterceptor); //全局的,所有页面需要处理,用于登陆验证
//访问setting的时候才访问这个拦截器;,用于权限验证
super.addInterceptors(registry);
}
}
继承的接口是HandlerInterceptor
@Component
public class PassportInterceptor implements HandlerInterceptor{
@Autowired
LoginTicketDAO loginTicketDAO;
@Autowired
UserDAO userDAO;
@Autowired
HostHolder hostHolder;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
String ticket = null;
if (httpServletRequest.getCookies() != null) {
for (Cookie cookie : httpServletRequest.getCookies()) {
if (cookie.getName().equals("ticket")) {
ticket = cookie.getValue();
break;
}
}
if (ticket != null) {
LoginTicket loginTicket = loginTicketDAO.selectByTicket(ticket);
if (loginTicket == null || loginTicket.getExpired().before(new Date()) || loginTicket.getStatus() != 0) {
return true;
}
User user = userDAO.selectById(loginTicket.getUserId());
hostHolder.setUsers(user);
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
if (modelAndView != null && hostHolder.getUsers() != null){
modelAndView.addObject("user", hostHolder.getUsers()); //前后端代码交互的地方
}
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
hostHolder.clear();
}
}
权限认证
新增一个拦截器LoginRequiredInterceptor
@Component
public class LoginRequiredInterceptor implements HandlerInterceptor {
@Autowired
private HostHolder hostHolder;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
if (hostHolder.getUser() == null) {
httpServletResponse.sendRedirect("/?pop=1"); //自己定义的,传到前端,假如pop=1,让登录框弹出
return false;
}
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 {
}
}
拦截位置放在登陆验证PassportInterceptor
的后面,通过登陆验证看HostHolder类中有没有线程本地变量user类,如果有,证明能确定是谁登陆的。如果没有,那没有权限访问/setting*
相关的页面。通过PassportInterceptor
的PreHandler()方法后,进入LoginRequiredInterceptor
的PreHandler方法,如果没有拿到user类,返回false。不能进入controller层,重定向到首页,并且约定pop=1
,弹出登陆框。
在preHandler方法中return false;
和return true;
的区别是允不允许进入controller层。
用户数据安全性:
https可以防止运行商加塞广告,
公钥加密私钥解密,
用户密码salt防止破解
token有效期
单一平台的单点登陆,登陆IP异常检验
用户状态的权限判断
添加验证码机制,防止爆破和批量注册。
手机验证码,一瞬间一万个请求,0~9999都发到服务器上,总有一个对的上的。如果一个验证码失效,重新下发token。
AJAX:异步数据交互
1. 页面不刷新
2. 体验更好
3. 传输数据更少
4. APP/网站通用
评论有很多页,选择一页的时候,页面不刷新,URL不变,只返回变化的数据信息。
扩展:
统一的数据格式:{code:0,msg:”,data:”}
例子:牛客投递登录框,点赞登录框