返回顶部
首页 > 资讯 > 服务器 >黑马点评项目总结
  • 166
分享到

黑马点评项目总结

java服务器数据库 2023-09-08 09:09:37 166人浏览 八月长安
摘要

黑马点评 一、短信登陆功能1.基于session实现2.基于session实现登陆的问题3.基于redis实现短信登陆4.补充ThreadLocal相关知识a.ThreadLocal的数据结构

黑马点评

一、短信登陆功能

1.基于session实现

在这里插入图片描述

2.基于session实现登陆的问题

单体应用时用户的会话信息保存在session中,session存在于服务器端的内存中,由于前前后后用户只针对一个WEB服务器,所以没啥问题。但是一到了web服务器集群的环境下(我们一般都是用Nginx负载均衡,若是使用了轮询等这种请求分配策略),就会导致用户小a在A服务器登录了,session存在于A服务器中,但是第二次请求被分配到了B服务器,由于B服务器中没有用户小a的session会话,导致用户小a还要再登陆一次,以此类推。这样用户体验很不好。当然解决办法也有很多种,比如同一个用户分配到同一个服务处理、使用cookie保持用户会话信息等。
因此,要解决这样的问题必须满足以下条件:

  • 数据共享
  • 内存存储
  • key、value结构

3.基于Redis实现短信登陆

在这里插入图片描述

发送验证码:

@PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, httpsession session) {    return userService.sendCode(phone,session);}@Overridepublic Result sendCode(String phone, HttpSession session) {    //1.校验手机号    if (RegexUtils.isPhoneInvalid(phone)) {        //2.如果不符合,返回错误信息        return Result.fail("手机号格式错误!");    }    //3.符合则生成验证码    final String code = RandomUtil.randomNumbers(6);    //4.保存验证码到redis    stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);    //5.发送验证码    log.debug("发送短信验证码成功,验证码:{}",code);    //6.返回null    return Result.ok();}

验证登陆功能:
login方法会把生成的token返回给前端,浏览器会将其保存到session中。

@PostMapping("/login")public Result login(@RequestBody LoginFORMDTO loginForm, HttpSession session){    return userService.login(loginForm,session);}@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {    //1.校验手机号    final String phone = loginForm.getPhone();    if (RegexUtils.isPhoneInvalid(phone)) {        //2.如果不符合,返回错误信息        return Result.fail("手机号格式错误!");    }    //2.校验验证码,从redis中获取    final String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);    final String code = loginForm.getCode();    if(cacheCode==null||!cacheCode.equals(code)){        //3.不一直,报错        return Result.fail("验证码错误");    }    //4.一致,根据手机号查询用户    User user = query().eq("phone", phone).one();    //5.判断用户是否存在    if (user == null) {        //6.不存在,创建新用户并保存        user = createUserWithPhone(phone);    }    //7.保存用户信息到redis中    //7.1随机生成token,作为登陆令牌    String token = UUID.randomUUID().toString(true);    //7.2将User对象转为HashMap存储    UserDTO userDTO = BeanUtil.copyProperties(user,UserDTO.class);    final Map<String, Object> map = BeanUtil.beanToMap(userDTO, new HashMap<>(),            CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)->{                return fieldValue.toString();            })    );    //7.3存储    stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY+token,map);    //7.4设置token有效期    stringRedisTemplate.expire(LOGIN_USER_KEY+token,3000,TimeUnit.MINUTES);    //8.返回token    return Result.ok(token);}private User createUserWithPhone(String phone) {    User user = new User();    user.setPhone(phone);    user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(5));    save(user);    return user;}

这里使用redis的hash结构存储user信息,原因是:

  • 若使用String结构,以JSON字符串来保存,比较直观
  • 但Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD,并且内存占用更少

在这里插入图片描述
拦截器:

  • 首先,对于每个请求,我们首先根据token判断用户是否已经登陆(是否已经保存到ThreadLocal中),如果没有登陆,放行交给登陆拦截器去做,如果已经登陆,刷新token的有效期,然后放行。
  • 之后来到登陆拦截器,如果ThreadLocal没有用户,说明没有登陆,拦截,否则放行。
    在这里插入图片描述

定义UserHolder工具类:

public class UserHolder {    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();    public static void saveUser(UserDTO user){        tl.set(user);    }    public static UserDTO getUser(){        return tl.get();    }    public static void removeUser(){        tl.remove();    }}

刷新token拦截器:

@Slf4jpublic class RefreshTokenInterceptor implements HandlerInterceptor {    private StringRedisTemplate stringRedisTemplate;    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate){        this.stringRedisTemplate = stringRedisTemplate;    }    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        //1.获取请求头中的token        final String token = request.getHeader("authorization");        if (token == null) {            return true;        }        //2.获取redis中的用户        final Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);        //3.判断用户是否存在        if (userMap.isEmpty()) {            return true;        }        //5.将查询到的Hash数据转换为UserDto对象        final UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);        //6.存在,保存用户信息到ThreadLocal        UserHolder.saveUser(userDTO);        //7.刷新token有效期        stringRedisTemplate.expire(LOGIN_USER_KEY+token,3000, TimeUnit.MINUTES);        //8.放行        return true;    }    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        UserHolder.removeUser();    }}

登陆拦截器:

public class LoginInterceptor implements HandlerInterceptor {    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        //1.判断是否需要拦截(ThreadLocal中是否有用户)        if(UserHolder.getUser()==null){            response.setStatus(401);            return false;        }        //8.放行        return true;    }    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        UserHolder.removeUser();    }}

在配置类中配置拦截器:

@Configurationpublic class mvcConfig implements WebMvcConfigurer {    @Resource    private StringRedisTemplate stringRedisTemplate;    @Override    public void addInterceptors(InterceptorReGIStry registry) {        //登陆拦截器        registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(                "/user/code","/user/login","/blog/hot","/shoppublic <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Long time, TimeUnit unit,Function<ID,R> dbFallback){    String key = keyPrefix+id;    //1.从redis查询商铺缓存    String json = stringRedisTemplate.opsForValue().get(key);    //2.判断是否存在    if (StrUtil.isNotBlank(json)) {        //3.存在,直接返回        return JSONUtil.toBean(json, type);    }    //命中的是否是空值    if (json != null) {        return null;    }    //4.不存在,根据id查询数据库    R r = dbFallback.apply(id);    //5.不存在,返回错误    if(r==null){        //将空值写入reddis        stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);        return null;    }    //6.存在,写入redis    this.set(key,r,time,unit);    //7.返回    return r;}

b.缓存雪崩

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案:

  • 给不同的Key的TTL添加随机值
  • 利用Redis集群提高服务的可用性
  • 给缓存业务添加降级限流策略
  • 给业务添加多级缓存

c.缓存击穿

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

  • 互斥
  • 逻辑过期

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.基于逻辑过期解决缓存击穿问题

在这里插入图片描述

private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);public <R,ID> R queryWithLogicalExpire(String keyPrefix,ID id,Class<R> type, Long time, TimeUnit unit,Function<ID,R> dbFallback){    String key = keyPrefix+id;    //1.从redis查询商铺缓存    String json = stringRedisTemplate.opsForValue().get(key);    //2.判断是否存在    if (StrUtil.isBlank(json)) {        //3.不存在,直接返回        return null;    }    //4.命中,先把json反序列化    RedisData redisData = JSONUtil.toBean(json, RedisData.class);    JSONObject data = (JSONObject) redisData.getData();    R r = JSONUtil.toBean(data, type);    LocalDateTime expireTime = redisData.getExpireTime();    //5.判断是否过期    if(expireTime.isAfter(LocalDateTime.now())){        //5.1未过期,直接返回        return r;    }    //5.2已过期,需要缓存重建    //6.缓存重建    //6.1获取互斥锁    String lockkey = LOCK_SHOP_KEY + id;    boolean lock = tryLock(lockkey);    //6.2判断是否获取锁成功    if(lock){        //6.3成功,开启独立线程,实现缓存重建        CACHE_REBUILD_EXECUTOR.submit(()->{            try {                //查询数据库                R r1 = dbFallback.apply(id);                //写入redis                this.setWithLogicalExpire(key,r1,time,unit);            } catch (Exception e) {                e.printStackTrace();            } finally {                //释放锁                unlock(lockkey);            }        });    }    //6.4返回商铺信息    return r;}

三、优惠券秒杀

1.优惠券秒杀下单

一般流程:
在这里插入图片描述

2.超卖问题

请求a查询库存,发现库存为1,请求b这时也来查询库存,库存也为1,然后请求a让数据库减1,这时候b查询到的仍然是1,也继续让库存减1,就会导致超卖。

超卖问题有以下几个解决方案:

  • 乐观锁:认为线程安全问题不一定会发生,因此不加锁,只是在更新数据时去判断有没有其它线程对数据做了修改。如果没有修改则认为是安全的,自己才更新数据。如果已经被其它线程修改说明发生了安全问题,此时可以重试或异常。
  • 悲观锁:认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行。例如Synchronized、Lock都属于悲观锁

实现乐观锁主要有以下两种方法:

  1. 版本号法

每次更新数据库的时候按照版本查询,并且要更新版本。
在这里插入图片描述

  1. CAS

CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
在这里插入图片描述

CAS的缺点:

CPU开销较大
并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。

不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。

3.一人一单功能

要求同一个优惠券,一个用户只能下一单

在这里插入图片描述
这样的方式会产生并发安全问题:
在这里插入图片描述

通过加锁可以解决在单机情况下的一人一单安全问题,但是在集群模式下就不行了(每个jvm都有自己的锁监视器,集群模式下各个服务器的锁不共享)。
因此,我们的解决方案就是实现一个共享的锁监视器,即:
分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。

4.基于redis的分布式

a.setnx命令

setnx = SET if Not eXists

  • 将 key 的值设为 value ,当且仅当 key 不存在。

  • 若给定的 key 已经存在,则 SETNX 不做任何动作

在这里插入图片描述

b.普通setnx分布式锁出现的问题

在某个线程获取锁执行业务时若发生阻塞,且阻塞过程中锁超时,此时另一个线程同样来请求锁,发现可以获取锁,但实际上前一个线程还没执行完。

解决方案:

  • 在获取锁时存入线程标示(可以用UUID表示)
  • 在释放锁时先获取锁中的线程标示,判断是否与当前线程标示一致
  • 如果一致则释放锁
  • 如果不一致则不释放锁

在这里插入图片描述

四、消息队列优化

(三四章先占坑)

五、达人探店

1.发布探店笔记

简单的crud
在这里插入图片描述

2.实现点赞功能

需求:

  • 同一个用户只能点赞一次,再次点击则取消点赞
  • 如果当前用户已经点赞,则点赞按钮高亮显示(前端已实现,判断字段Blog类的isLike属性)

实现步骤:

  1. 给Blog类中添加一个isLike字段,标示是否被当前用户点赞
  2. 修改点赞功能,利用Redis的set集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则点赞数-1
  3. 修改根据id查询Blog的业务,判断当前登录用户是否点赞过,赋值给isLike字段
  4. 修改分页查询Blog业务,判断当前登录用户是否点赞过,赋值给isLike字段

3.点赞排行榜

需求:按照点赞时间先后排序,返回Top5的用户
使用SortedSet:

  • 通过 ZSCORE 命令获取 SortedSet 中存储的元素的相关的 SCORE 值。
  • 通过 ZRANGE 命令获取指定范围内的元素。

在这里插入图片描述
完整代码:
BlogController

@RestController@RequestMapping("/blog")public class BloGController {    @Resource    private IBlogService blogService;    @PutMapping("/like/{id}")    public Result likeBlog(@PathVariable("id") Long id) {        return blogService.likeBlog(id);    }    @GetMapping("/hot")    public Result queryHotBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {        return blogService.queryHotBlog(current);    }    @GetMapping("/{id}")    public Result queryBlogById(@PathVariable("id") String id){        return blogService.queryBlogById(id);    }    @GetMapping("/likes/{id}")    public Result queryBlogLikes(@PathVariable("id") String id) {        return blogService.queryBlogLikes(id);    }}

IBlogService

public interface IBlogService extends IService<Blog> {    Result queryBlogById(String id);    Result queryHotBlog(Integer current);    Result likeBlog(Long id);    Result queryBlogLikes(String id);}

BlogServiceImpl

Servicepublic class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {    @Autowired    private IUserService userService;    @Autowired    private StringRedisTemplate stringRedisTemplate;    @Override    public Result queryHotBlog(Integer current) {        // 根据用户查询        Page<Blog> page = query()                .orderByDesc("liked")                .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));        // 获取当前页数据        List<Blog> records = page.getRecords();        // 查询用户        records.forEach(blog -> {            this.queryBlogUser(blog);            this.isBlogLiked(blog);        });        return Result.ok(records);    }    @Override    public Result likeBlog(Long id) {        // 1、获取登录用户        UserDTO user = UserHolder.getUser();        // 2、判断当前登录用户是否已经点赞        Double score = stringRedisTemplate.opsForZSet().score(RedisConstants.BLOG_LIKED_KEY + id, user.getId().toString());        if(score == null) {            // 3、如果未点赞,可以点赞            // 3.1、数据库点赞数 +1            boolean isSuccess = update().setsql("liked = liked+1").eq("id", id).update();            // 3.2、保存用户到 Redis 的 set 集合            if(isSuccess){                // 时间作为 key 的 score                stringRedisTemplate.opsForZSet().add(RedisConstants.BLOG_LIKED_KEY + id, user.getId().toString(), System.currentTimeMillis());            }        } else {            // 4、如果已点赞,取消点赞            // 4.1、数据库点赞数 -1            boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();            // 4.2、把用户从 Redis 的 set 集合移除            if(isSuccess){                stringRedisTemplate.opsForZSet().remove(RedisConstants.BLOG_LIKED_KEY + id, user.getId().toString());            }        }        return Result.ok();    }    @Override    public Result queryBlogLikes(String id) {        String key = RedisConstants.BLOG_LIKED_KEY + id;        // 查询 top5 的点赞用户        Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);        if(top5 == null){            return Result.ok(Collections.emptyList());        }        // 解析出其中的用户id        List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());        String join = StrUtil.join(",", ids);        // 根据用户id查询用户        List<UserDTO> userDTOS = userService.query().in("id", ids).last("order by filed(id, "+join+")").list()                .stream()                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))                .collect(Collectors.toList());        return Result.ok(userDTOS);    }    private void queryBlogUser(Blog blog) {        Long userId = blog.getUserId();        User user = userService.getById(userId);        blog.setName(user.getNickName());        blog.setIcon(user.getIcon());    }    @Override    public Result queryBlogById(String id) {        Blog blog = getById(id);                if(blog == null){            return Result.fail("笔记不存在!");        }        queryBlogUser(blog);        // 查询 Blog 是否被点赞        isBlogLiked(blog);        return Result.ok(blog);    }    private void isBlogLiked(Blog blog) {        UserDTO user = UserHolder.getUser();        if(user == null){            return;        }        Long userId = user.getId();        String key = RedisConstants.BLOG_LIKED_KEY + blog.getId();        Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());        blog.setIsLike(score != null);    }}

六、好友关注

1.关注和取关

在这里插入图片描述

基于Mysql实现:
在这里插入图片描述
基于redis实现:
设置一个给每个用户设置一个key,利用set结构存储关注该用户的人。
代码:

@PutMapping("/{id}/{isFollow}")public Result follow(@PathVariable("id") Long followUserId,@PathVariable("isFollow") Boolean isFollow){    return followService.follow(followUserId,isFollow);}@Overridepublic Result follow(Long followUserId, Boolean isFollow) {    Long userId = UserHolder.getUser().getId();    String key = "follows:" + userId;    //1.判断关注还是取关    if(isFollow){        //2.关注,新增数据        Follow follow = new Follow();        follow.setUserId(userId);        follow.setFollowUserId(followUserId);        boolean success = save(follow);        if(success){            //把关注用户的id,放入redis的set集合            stringRedisTemplate.opsForSet().add(key,followUserId.toString());        }    }else{        //3.取关,删除数据        boolean success = remove(new QueryWrapper<Follow>().eq("user_id", userId).eq("follow_user_id", followUserId));        //把关注的用户id从redis集合中移除        if(success)  stringRedisTemplate.opsForSet().remove(key,followUserId.toString());    }    return Result.ok();}
@GetMapping("/or/not/{id}")public Result isFollow(@PathVariable("id") Long followUserId){    return followService.isFollow(followUserId);}@Overridepublic Result isFollow(Long followUserId) {    Long userId = UserHolder.getUser().getId();    //1.查询是否关注    Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();    return Result.ok(count>0);}

2.共同关注

在这里插入图片描述
在关注点击关注用户时,用redis的set结构存储自己关注了哪些用户,然后利用集合的交集就能轻松求出共同关注的用户了。

@GetMapping("/common/{id}")public Result followCommons(@PathVariable("id") Long id){    return followService.followCommons(id);}@Overridepublic Result followCommons(Long id) {    //1.获取当前用户    Long userId = UserHolder.getUser().getId();    String key = "follows:" + userId;    //2.求交集    String key2 = "follows:" + id;    Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key, key2);    if(intersect==null||intersect.isEmpty()){        //无交集        return Result.ok(Collections.emptyList());    }    //3.解析id集合    List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());    //4.查询用户    List<UserDTO> users = userService.listByIds(ids)            .stream()            .map(user -> BeanUtil.copyProperties(user, UserDTO.class))            .collect(Collectors.toList());    return Result.ok(users);}

3.关注推送

在这里插入图片描述

Feed流产品有两种常见模式:

  • Timeline:不做内容筛选,简单的按照内容发布时间排序,常用于好友或关注。例如朋友圈
    优点:信息全面,不会有缺失。并且实现也相对简单
    缺点:信息噪音较多,用户不一定感兴趣,内容获取效率低
  • 智能排序:利用智能算法屏蔽掉违规的、用户不感兴趣的内容。推送用户感兴趣信息来吸引用户
    优点:投喂用户感兴趣信息,用户粘度很高,容易沉迷
    缺点:如果算法不精准,可能起到反作用

本例中的个人页面,是基于关注的好友来做Feed流,因此采用Timeline的模式。该模式的实现方案有三种:

  • 拉模式
  • 推模式
  • 推拉结合
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

这里基于推模式模式实现关注推送,需求:

  • 修改新增探店笔记的业务,在保存blog到数据库的同时,推送到粉丝的收件箱
  • 收件箱满足可以根据时间戳排序,必须用Redis的数据结构实现
  • 查询收件箱数据时,可以实现分页查询
    在这里插入图片描述

4.实现推送功能

推送:

@PostMappingpublic Result saveBlog(@RequestBody Blog blog) {    return blogService.saveBlog(blog);}@Overridepublic Result saveBlog(Blog blog) {    // 1.获取登录用户    UserDTO user = UserHolder.getUser();    blog.setUserId(user.getId());    // 2.保存探店博文    boolean success = this.save(blog);    if(!success) return Result.fail("新增笔记失败!");    //3.查询笔记作者的所有粉丝    List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();    //4.推送笔记id给有所粉丝    for (Follow follow : follows) {        //4.1获取粉丝id        Long followId = follow.getUserId();        //4.2推送        String key = FEED_KEY+followId;        stringRedisTemplate.opsForZSet().add(key,blog.getId().toString(),System.currentTimeMillis());    }    // 返回id    return Result.ok(blog.getId());}

读取:

@GetMapping("/of/follow")public Result queryBloGofFollow(        @RequestParam("lastId") Long max,@RequestParam(value = "offset",defaultValue = "0") Integer offset){    return blogService.queryBlogOfFollow(max,offset);}@Overridepublic Result queryBlogOfFollow(Long max, Integer offset) {    //1.获取当前用户    Long userId = UserHolder.getUser().getId();    //2.查询收件箱    String key = FEED_KEY + userId;    Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()            .reverseRangeByScoreWithScores(key, 0, max, offset, 2);    if(typedTuples==null||typedTuples.isEmpty()) return Result.ok();    //3.解析数据:blogId,minTime(时间戳),offset    List<Long> ids = new ArrayList<>(typedTuples.size());    long minTime = 0;    int os = 1;    for (ZSetOperations.TypedTuple<String> tuple : typedTuples) {        //3.1查询id        String idStr = tuple.getValue();        ids.add(Long.valueOf(idStr));        //4.2获取分数(时间戳)        if(tuple.getScore().longValue()==minTime){            os++;        }else os  = 1;        minTime = tuple.getScore().longValue();    }    //4.根据id查询blog    String idStr = StrUtil.join(",", ids);    List<Blog> blogs = query()            .in("id",ids).last("ORDER BY FIELD(ID,"+idStr+")").list();    for (Blog blog : blogs) {        queryBlogUser(blog);        isBlogLiked(blog);    }    //5.封装并返回    ScrollResult r = new ScrollResult();    r.setList(blogs);    r.setOffset(os);    r.setMinTime(minTime);    return Result.ok(r);}

七、签到功能

1.数据库实现

在这里插入图片描述

2.redis实现

在这里插入图片描述

3.具体代码

@PostMapping("/sign")public Result sign(){    return userService.sign();} @Override public Result sign() {     //1.获取当前用户     Long userId = UserHolder.getUser().getId();     //2.获取日期     LocalDateTime now = LocalDateTime.now();     //3.拼接key     String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));     String key = USER_SIGN_KEY + userId + keySuffix;     //4.获取今天是本月的第几天     int dayOfMonth = now.getDayOfMonth() - 1;     //5.写入redis     stringRedisTemplate.opsForValue().setBit(key,dayOfMonth,true);     return Result.ok(); }

来源地址:https://blog.csdn.net/qq_45733304/article/details/126443684

--结束END--

本文标题: 黑马点评项目总结

本文链接: https://lsjlt.com/news/399700.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 黑马点评项目总结
    黑马点评 一、短信登陆功能1.基于session实现2.基于session实现登陆的问题3.基于redis实现短信登陆4.补充ThreadLocal相关知识a.ThreadLocal的数据结构...
    99+
    2023-09-08
    java 服务器 数据库
  • 菜鸟项目练习:黑马点评项目总结
    目录 1. 项目介绍 2.各个功能模块  2.1  登录模块   2.1.1 实现短信登录   2.1.2 编写拦截器  2.2 查询商户模块    2.2.1 主页面查询商户类型    2.2.3 按距离查询商户  2.3 优惠券秒杀模...
    99+
    2023-09-05
    java redis
  • 黑马点评项目全面业务总结
    1 黑马点评项目 1.1 短信登陆 1.1.1 短信登陆简介 session共享问题:多台服务器并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失的问题。 在进行短信登录...
    99+
    2023-10-08
    java redis 开发语言
  • 黑马实战项目瑞吉外卖的总结
    文章目录 一. 瑞吉外卖项目总结1. 后端Controller层返回结果统一封装的R对象2.定义静态资源映射关系3. 配置消息资源转换器3.1 Reggie项目中遇到的问题3.2 原理3.3 ...
    99+
    2023-09-09
    mybatis java spring boot 后端
  • 【Java项目推荐】值得写到简历上的项目--黑马点评
    优惠卷秒杀 前言优惠券秒杀实现优惠券秒杀下单超卖问题一人一单分布式锁redis中加锁的一些特殊情况手动实现分布式锁分布式锁误删情况1分布式锁误删情况2lua脚本解决多条命令的原子性问题Redisson 秒杀优化异步秒杀思路基...
    99+
    2023-08-24
    java spring boot redis rabbitmq
  • 项目总结(三)----------Pyt
    在自动化测试过程中,比较常用的操作就是对远程主机进行操作,如何操作呢?使用SSH远程登陆到主机,然后执行相应的command即可。 使用Python来实现这些操作就相当简单了。下面是测试code。 代码如下:(code运行环境:pyt...
    99+
    2023-01-31
    项目 Pyt
  • Python项目通用的目录结构总结
    一个好的项目结构会让我们在开发中更加得心应手。 对于Web项目,我们通常采用Flask或Django等框架,会有一套适合这种项目的工程目录。 对于爬虫项目,通常有Scrapy等开源框架,也会提供一套适合这种项目的工程目录。 对...
    99+
    2023-01-31
    结构 目录 项目
  • 如何运行黑马程序员redis项目黑马点评(hm-dianping)、常见报错解决与部分接口的测试方法
    文章目录 一、相关链接二、下载代码方法一:使用git clone方法二:直接下载程序zip压缩包 三、如何运行这份代码运行sql文件1、先新建数据库hmdp2、导入项目中的hmdp.sql...
    99+
    2023-09-02
    redis 数据库 mysql postman 经验分享
  • 亚马逊测评项目真实吗?
    1. 什么是亚马逊测评项目? 亚马逊测评项目是亚马逊公司推出的一项服务,旨在帮助卖家提高其产品在亚马逊平台上的排名和销量。该项目的主要方式是通过向卖家提供免费或者优惠的产品,以换取他们对该产品进行评价和评论。 2. 亚马逊测评项目的真实性...
    99+
    2023-10-27
    亚马逊 真实 项目
  • 牛客论坛项目总结
    目录 1.请简要介绍一下你的项目? 1.如何实现项目的注册问题 2.项目如何实现用户唯一性检验 3.登录状态保存在哪 4.用户登陆上之后怎么显示登录页面 5.拦截器(Interceptor) 6.ThreadLocal(线程安全) 7.md...
    99+
    2023-09-13
    servlet java 开发语言
  • Maven项目中resources配置总结
    目录背景第一部分 基本配置介绍第二部分 具体配置和注意事项2.1 案例说明2.2 正则过滤2.3 变量占位符2.5 子目录第二部分 读取resources资源背景 通常Maven项目...
    99+
    2024-04-02
  • JS实现扫雷项目总结
    本文实例为大家分享了JS实现扫雷项目的总结,供大家参考,具体内容如下 项目展示图 项目准备 一样的,我们先是准备出三个文件夹,以及根目录下的index.html 文件 然后是两张...
    99+
    2024-04-02
  • python项目打包发布总结
    概览 这里主要收集python项目的打包、发布和部署的常用方法,只是入门级别,深入的流程还是以官方文档为准(链接每节都已经给出)。 distutils,setuptools,pip,virtualenv 官网资料(Python...
    99+
    2023-01-31
    项目 python
  • Vue3结合TypeScript项目开发实践总结
    目录概述1、compositon Api1、ref 和 reactive的区别?2、周期函数3、store使用4、router的使用2、关注点分离3、TypeScript支持概述 ...
    99+
    2024-04-02
  • Vue3.x项目开发的一些常用知识点总结
    目录一、定义组件属性二、formatter简写三、子父组件通信四、监听组件属性变化五、自定义指令总结 PS:以下知识点都是基于 vue3.x + typescri...
    99+
    2024-04-02
  • uni-app分包项目实战总结
    目录前言今天就来说uni-app如何分包:总结前言 项目需要uni-app开发,说说uni-app是什么,uni-app它跟Trao框架一样都是用来做多端开发的 共同点是:都可以发布...
    99+
    2024-04-02
  • iOS练手项目知识点汇总
    基础理解篇 Objective-C是一种面向对象的编程语言,它支持元编程。元编程是指编写程序来生成或操纵其他程序的技术。 Objective-C中,元编程可以使用Objective-C的动态特性来实现...
    99+
    2023-09-12
    ios cocoa macos
  • 仿牛客网讨论社区项目—项目总结及项目常见面试题
    1.项目中大部分的功能和技术         整个技术是构建在SpringBoot上的,其他技术是依托于SpringBoot之上的。SpringBoot只是起到辅助的作用,降低其他技术的使用难度。整个技术的核心是Spring框架,在Spri...
    99+
    2023-09-01
    spring boot 中间件 mysql javascript maven
  • Django项目优化数据库操作总结
    目录合理的创建索引设置数据库持久连接 减少SQL的执行次数仅获取需要的字段数据使用批量创建、更新和删除,不随意对结果排序参考网址:Django官方数据库优化 使用 QuerySet...
    99+
    2024-04-02
  • Unity开发VR项目问题总结分析
    目录一、StreamVR问题:1.运行项目时不显示手柄控制器:2.按键动作检测出现重复问题:3.Error during OpenVR Init: Init_InterfaceNot...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作