当前位置: 首页 > news >正文

红月私服网站怎么做购买网站域名

红月私服网站怎么做,购买网站域名,科技股有哪些股票龙头2021,可以直接用php做网站吗Redis 学习笔记 3:黑马点评 准备工作 需要先导入项目相关资源: 数据库文件 hmdp.sql后端代码 hm-dianping.zip包括前端代码的 Nginx 启动后端代码和 Nginx。 短信登录 发送验证码 PostMapping("code") public Result sendCode(RequestP…

Redis 学习笔记 3:黑马点评

准备工作

需要先导入项目相关资源:

  • 数据库文件 hmdp.sql
  • 后端代码 hm-dianping.zip
  • 包括前端代码的 Nginx

启动后端代码和 Nginx。

短信登录

发送验证码

@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {// 发送短信验证码并保存验证码return userService.sendCode(phone, session);
}
@Log4j2
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Overridepublic Result sendCode(String phone, HttpSession session) {if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("不是合法的手机号!");}String code = RandomUtil.randomNumbers(6);session.setAttribute("code", code);// 发送短信log.debug("发送短信验证码:{}", code);return Result.ok();}
}

登录

@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session) {// 实现登录功能return userService.login(loginForm, session);
}
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {// 验证手机号和验证码if (RegexUtils.isPhoneInvalid(loginForm.getPhone())) {return Result.fail("手机号不合法!");}String code = (String) session.getAttribute("code");if (code == null || !code.equals(loginForm.getCode())) {return Result.fail("验证码不正确!");}// 检查用户是否存在QueryWrapper<User> qw = new QueryWrapper<>();qw.eq("phone", loginForm.getPhone());User user = this.baseMapper.selectOne(qw);if (user == null) {user = this.createUserByPhone(loginForm.getPhone());}// 将用户信息保存到 sessionsession.setAttribute("user", user);return Result.ok();
}private User createUserByPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName("user_" + RandomUtil.randomString(5));this.baseMapper.insert(user);return user;
}

统一身份校验

定义拦截器:

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 从 session 获取用户信息HttpSession session = request.getSession();User user = (User) session.getAttribute("user");if (user == null) {response.setStatus(401);return false;}// 将用户信息保存到 ThreadLocalUserDTO userDTO = new UserDTO();userDTO.setIcon(user.getIcon());userDTO.setId(user.getId());userDTO.setNickName(user.getNickName());UserHolder.saveUser(userDTO);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}

添加拦截器:

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}

使用 Redis 存储验证码和用户信息

用 Session 存储验证码和用户信息的系统,无法进行横向扩展,因为多台 Tomcat 无法共享 Session。如果改用 Redis 存储就可以解决这个问题。

修改后的 UserService:

@Log4j2
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();@Overridepublic Result sendCode(String phone, HttpSession session) {if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("不是合法的手机号!");}String code = RandomUtil.randomNumbers(6);stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL);// 发送短信log.debug("发送短信验证码:{}", code);return Result.ok();}@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 验证手机号和验证码if (RegexUtils.isPhoneInvalid(loginForm.getPhone())) {return Result.fail("手机号不合法!");}String code = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + loginForm.getPhone());if (code == null || !code.equals(loginForm.getCode())) {return Result.fail("验证码不正确!");}// 检查用户是否存在QueryWrapper<User> qw = new QueryWrapper<>();qw.eq("phone", loginForm.getPhone());User user = this.baseMapper.selectOne(qw);if (user == null) {user = this.createUserByPhone(loginForm.getPhone());}// 将用户信息保存到 sessionString token = UUID.randomUUID().toString(true);UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(user, userDTO);try {stringRedisTemplate.opsForValue().set(LOGIN_USER_KEY + token,OBJECT_MAPPER.writeValueAsString(userDTO), LOGIN_USER_TTL);} catch (JsonProcessingException e) {e.printStackTrace();throw new RuntimeException(e);}return Result.ok(token);}private User createUserByPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName("user_" + RandomUtil.randomString(5));this.baseMapper.insert(user);return user;}
}

修改后的登录校验拦截器:

public class LoginInterceptor implements HandlerInterceptor {private final StringRedisTemplate stringRedisTemplate;private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 从头信息获取 tokenString token = request.getHeader("Authorization");if (ObjectUtils.isEmpty(token)) {// 缺少 tokenresponse.setStatus(401);return false;}// 从 Redis 获取用户信息String jsonUser = this.stringRedisTemplate.opsForValue().get(LOGIN_USER_KEY + token);UserDTO userDTO = OBJECT_MAPPER.readValue(jsonUser, UserDTO.class);if (userDTO == null) {response.setStatus(401);return false;}// 将用户信息保存到 ThreadLocalUserHolder.saveUser(userDTO);// 刷新 token 有效期stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}

还需要添加一个更新用户信息有效期的拦截器:

public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 如果请求头中有 token,且 redis 中有 token 相关的用户信息,刷新其有效期String token = request.getHeader("Authorization");if (ObjectUtils.isEmpty(token)) {return true;}if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(LOGIN_USER_KEY + token))) {stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL);}return true;}
}

添加这个新的拦截器,并且确保其位于登录验证拦截器之前:

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**");registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}

商户查询

缓存

对商户类型查询使用 Redis 缓存以提高查询效率:

@Service
public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapper, ShopType> implements IShopTypeService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result queryTypeList() {String jsonTypeList = stringRedisTemplate.opsForValue().get(CACHE_TYPE_LIST_KEY);if (!StringUtils.isEmpty(jsonTypeList)) {List<ShopType> typeList = JSONUtil.toList(jsonTypeList, ShopType.class);return Result.ok(typeList);}List<ShopType> typeList = this.query().orderByAsc("sort").list();if (!typeList.isEmpty()){stringRedisTemplate.opsForValue().set(CACHE_TYPE_LIST_KEY, JSONUtil.toJsonStr(typeList), CACHE_TYPE_LIST_TTL);}return Result.ok(typeList);}
}

对商户详情使用缓存:

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result queryById(Long id) {// 先从 Redis 中查询String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (!StringUtils.isEmpty(jsonShop)) {Shop shop = JSONUtil.toBean(jsonShop, Shop.class);return Result.ok(shop);}// Redis 中没有,从数据库查Shop shop = this.getById(id);if (shop != null) {jsonShop = JSONUtil.toJsonStr(shop);stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonShop, CACHE_SHOP_TTL);}return Result.ok(shop);}
}

缓存更新策略

在编辑商户信息时,将对应的缓存删除:

@Override
public Result update(Shop shop) {if (shop.getId() == null) {return Result.fail("商户id不能为空");}// 更新商户信息this.updateById(shop);// 删除缓存stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId());return Result.ok();
}

缓存穿透

缓存穿透指如果请求的数据在缓存和数据库中都不存在,就不会生成缓存数据,每次请求都不会使用缓存,会对数据库造成压力。

可以通过缓存空对象的方式解决缓存穿透问题。

在查询商铺信息时缓存空对象:

@Override
public Result queryById(Long id) {// 先从 Redis 中查询String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (!StringUtils.isEmpty(jsonShop)) {Shop shop = JSONUtil.toBean(jsonShop, Shop.class);return Result.ok(shop);}// Redis 中没有,从数据库查Shop shop = this.getById(id);if (shop != null) {jsonShop = JSONUtil.toJsonStr(shop);stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonShop, CACHE_SHOP_TTL);return Result.ok(shop);} else {// 缓存空对象到缓存中stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL);return Result.fail("店铺不存在");}
}

在这里,缓存中的空对象用空字符串代替,并且将缓存存活时间设置为一个较短的值(比如说2分钟)。

在从缓存中查询到空对象时,返回商铺不存在:

@Override
public Result queryById(Long id) {// 先从 Redis 中查询String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (!StringUtils.isEmpty(jsonShop)) {Shop shop = JSONUtil.toBean(jsonShop, Shop.class);return Result.ok(shop);}// 如果从缓存中查询到空对象,表示商铺不存在if ("".equals(jsonShop)) {return Result.fail("商铺不存在");}// ...
}

缓存击穿

缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

常见的解决方案有两种:

  • 互斥锁
  • 逻辑过期

可以利用 Redis 做互斥锁来解决缓存击穿问题:

@Override
public Result queryById(Long id) {//        return queryWithCachePenetration(id);return queryWithCacheBreakdown(id);
}/*** 用 Redis 创建互斥锁** @param name 锁名称* @return 成功/失败*/
private boolean lock(String name) {Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(name, "1", Duration.ofSeconds(10));return BooleanUtil.isTrue(result);
}/*** 删除 Redis 互斥锁** @param name 锁名称*/
private void unlock(String name) {stringRedisTemplate.delete(name);
}/*** 查询店铺信息-缓存击穿** @param id* @return*/
private Result queryWithCacheBreakdown(Long id) {// 先查询是否存在缓存String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (!StringUtils.isEmpty(jsonShop)) {Shop shop = JSONUtil.toBean(jsonShop, Shop.class);return Result.ok(shop);}// 如果从缓存中查询到空对象,表示商铺不存在if ("".equals(jsonShop)) {return Result.fail("商铺不存在");}// 缓存不存在,尝试获取锁,并创建缓存final String lockName = "lock:shop:" + id;try {if (!lock(lockName)){// 获取互斥锁失败,休眠一段时间后重试Thread.sleep(50);return queryWithCacheBreakdown(id);}// 获取互斥锁成功,创建缓存// 模拟长时间才能创建缓存Thread.sleep(100);Shop shop = this.getById(id);if (shop != null) {jsonShop = JSONUtil.toJsonStr(shop);stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonShop, CACHE_SHOP_TTL);return Result.ok(shop);} else {// 缓存空对象到缓存中stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL);return Result.fail("店铺不存在");}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {// 释放锁unlock(lockName);}
}

下面是用逻辑过期解决缓存击穿问题的方式。

首先需要将热点数据的缓存提前写入 Redis(缓存预热):

public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {/*** 创建店铺缓存** @param id       店铺id* @param duration 缓存有效时长*/public void saveShopCache(Long id, Duration duration) {Shop shop = getById(id);RedisCache<Shop> redisCache = new RedisCache<>();redisCache.setExpire(LocalDateTime.now().plus(duration));redisCache.setData(shop);stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisCache));}// ...
}
@SpringBootTest
class HmDianPingApplicationTests {@Autowiredprivate ShopServiceImpl shopService;@Testpublic void testSaveShopCache(){shopService.saveShopCache(1L, Duration.ofSeconds(1));}}
@Data
public class RedisCache<T> {private LocalDateTime expire; //逻辑过期时间private T data; // 数据
}

Redis 中的缓存信息包含两部分:过期时间和具体信息。大致如下:

{"data": {"area": "大关","openHours": "10:00-22:00","sold": 4215,// ...},"expire": 1708258021725
}

且其 TTL 是-1,也就是永不过期。

具体的缓存读取和重建逻辑:

/*** 用逻辑过期解决缓存击穿问题** @return*/
private Result queryWithLogicalExpiration(Long id) {//检查缓存是否存在String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (StringUtils.isEmpty(jsonShop)) {// 缓存不存在return Result.fail("店铺不存在");}// 缓存存在,检查是否过期RedisCache<Shop> redisCache = JSONUtil.toBean(jsonShop, new TypeReference<RedisCache<Shop>>() {}, true);if (redisCache.getExpire().isBefore(LocalDateTime.now())) {// 如果过期,尝试获取互斥锁final String LOCK_NAME = LOCK_SHOP_KEY + id;if (lock(LOCK_NAME)) {// 获取互斥锁后,单独启动线程更新缓存CACHE_UPDATE_ES.execute(() -> {try {// 模拟缓存重建的延迟Thread.sleep(200);saveShopCache(id, Duration.ofSeconds(1));} catch (Exception e) {throw new RuntimeException(e);} finally {unlock(LOCK_NAME);}});}}// 无论是否过期,返回缓存对象中的信息return Result.ok(redisCache.getData());
}

封装 Redis 缓存工具类

可以对对 Redis 缓存相关逻辑进行封装,可以避免在业务代码中重复编写相关逻辑。封装后分别对应以下方法:

  • 设置缓存数据(TTL)
  • 设置缓存数据(逻辑过期时间)
  • 从缓存获取数据(用空对象解决缓存穿透问题)
  • 从缓存获取数据(用互斥锁解决缓存击穿问题)
  • 从缓存获取数据(用逻辑过期解决缓存击穿问题)

工具类的完整代码可以参考这里。

本文的完整示例代码可以从这里获取。

参考资料

  • 黑马程序员Redis入门到实战教程
http://www.ds6.com.cn/news/110782.html

相关文章:

  • 做网站公司的前景电商网站上信息资源的特点包括
  • 分类信息网站建设方案爱站网长尾关键词挖掘工具电脑版
  • adobe cms 网站制作产品推广策划
  • 文章网站哪里建设好站长工具 seo综合查询
  • 如何为产品做网站网站seo哪家公司好
  • 做网站asp用什么软件google推广一年3万的效果
  • 厦门市住建委网站首页高清免费观看电视网站
  • 酒店网站策划书新疆头条今日头条新闻
  • 可以查授权的网站怎么做企业站seo报价
  • 湖州市交通建设管理局网站在线工具seo
  • word里面网站超链接怎么做百度知道问答首页
  • 杭州网站建设厦门网站优化
  • 逼格高的网站网站流量指标有哪些
  • 买了域名后做网站该怎么弄网店怎么推广和宣传
  • 网站设计开发建设公司宁波网站建设网站排名优化
  • 站点地址和wordpress区别舆情分析网站
  • 武汉网站seo技术国际新闻网
  • 网站维护团队域名注册平台
  • 长安网站建设多少钱关于营销的最新的新闻
  • 室内设计论坛网站管理培训机构
  • 网站建设安全性原则网络营销主要是什么
  • 上海崇明林业建设有限公司 网站厦门百度seo
  • 动态网站和静态网站区别产品线下推广方式都有哪些
  • 网站访问量数据百度网络营销的概念
  • 哪个网站做团购要求低点关键词有几种类型
  • 南京移动网站建设百度网站名称及网址
  • 北京哪家做网站和网络推广好的国内营销推广渠道
  • 企业宣传网站建设软文写手接单平台
  • 荆门网站开发公司电话竞价托管服务公司
  • 租服务器做网站百度推广账号注册流程