import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
@Bean
public JedisPool JedisPoolFactory() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(redisConfig.getPoolMaxIdle());
poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal());
poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait() * 1000);
JedisPool jp = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(),
redisConfig.getTimeout()*1000, redisConfig.getPassword(), 0);
return jp;
}
maxTotal 资源池中最大连接数 默认值8 建议值
maxIdle 资源池允许最大空闲的连接数 默认值8 建议值
minIdle 资源池确保最少空闲的连接数 默认值0 建议值
private void returnToPool(Jedis jedis) {
if(jedis != null) {
jedis.close();
}
}
/**
* 获取当个对象
* */
public <T> T get(KeyPrefix prefix, String key, Class<T> clazz) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
String str = jedis.get(realKey);
T t = stringToBean(str, clazz);
return t;
}finally {
returnToPool(jedis);
}
}
/**
* 设置对象
* */
public <T> boolean set(KeyPrefix prefix, String key, T value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String str = beanToString(value);
if(str == null || str.length() <= 0) {
return false;
}
//生成真正的key
String realKey = prefix.getPrefix() + key;
int seconds = prefix.expireSeconds();
if(seconds <= 0) {
jedis.set(realKey, str);
}else {
jedis.setex(realKey, seconds, str);
}
return true;
}finally {
returnToPool(jedis);
}
}
jedis = jedisPool.getResource();
jedis.exists(realKey);
long ret = jedis.del(realKey);
/**
* 增加值
* */
jedis.incr(realKey);
/**
* 减少值
* */
jedis.decr(realKey);
public List<String> scanKeys(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
List<String> keys = new ArrayList<String>();
String cursor = "0";
ScanParams sp = new ScanParams();
sp.match("*"+key+"*");
sp.count(100);
do{
ScanResult<String> ret = jedis.scan(cursor, sp);
List<String> result = ret.getResult();
if(result!=null && result.size() > 0){
keys.addAll(result);
}
//再处理cursor
cursor = ret.getStringCursor();
}while(!cursor.equals("0"));
return keys;
} finally {
if (jedis != null) {
jedis.close();
}
}
}
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;
在 Redis 里,所谓 SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果
可用版本: >= 2.0.0 时间复杂度: O(1)
将键 key
的值设置为 value
, 并将键 key
的生存时间设置为 seconds
秒钟。
如果键 key
已经存在, 那么 SETEX
命令将覆盖已有的值。
SETEX
命令的效果和以下两个命令的效果类似:
SET key value
EXPIRE key seconds # 设置生存时间
SETEX
和这两个命令的不同之处在于 SETEX
是一个原子(atomic)操作, 它可以在同一时间内完成设置值和设置过期时间这两个操作, 因此 SETEX
命令在储存缓存的时候非常实用。
可用版本: >= 1.0.0 时间复杂度: O(1)
为键 key
储存的数字值减去一。
如果键 key
不存在, 那么键 key
的值会先被初始化为 0
, 然后再执行 DECR
操作。
如果键 key
储存的值不能被解释为数字, 那么 DECR
命令将返回一个错误。
本操作的值限制在 64 位(bit)有符号数字表示之内。
关于递增(increment) / 递减(decrement)操作的更多信息, 请参见 INCR
命令的文档。
DECR
命令会返回键 key
在执行减一操作之后的值。
是原子操作
import org.apache.commons.codec.digest.DigestUtils;
public class MD5Util {
public static String md5(String src){
return DigestUtils.md5Hex(src);
}
private static String salt = "1a2b3c4d";
public static String inputPassToFormPass(String inputPass) {
String str = ""+salt.charAt(0)+salt.charAt(2) + inputPass +salt.charAt(5) + salt.charAt(4);
System.out.println(str);
return md5(str);
}
public static String formPassToDBPass(String formPass, String salt) {
String str = ""+salt.charAt(0)+salt.charAt(2) + formPass +salt.charAt(5) + salt.charAt(4);
return md5(str);
}
public static String inputPassToDbPass(String inputPass, String saltDB) {
String formPass = inputPassToFormPass(inputPass);
String dbPass = formPassToDBPass(formPass, saltDB);
return dbPass;
}
因为明文传输,所以两次的MD5
需要Mapper
@Mapper
@Component
public interface GoodsDao {
@Select("select g.*,mg.stock_count, mg.start_date, mg.end_date,mg.miaosha_price from miaosha_goods mg left join goods g on mg.goods_id = g.id")
public List<GoodsVo> listGoodsVo();
@Select("select g.*,mg.stock_count, mg.start_date, mg.end_date,mg.miaosha_price from miaosha_goods mg left join goods g on mg.goods_id = g.id where g.id = #{goodsId}")
public GoodsVo getGoodsVoByGoodsId(@Param("goodsId")long goodsId);
@Update("update miaosha_goods set stock_count = stock_count - 1 where goods_id = #{goodsId} and stock_count > 0")
public int reduceStock(MiaoshaGoods g);
@Update("update miaosha_goods set stock_count = #{stockCount} where goods_id = #{goodsId}")
public int resetStock(MiaoshaGoods g);
}
public CodeMsg login(HttpServletResponse response, LoginVo loginVo){
if (loginVo==null){
throw new GlobalException(CodeMsg.SERVER_ERROR);
}
String mobile = loginVo.getMobile();
String formPass = loginVo.getPassword();
//验证手机号是否存在
MiaoshaUser user = getById(Long.parseLong(mobile));
if (user == null){
throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
}
String dbPass = user.getPassword();
String saltDB = user.getSalt();
String calcPass = MD5Util.formPassToDBPass(formPass,saltDB);
// System.out.println("frompass"+formPass);
// System.out.println("calcPass"+calcPass);
// System.out.println("dbPass"+dbPass);
if (!calcPass.equals(dbPass)){
throw new GlobalException(CodeMsg.PASSWORD_ERROR);
}
//生成Cookie
// String token = UUIDUtil.uuid();
// redisService.set(MiaoshaUserKey.token,token,user);
//
// Cookie cookie = new Cookie(COOKI_NAME_TOKEN,token);
// cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
// cookie.setPath("/");
// response.addCookie(cookie);
String token = UUIDUtil.uuid();
addCookie(response,token,user);
return CodeMsg.SUCCESS;
}
@Valid 参数校验 省去了一大堆的参数校验
LoginVo loginVo 这个是从页面上获取到的数据
@RequestMapping("/do_login")
@ResponseBody
public Result<Boolean> doLogin(HttpServletResponse response, @Valid LoginVo loginVo) {
log.info(loginVo.toString());
//登录
// String passInput = loginVo.getPassword();
// String mobile = loginVo.getMobile();
// if (StringUtils.isEmpty(passInput)){
// return Result.error(CodeMsg.PASSWORD_EMPTY);
// }
// if (StringUtils.isEmpty(mobile)){
// return Result.error(CodeMsg.MOBILE_EMPTY);
// }
// if (!ValidatorUtil.isMobile(mobile)){
// return Result.error(CodeMsg.MOBILE_ERROR);
// }
CodeMsg cm = userService.login(response,loginVo);
if (cm.getCode()==0){
return Result.success(true);
}else {
return Result.error(cm);
}
}
Session:在计算机中,尤其是在网络应用中,称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。
当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。Session 对象最常见的一个用法就是存储用户的首选项。
UUID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。
在网站中,http请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。cookie的出现就是为了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的cookie数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是哪个了。cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用cookie只能存储一些小量的数据。
session和cookie的作用有点类似,都是为了存储用户相关的信息。不同的是,cookie是存储在本地浏览器,而session存储在服务器。存储在服务器的数据会更加的安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器已经发展至今,一些session信息还是绰绰有余的。
web开发发展至今,cookie和session的使用已经出现了一些非常成熟的方案。在如今的市场或者企业里,一般有两种存储方式:
1、存储在服务端:通过cookie存储一个session_id,然后具体的数据则是保存在session中。如果用户已经登录,则服务器会在cookie中保存一个session_id,下次再次请求的时候,会把该session_id携带上来,服务器根据session_id在session库中获取用户的session数据。就能知道该用户到底是谁,以及之前保存的一些状态信息。这种专业术语叫做server side session。
2、将session数据加密,然后存储在cookie中。这种专业术语叫做client side session。flask采用的就是这种方式,但是也可以替换成其他形式。
Token代表用户 session存放在redis里面
{
//生成cookie
String token = UUIDUtil.uuid();
addCookie(response, token, user);
return true;
}
private void addCookie(HttpServletResponse response, String token, MiaoshaUser user) {
redisService.set(MiaoshaUserKey.token, token, user);
Cookie cookie = new Cookie(COOKI_NAME_TOKEN, token);
cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
cookie.setPath("/");
response.addCookie(cookie);
}
1.UUID 简介 UUID 含义是通用唯一识别码 (Universally Unique Identifier),这是一个软件建构的标准。 也是被开源软件基金会 (Open Software Foundation, OSF) 的组织应用在分布式计算环境 (Distributed Computing Environment, DCE) 领域的一部分。 UUID 的目的,是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。 如此一来,每个人都可以建立不与其它人冲突的 UUID。在这样的情况下,就不需考虑数据库建立时的名称重复问题。
2.UUID 组成 UUID保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。 按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。UUID由以下几部分的组合:(1)当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。(2)时钟序列。(3)全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。 UUID的唯一缺陷在于生成的结果串会比较长。关于UUID这个标准使用最普遍的是微软的GUID(Globals Unique Identifiers)。 标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)。
根据服务端把一个token写到了cookie之中 客户端在随后的访问之中 携带这个cookie 那么服务端根据这个cookie里面的token就能找到用户
public String list(HttpServletRequest request, HttpServletResponse response, Model model, MiaoshaUser user) {
model.addAttribute("user",user);
//取缓存
String html = redisService.get(GoodsKey.getGoodsList,"",String.class);
if (!StringUtils.isEmpty(html)){
return html;
}
//查询商品列表
List<GoodsVo> goodsList = goodsService.listGoodsVo();
model.addAttribute("goodsList",goodsList);
// return "goods_list";
SpringWebContext ctx = new SpringWebContext(request,response,request.getServletContext(),request.getLocale(), model.asMap() ,applicationContext);
//手动渲染
html = thymeleafViewResolver.getTemplateEngine().process("goods_list",ctx);
if (!StringUtils.isEmpty(html)){
redisService.set(GoodsKey.getGoodsList,"",html);
}
return html;
}
对象级别的缓存
public boolean updatePassword(String token,long id,String formPassword){
//取user
MiaoshaUser user = getById(id);
if (user==null){
throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
}
//更新数据库
MiaoshaUser toBeUpdate = new MiaoshaUser();
toBeUpdate.setId(id);
toBeUpdate.setPassword(MD5Util.formPassToDBPass(formPassword,user.getSalt()));
miaoshaUserDao.update(toBeUpdate);
//detail the cache
redisService.delete(MiaoshaUserKey.getById,""+id);
user.setPassword(toBeUpdate.getPassword());
redisService.set(MiaoshaUserKey.token,token,user);
return true;
}
数据一但有更新 ,那么缓存里面的东西也需要更新
前:
加入缓存之后:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。