久违地来更新一下项目。这次在之前的博客项目上加上了防止重复登录和文章的点赞功能。
Redis相关的代码写在一个新的provider中,模拟一个独立的服务器。同样将提供的服务注册在zookeeper中。该provider结构如下:
JedisUtils是帮助获取redis连接的工具类。主要功能就是和redis建立连接。
package com.zhz.f.provider2.utils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.ResourceBundle;
public class JedisUtils {
public static JedisPool jp = null;
public static String host = null;
public static Integer port = null;
public static Integer MaxTotal = null;
public static Integer MaxIdle = null;
static {
//从properties中取出数据
ResourceBundle rb = ResourceBundle.getBundle("Jedis");
host = rb.getString("redis.host");
port = Integer.parseInt(rb.getString("redis.port"));
MaxTotal = Integer.parseInt(rb.getString("redis.MaxTotal"));
MaxIdle = Integer.parseInt(rb.getString("redis.MaxIdle"));
//新建配置类
JedisPoolConfig jpc = new JedisPoolConfig();
jpc.setMaxTotal(MaxTotal);//最大链接数
jpc.setMaxIdle(MaxIdle);//最大活动数
//新建Jedis池
jp = new JedisPool(jpc,host,port);
}
public static Jedis getJedis(){
return jp.getResource();
}
}
其中properties中存的是一些连接参数
redis.host=127.0.0.1
redis.port=6379
redis.MaxTotal=30
redis.MaxIdle=10
1.创建一个set类型的key“account”来保存登录过的账号。
2.当有新账号登录时,使用sadd添加,根据返回结果可以判定账号是否存在(0:账号已登录,添加失败;1:账号未登录,添加成功)
3.账号退出时,账号对应的将member删除
注意:接口是在共用的api包下的
package com.zhz.f.api.service;
public interface MyRedis {
boolean registerAccountInRedis(String account);
boolean logoutAccountInRedis(String account);
}
@DubboService
@Service
public class MyRedisService implements MyRedis {
private static final Jedis jedis = JedisUtils.getJedis();
@Override
public boolean registerAccountInRedis(String account) {
//如果账号存在则返回false,不存在则成功添加,返回true
Long ifAdded = jedis.sadd("account", account);
return ifAdded == 1L;
}
@Override
public boolean logoutAccountInRedis(String account) {
Long aLong = jedis.srem("account", account);
return aLong == 1L;
}
在client服务器的LoginController中添加判断代码,防止重复登录。
在确定输入账号密码正确后,加入判断,如果账号存在则返回错误信息,不存在则可以正常登录。(为了更好看见新代码,删掉了其它代码的注释)
package com.zhz.f.client.controller.account;
@Controller
public class LoginController {
@DubboReference
private MyData myData;
@DubboReference
private MyRedis myRedis;
@RequestMapping("/login")
public String loginServer(HttpServletRequest request, Model model, HttpSession session){
if(session.getAttribute("userAccount")!=null){
return "redirect:/zhz/toUserMain";
}
String userAccount = request.getParameter("userAccount");
String userPassword = request.getParameter("userPassword");
if (userAccount==null)return "/account/login";
if(myData.comparePasswordsByAccount(userAccount,userPassword)){
session.setAttribute("userAccount",userAccount);
//将账号注册到redis,成功返回true,失败返回false说明已经存在
if(!myRedis.registerAccountInRedis(userAccount)){
model.addAttribute("callback","该账号已经登录");
return "/account/login";
}
//进入主界面
return "redirect:/zhz/toUserMain";
}else {
model.addAttribute("callback","输入账号或密码错误");
return "/account/login";
}
}
}
在client服务器的LogoutController中添加判断代码,去除已退出的账号。
移除登录信息之前先删除Redis中保存的登录记录。
package com.zhz.f.client.controller.account;
import com.zhz.f.api.service.MyRedis;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
@Controller
public class LogoutController {
@DubboReference
private MyRedis myRedis;
@RequestMapping("/zhz/logout")
public String logout(HttpSession session){
//删掉redis中保存的登录账号
myRedis.logoutAccountInRedis((String) session.getAttribute("userAccount"));
//删掉login中放入session中的userAccount
session.removeAttribute("userAccount");
return "/account/login";
}
}
目前这个防止重复的功能其实是有点问题的,当用户在不退出账号而关闭页面时,虽然下次直接访问界面还能访问(因为我在session中存了账号信息,下次会直接登录,具体实现见前面的博客),但退出浏览器后就不能再次登录(redis没有将账号信息删掉)。这样再次登录就会提示已登录,该账号相当于永远都不能再登录了(除非我在redis里手动删掉它)。
这时可以添加一个Session的监听器,来监听Session的销毁(浏览器的关闭)
实现的话只需要新建一个类实现HttpSessionListener 接口,并重写sessionDestroyed方法完成所需要的功能即可。
(勘误:在浏览器退出后,服务器中的session不会立即销毁,而是一段时间后销毁)
public class AllSessionListener implements HttpSessionListener {
@DubboReference
private MyRedis myRedis;
@Override
//检测到浏览器退出,将session中登录的信息给删除
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
String userAccount = (String) session.getAttribute("userAccount");
//如果未登录或者已经退出,session中将没有userAccount信息,所以不用去Redis中删除(防止误删或出现错误)。
if(userAccount!=null&&(!"".equals(userAccount)))
myRedis.logoutAccountInRedis((String) session.getAttribute("userAccount"));
}
}
这里为了防止重复登录其实有很多其它的方法,这里我只是为了练手而使用了Redis,Spring security等也可以防止用户重复登录(但我还没学)。
(这边一些变量的名字取的有些随意,写的时候没有想清除就写了。。。)
1.创建hash类型的key:“good”,field:“文章名字:文章所有者:点赞者”,value:“点赞者”。(这个value暂时没有用上,其实也可以直接用string类型来存,但我觉得用hash的好处是所有的点赞信息都归纳在一个key中,会便于管理一点)
2.点赞的时候使用hsetnx方法,该用户已经给该文章点过赞,则删除该field,并将文章的点赞数-1;如果没有点赞则加上该field,并将文章的点赞数+1.
package com.zhz.f.api.service;
public interface MyRedis {
boolean goodGood(String articleOwner,String articleName,String user);
int countOfGood(String articleOwner,String articleName);
}
package com.zhz.f.provider2.service;
import com.zhz.f.api.service.MyRedis;
import com.zhz.f.provider2.utils.JedisUtils;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
@DubboService
@Service
public class MyRedisService implements MyRedis {
private static final Jedis jedis = JedisUtils.getJedis();
@Override
public boolean goodGood(String articleOwner, String articleName, String user) {
//尝试添加,如果没有赞过,添加赞
Long ifAdded = jedis.hsetnx("good", articleOwner + ":" + articleName + "goodBy" + user, user);
//添加成功后,为赞的次数+1
if (ifAdded == 1L) {
//如果是第一次被赞,计数为1
Long ifFirst = jedis.hsetnx("good", articleOwner + ":" + articleName + "goodCount", "1");
//如果不是第一次,添加失败,ifFirst返回0,计数+1
if (ifFirst == 0L) {
jedis.hincrBy("good", articleOwner + ":" + articleName + "goodCount", 1L);
}
return true;
}
//添加失败,说明已经赞过了,将赞取消,将对应的count-1
jedis.hdel("good", articleOwner + ":" + articleName + "goodBy" + user);
jedis.hincrBy("good", articleOwner + ":" + articleName + "goodCount", -1L);
return false;
}
@Override
public int countOfGood(String articleOwner, String articleName) {
String hget = jedis.hget("good", articleOwner + ":" + articleName + "goodCount");
//当从来没有没有点过赞的时候,该field不存在,会返回null。
if (hget==null){
return 0;
}
return Integer.parseInt(hget);
}
}
在ToArticleController中获取被点赞数,传给前端:
//获取被赞的数量
int countOfLikes = myRedis.countOfGood(articleOwnerAccount, articleTitle);
model.addAttribute("countOfLikes", countOfLikes);
为了展示新添加的点赞功能,将内容显示和评论功能略去了,感兴趣的可以看看我之前的文章。
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
。。。
head>
<body>
<h2>
<div th:text="${articleTitle}">div>
h2>
。。。
<h3>如果你喜欢请给该作品点赞h3>
<form action="/zhz/likeArticle">
<input type="image" src="../static/images/good.jpg" th:src="@{/images/good.jpg}" height="50px" width="50px"
name=good title="点赞或取消" onclick="this.form.submit()">
<input name="articleOwnerAccount" th:value="${articleOwnerAccount}" hidden>
<input name="articleTitle" th:value="${articleTitle}" hidden>
form>
<h3>已点赞数:<strong th:text="${countOfLikes}">strong>h3>
。。。
form>
body>
html>
@Controller
public class LikeArticlesController {
@DubboReference
private MyRedis myRedis;
@RequestMapping("/zhz/likeArticle")
public String likeArticle(HttpServletRequest request, HttpSession session, Model model){
String articleOwner = request.getParameter("articleOwnerAccount");
String articleTitle = request.getParameter("articleTitle");
String userAccount = (String) session.getAttribute("userAccount");
myRedis.goodGood(articleOwner, articleTitle, userAccount);
return "redirect:/zhz/toUserMain";
}
}
这样点赞功能就实现了。