Featured image of post 旧式Servlet编程札记 低质量

旧式Servlet编程札记 低质量

# 旧Servlet + JSP用户管理项目

沐颜科技

在线用户剔除操作

WEB应用是一个多用户的使用环境,每一个用户都通过自己的session进行个人数据的记录,用户也可以手工进行注销操作,以实现session资源的释放,但是在一些系统中为了便于用户的管理,往往需要进行用户手工强制注销操作

# 管理程序清单

No. 程序文件名称 类型 描述
1 /login.jsp JSP 提供登录表单,以及错误显示
2 /pages/admin/online_user_list.jsp JSP 管理员查看在线用户列表信息
3 /pages/front/welcome.jsp JSP 用户登录成功后的欢迎页面
4 com.yootk.servlet.LoginServlet Servlet 用户登录处理程序,密码为yootk表示登录成功
5 com.yootk.servlet.KickoutServlet Servlet 在线用户剔除处理Servlet
6 com.yootk.listener.OnlineListener Listener 监听器,在用户登录成功或注销后更新用户列表
7 com.yootk.filter.InvalidateFilter Filter 登录失效检查,如果发现登录失效则跳转到登录页
8 com.yootk.filter.EncodingFilter Filter 编码过滤器

# 登录

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String id = req.getParameter("userid"); // 接收请求参数
        String password = req.getParameter("password"); // 接收请求参数
        if (!"yootk".equals(password)) { // 密码输入错误
            req.setAttribute("error", "错误的用户名及密码!");
            req.getRequestDispatcher("/errors.jsp").forward(req, resp);
        }
        // WEB组件中提供有监听操作,而通过监听操作可以实现HttpSession属性的处理
        req.getSession().setAttribute("userid", id); // 设置Session属性内容
        resp.sendRedirect("/pages/front/welcome.jsp"); // 客户端跳转
    }
}

需要注意的是,此时并不是要完成一个简单的用户登录,而是需要对用户登录的状态进行
监听,因为最终要将登录的结果保
存在session属性之中,而session的内容又需要保存在application属性之中. .

# 保存登录信息

@WebListener
public class OnlineListener implements ServletContextListener, HttpSessionListener, HttpSessionAttributeListener {
    private ServletContext servletContext; // 获取application实例
    @Override
    public void contextInitialized(ServletContextEvent sce) { // 进行初始化集合存储
        this.servletContext = sce.getServletContext(); // 存在有公共的application属性
        // Map集合之中的key表示用户名,而Value是保存当前的用户状态
        this.servletContext.setAttribute("online", new HashMap<String, Boolean>());
    }

    @Override
    public void attributeAdded(HttpSessionBindingEvent se) {
        // 用户登录成功会设置session的属性内容,这样就会触发本方法的执行
        if ("userid".equals(se.getName())) {    // 判断是否为指定的属性操作
            Map<String, Boolean> map = (Map<String, Boolean>) this.servletContext.getAttribute("online");
            map.put((String)se.getValue(), false); // 保存用户信息
            this.servletContext.setAttribute("online", map);
        }
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) { // 用户离开了
        Map<String, Boolean> map = (Map<String, Boolean>) this.servletContext.getAttribute("online");
        map.remove(se.getSession().getAttribute("userid")); // 删除数据
        this.servletContext.setAttribute("online", map);
    }
}

# 在线用户列表

%@ page pageEncoding="UTF-8" import="java.util.*" %>
<%  // 通过request获取相关资源信息,拼凑成完整的访问路径
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" +
            request.getServerPort() + request.getContextPath() + "/" ;
%>
<html>
<head>
    <title>沐言科技www.yootk.com</title>
    <base href="<%=basePath%>">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
    <link rel="stylesheet" type="text/css" href="bootstrap/css/bootstrap.min.css" />
</head>
<%!
    public static final String KICKOUT_URL = "/pages/admin/KickoutServlet";
%>
<body class="container">
<div class="row">
    <div class="panel panel-success">
        <div class="panel-heading">
            <strong><i class="fa fa-archive"></i>&nbsp;在线用户列表</strong>
        </div>
        <div class="panel-body">
            <%  // 获取当前全部的用户信息
                Map<String, Boolean> onlineMap = (Map<String, Boolean>) application.getAttribute("online"); // 获取Map集合
            %>
            <table class="table table-hover">
                <tr>
                    <th width="40%" class="text-center">用户名</th>
                    <th width="20%" class="text-center">状态</th>
                    <th width="10%" class="text-center">操作</th>
                </tr>
                <%
                    for (Map.Entry<String, Boolean> entry : onlineMap.entrySet()) {
                %>
                <tr>
                    <td class="text-center"><%=entry.getKey()%></td>
                    <td class="text-center">
                    <%
                        if (entry.getValue()) {
                    %>
                        <span class="label label-default">剔除</span>
                    <%
                        } else {
                    %>
                        <span class="label label-success">在线</span>
                    <%
                        }
                    %>
                    </td>
                    <td class="text-center">
                        <a class="btn btn-xs btn-danger" href="<%=KICKOUT_URL%>?userid=<%=entry.getKey()%>">
                            <span class="glyphicon glyphicon-remove"></span>&nbsp;强制下线</a></td>
                </tr>
                <%
                    }
                %>
            </table>
        </div>
        <div class="panel-footer">
            <div style="text-align:right;">
                <img src="images/logo.png" style="height: 30px;">
                <strong>沐言科技(www.yootk.com) —— 新时代软件教育领导品牌</strong>
            </div>
        </div>
    </div>
</div>
</body>
</html>

# 注销

@WebServlet("/pages/admin/KickoutServlet")
public class KickoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String userid = req.getParameter("userid");
        Map<String, Boolean> onlineMap = (Map<String, Boolean>) req.getServletContext().getAttribute("online"); // Map集合
        if (onlineMap.containsKey(userid)) {    // 用户还没走呢
            onlineMap.put(userid, true); // 设置为True就表示要剔除了
        }
        req.getServletContext().setAttribute("online", onlineMap);
        resp.sendRedirect("/pages/admin/online_user_list.jsp");
    }
}

能够让用户注销的操作只能够是用户本身完成的事情,所以可以考虑做一个过滤器,过滤
器在用户每次访问的时候都判断一
下用户的状态,如果发现用户已经被删除了,则强制性的注销.

@WebFilter("/pages/front/*")
public class InvalidateFilter extends HttpFilter {
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String userid = (String) request.getSession().getAttribute("userid");
        if (userid != null) {   // 用户登录过
            Map<String, Boolean> onlineMap = (Map<String, Boolean>) request.getServletContext().getAttribute("online"); // Map集合
            if (onlineMap.containsKey(userid)) {    // 用户还存在
                if (onlineMap.get(userid) == true) {    // 本身保存的就是布尔类型
                    request.getSession().invalidate(); // 强制注销
                    request.setAttribute("error", "您的账户已被系统强制下线,为了您的安全,请重新登录!");
                    request.getRequestDispatcher("/login.jsp").forward(request, response);
                } else {    // 正常的状态
                    chain.doFilter(request, response);
                }
            }
        } else {
            request.setAttribute("error", "您还未登录,请先登录!");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }
    }
}

# 旧Servlet SSM项目

# 用户登录和注销接口开发


//controller层代码
public void login(HttpServletRequest request,HttpServletResponse response){

        String phone = request.getParameter("phone");
        String pwd = request.getParameter("pwd");

        User user = userService.login(phone,pwd);

        if(user != null){

            request.getSession().setAttribute("loginUser",user);
            //跳转页面

        }else {

            request.setAttribute("msg","用户名或者密码不正确");

        }

}

//service层代码
@Override
public User login(String phone, String pwd) {

        String md5pwd = CommonUtil.MD5(pwd);

        User user = userDao.findByPhoneAndPwd(phone,md5pwd);

        return user;
}
  
  
//dao层代码
public User findByPhoneAndPwd(String phone, String md5pwd) {

        String sql = "select * from user where phone=? and pwd=?";

        User user = null;

        try{

            user = queryRunner.query(sql,new BeanHandler<>(User.class,processor),phone,md5pwd);

        }catch (Exception e){
            e.printStackTrace();
        }

        return user;
    }

# 发布主题接口

   //controller层代码
    /**
     * http://localhost:8080/topic?method=addTopic
     * 发布主题
     * @param request
     * @param response
     */
    public void addTopic(HttpServletRequest request,HttpServletResponse response){

        User loginUser = (User)request.getSession().getAttribute("loginUser");
        if(loginUser == null){
            request.setAttribute("msg","请登录");
            return;
            //页面跳转 TODO
        }

        String title = request.getParameter("title");
        String content = request.getParameter("content");
        int cId = Integer.parseInt(request.getParameter("c_id"));

        int rows =  topicService.addTopic(loginUser,title,content,cId);

        if(rows ==1){

            //发布主题成功

        }else {

            //发布主题失败

        }

}



//service层代码
@Override
public int addTopic(User loginUser, String title, String content, int cId) {

        Category category = categoryDao.findById(cId);
        if(category == null){ return 0;}

        Topic topic = new Topic();
        topic.setTitle(title);
        topic.setContent(content);
        topic.setCreateTime(new Date());
        topic.setUpdateTime(new Date());
        topic.setPv(1);
        topic.setDelete(0);
        topic.setUserId(loginUser.getId());
        topic.setUsername(loginUser.getUsername());
        topic.setUserImg(loginUser.getImg());
        topic.setcId(cId);
        topic.setHot(0);

        int rows = 0;
        try {
            rows = topicDao.save(topic);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return rows;
}

//dao层代码
public int save(Topic topic) throws Exception {

String sql = "insert into topic(c_id,title,content,pv,user_id,username,user_img,create_time,update_time,hot,`delete`) " +"values(?,?,?,?,?,?,?,?,?,?,?)";

        Object [] params = {
                topic.getcId(),
                topic.getTitle(),
                topic.getContent(),
                topic.getPv(),
                topic.getUserId(),
                topic.getUsername(),
                topic.getUserImg(),
                topic.getCreateTime(),
                topic.getUpdateTime(),
                topic.getHot(),
                topic.getDelete()
        };
        int i =0;
        try{
            i= queryRunner.update(sql,params);

        }catch (Exception e){
            e.printStackTrace();
            throw new Exception();
        }
        return i;
    }

# 回复盖楼功能开发

  • Servlet-Service-Dao层开发
    //controller层代码
    /**
     * http://localhost:8080/topic?method=replyByTopicId&topic_id=9
     * 盖楼回复
     * @param request
     * @param response
     */
    public void replyByTopicId(HttpServletRequest request,HttpServletResponse response){

        User loginUser = (User)request.getSession().getAttribute("loginUser");
        if(loginUser == null){
            request.setAttribute("msg","请登录");
            return;
            //页面跳转 TODO
        }

        int topicId = Integer.parseInt(request.getParameter("topic_id"));

        String content = request.getParameter("content");


        int rows = topicService.replyByTopicId(loginUser,topicId,content);

        if(rows ==1){

            //回复成功

        }else {

            //回复失败

        }

    }

    //service层代码
    @Override
    public int replyByTopicId(User loginUser, int topicId, String content) {

        int floor = topicDao.findLatestFloorByTopicId(topicId);

        Reply reply = new Reply();
        reply.setContent(content);
        reply.setCreateTime(new Date());
        reply.setUpdateTime(new Date());
        reply.setFloor(floor+1);
        reply.setTopicId(topicId);
        reply.setUserId(loginUser.getId());
        reply.setUsername(loginUser.getUsername());
        reply.setUserImg(loginUser.getImg());
        reply.setDelete(0);

        int rows = replyDao.save(reply);

        return rows;
    }

//dao层代码
public int save(Reply reply) {

String sql = "insert into reply (topic_id,floor,content,user_id,username,user_img,create_time,update_time,`delete`)" +
"values (?,?,?,?,?,?,?,?,?)";

        Object [] params = {
                reply.getTopicId(),
                reply.getFloor(),
                reply.getContent(),
                reply.getUserId(),
                reply.getUsername(),
                reply.getUserImg(),
                reply.getCreateTime(),
                reply.getUpdateTime(),
                reply.getDelete()
        };

        int rows = 0;
        try{
            rows = queryRunner.update(sql,params);
        }catch (Exception e){
            e.printStackTrace();
        }

        return rows;
    }

# topic阅读量递增

  • 通过session和topic进行绑定,一个session访问同个topic只算一次阅读
//controller层代码
//处理浏览量,如果同个session内只算一次
String sessionReadKey = "is_read_"+topicId;
Boolean isRead = (Boolean) request.getSession().getAttribute(sessionReadKey);

if(isRead == null){
      request.getSession().setAttribute(sessionReadKey,true);
      //新增一个pv
      topicService.addOnePV(topicId);

}

//service层代码
@Override
public void addOnePV(int topicId) {

        Topic topic = topicDao.findById(topicId);

        int newPV = topic.getPv()+1;

        topicDao.updatePV(topicId,newPV,topic.getPv());

}

//dao层代码
/**
* 更新浏览量
*/
public int updatePV(int topicId, int newPV, int oldPV) {
        String sql = "update topic set pv=? where pv=? and id=?";
        int rows = 0;
        try {
            rows =  queryRunner.update(sql,newPV,oldPV,topicId);
        }catch (Exception e){
            e.printStackTrace();
        }
        return rows;
}

# 页自动跳转

简介:小滴课堂首页自动跳转配置

  • home.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>小滴课堂开发者论坛</title>
</head>
<body>


<jsp:forward page="/topic?method=list&c_id=1"></jsp:forward>

</body>
</html>

  • web.xml
<welcome-file-list>
    <welcome-file>home.jsp</welcome-file>
  </welcome-file-list>

# 用户登录校验拦截器开发

开发对应的登录拦截器

开发loginInterceptor

  • 登录校验成功放行

      /**
         * 进入到controller之前的方法
         * @param request
         * @param response
         * @param handler
         * @return
         * @throws Exception
         */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            try {
    
                String accesToken = request.getHeader("token");
                if (accesToken == null) {
                    accesToken = request.getParameter("token");
                }
    
                if (StringUtils.isNotBlank(accesToken)) {
                    Claims claims = JWTUtils.checkJWT(accesToken);
                    if (claims == null) {
                        //告诉登录过期,重新登录
                        sendJsonMessage(response, JsonData.buildError("登录过期,重新登录"));
                        return false;
                    }
    
                    Integer id = (Integer) claims.get("id");
                    String name = (String) claims.get("name");
    
                    request.setAttribute("user_id", id);
                    request.setAttribute("name", name);
    
                    return true;
    
                }
    
            }catch (Exception e){}
    
            sendJsonMessage(response, JsonData.buildError("登录过期,重新登录"));
    
            return false;
        }
    

  • 登录不成功返回json数据

     /**
         * 响应json数据给前端
         * @param response
         * @param obj
         */
    public static void sendJsonMessage(HttpServletResponse response, Object obj){
    
            try{
                ObjectMapper objectMapper = new ObjectMapper();
                response.setContentType("application/json; charset=utf-8");
                PrintWriter writer = response.getWriter();
                writer.print(objectMapper.writeValueAsString(obj));
                writer.close();
                response.flushBuffer();
            }catch (Exception e){
                e.printStackTrace();
            }
    
    
        }
    

# loginInterceptor注册和放行路径

loginInterceptor 拦截器注册和路径校验配置

  • 继承 WebMvcConfigurer
  • 配置拦截路径和放行路径
/**
 * 拦截器配置
 *
 * 不用权限可以访问url    /api/v1/pub/
 * 要登录可以访问url    /api/v1/pri/
 */

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {


    @Bean
    LoginInterceptor loginInterceptor(){
        return new LoginInterceptor();
    }


    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        //拦截全部
        registry.addInterceptor(loginInterceptor()).addPathPatterns("/api/v1/pri/*/*/**")
                //不拦截哪些路径   斜杠一定要加
                .excludePathPatterns("/api/v1/pri/user/login","/api/v1/pri/user/register");

        WebMvcConfigurer.super.addInterceptors(registry);

    }
}

# Guava Cache缓存

# 谷歌开源缓存框架Guava Cache讲解和封装缓存组件

  • 添加依赖

        <!--guava依赖包-->
        <dependency>
          <groupId>com.google.guava</groupId>
          <artifactId>guava</artifactId>
          <version>19.0</version>
        </dependency>
    
  • 封装api

     private Cache<String,Object> tenMinuteCache = CacheBuilder.newBuilder()
    
                //设置缓存初始大小,应该合理设置,后续会扩容
                .initialCapacity(10)
                //最大值
                .maximumSize(100)
                //并发数设置
                .concurrencyLevel(5)
    
                //缓存过期时间,写入后10分钟过期
                .expireAfterWrite(600,TimeUnit.SECONDS)
    
                //统计缓存命中率
                .recordStats()
    
                .build();
    
    
        public Cache<String, Object> getTenMinuteCache() {
            return tenMinuteCache;
        }
    
        public void setTenMinuteCache(Cache<String, Object> tenMinuteCache) {
            this.tenMinuteCache = tenMinuteCache;
        }
    

# 轮播图接口引入本地缓存

  • 轮播图接口加入缓存
         try{

            Object cacheObj =  baseCache.getTenMinuteCache().get(CacheKeyManager.INDEX_BANNER_KEY, ()->{

                List<VideoBanner> bannerList =  videoMapper.listVideoBanner();

                System.out.println("从数据库里面找轮播图列表");

                return bannerList;

            });

            if(cacheObj instanceof List){
                List<VideoBanner> bannerList = (List<VideoBanner>)cacheObj;
                return bannerList;
            }

        }catch (Exception e){
            e.printStackTrace();
        }
        return null;

# 视频列表引入本地缓存

播放列表加入本地缓存

@Override
    public List<Video> listVideo() {


        try{

          Object cacheObj =  baseCache.getTenMinuteCache().get(CacheKeyManager.INDEX_VIDEL_LIST,()->{

                List<Video> videoList = videoMapper.listVideo();

                return videoList;

            });

          if(cacheObj instanceof List){
              List<Video> videoList = (List<Video>)cacheObj;
              return videoList;
          }

        }catch (Exception e){
            e.printStackTrace();
        }

        //可以返回兜底数据,业务系统降级-》SpringCloud专题课程
        return null;
    }

# 视频详情引入本地缓存

视频详情加入本地缓存

    @Override
    public Video findDetailById(int videoId) {

        String videoCacheKey = String.format(CacheKeyManager.VIDEO_DETAIL,videoId);

        try{

         Object cacheObject = baseCache.getOneHourCache().get( videoCacheKey, ()->{

                // 需要使用mybaits关联复杂查询
                Video video = videoMapper.findDetailById(videoId);

                return video;

            });

         if(cacheObject instanceof Video){

             Video video = (Video)cacheObject;
             return video;
         }

        }catch (Exception e){
            e.printStackTrace();
        }

        return null;
    }

# 实战接口压力测试, 明白优化前后的QPS并发差距和跨域配置

# 开启Guava缓存压测热点数据接口

简介: 启用缓存 压测热点数据接接口

  • 视频轮播图接口 Throughput: 14000

  • 注意:接口的性能影响因素很多:机器的配置如CPU、内存、当前负载情况等,还有网络带宽因素影响,只能尽量减少影响因素

单线程访问结果

{"code":0,"data":null,"msg":null}

# 取消Guava缓存压测热点数据接口和前后对比

简介: 不启用缓存 压测热点数据接口

  • 视频轮播图接口 Throughput : 2700

Licensed under CC BY-NC-SA 4.0
本博客已稳定运行
发表了14篇文章 · 总计180.21k字
Powered by Blood, Sweat, and Tears
使用 Hugo 构建 主题 StackJimmy 设计