牛客网中级项目学习笔记(一)

牛客中级项目学习:
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表明选择的是特定用户的资讯,这个逻辑符合登陆用户,进入界面,显示的资讯是和自己相关的。
如果没有登陆,显示的就是首页。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.nowcoder.dao.NewsDAO">
    <sql id="table">news</sql>
    <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<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);
    }
}

下面是进入首页,要读取最新的十条资讯,展示在页面上。后台要做的是从数据库中查得十条资讯,返回的是十条news类,根据news类中的userId,去user表中查的发帖人的信息,图片链接等。每一条条news,user信息包在一个ViewObject类中,十条组成一个ViewObject类的数组。下面的代码就是具体实现

private List<ViewObject> getNews(int userId, int offset, int limit) {
    List<News> newsList = newsService.getLatestNews(userId, offset, limit);
    List<ViewObject> 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:”}
例子:牛客投递登录框,点赞登录框

  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 30
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 30
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值