├── src ├── main │ ├── java │ │ └── com │ │ │ └── hmdp │ │ │ ├── dto │ │ │ ├── UserDTO.java │ │ │ ├── LoginFormDTO.java │ │ │ ├── ScrollResult.java │ │ │ └── Result.java │ │ │ ├── utils │ │ │ ├── RedisData.java │ │ │ ├── ILock.java │ │ │ ├── SystemConstants.java │ │ │ ├── UserHolder.java │ │ │ ├── RegexPatterns.java │ │ │ ├── LoginInterceptor.java │ │ │ ├── RedisConstants.java │ │ │ ├── RegexUtils.java │ │ │ ├── PasswordEncoder.java │ │ │ ├── RedisIdWorker.java │ │ │ ├── RefreshTokenInterceptor.java │ │ │ ├── SimpleRedisLock.java │ │ │ └── CacheClient.java │ │ │ ├── mapper │ │ │ ├── BlogMapper.java │ │ │ ├── ShopMapper.java │ │ │ ├── UserMapper.java │ │ │ ├── FollowMapper.java │ │ │ ├── UserInfoMapper.java │ │ │ ├── BlogCommentsMapper.java │ │ │ ├── VoucherOrderMapper.java │ │ │ ├── SeckillVoucherMapper.java │ │ │ ├── ShopTypeMapper.java │ │ │ └── VoucherMapper.java │ │ │ ├── service │ │ │ ├── IUserInfoService.java │ │ │ ├── ISeckillVoucherService.java │ │ │ ├── IBlogCommentsService.java │ │ │ ├── IShopTypeService.java │ │ │ ├── IVoucherService.java │ │ │ ├── IVoucherOrderService.java │ │ │ ├── IFollowService.java │ │ │ ├── IShopService.java │ │ │ ├── impl │ │ │ │ ├── UserInfoServiceImpl.java │ │ │ │ ├── BlogCommentsServiceImpl.java │ │ │ │ ├── SeckillVoucherServiceImpl.java │ │ │ │ ├── VoucherServiceImpl.java │ │ │ │ ├── ShopTypeServiceImpl.java │ │ │ │ ├── FollowServiceImpl.java │ │ │ │ ├── ShopServiceImpl.java │ │ │ │ ├── UserServiceImpl.java │ │ │ │ ├── BlogServiceImpl.java │ │ │ │ └── VoucherOrderServiceImpl.java │ │ │ ├── IBlogService.java │ │ │ └── IUserService.java │ │ │ ├── controller │ │ │ ├── BlogCommentsController.java │ │ │ ├── ShopTypeController.java │ │ │ ├── FollowController.java │ │ │ ├── VoucherOrderController.java │ │ │ ├── VoucherController.java │ │ │ ├── UploadController.java │ │ │ ├── ShopController.java │ │ │ ├── BlogController.java │ │ │ └── UserController.java │ │ │ ├── config │ │ │ ├── WebExceptionAdvice.java │ │ │ ├── RedissonConfig.java │ │ │ ├── MybatisConfig.java │ │ │ └── MvcConfig.java │ │ │ ├── HmDianPingApplication.java │ │ │ └── entity │ │ │ ├── Follow.java │ │ │ ├── SeckillVoucher.java │ │ │ ├── ShopType.java │ │ │ ├── User.java │ │ │ ├── BlogComments.java │ │ │ ├── VoucherOrder.java │ │ │ ├── UserInfo.java │ │ │ ├── Blog.java │ │ │ ├── Voucher.java │ │ │ └── Shop.java │ └── resources │ │ ├── mapper │ │ └── VoucherMapper.xml │ │ ├── seckill.lua │ │ ├── application.yaml │ │ └── db │ │ └── hmdp.sql └── test │ └── java │ └── com │ └── hmdp │ ├── NormalTest.java │ ├── RedissonTest.java │ └── HmDianPingApplicationTests.java ├── .gitignore └── pom.xml /src/main/java/com/hmdp/dto/UserDTO.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.dto; 2 | import lombok.Data; 3 | @Data 4 | public class UserDTO { 5 | private Long id; 6 | private String nickName; 7 | private String icon; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/dto/LoginFormDTO.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class LoginFormDTO { 7 | private String phone; 8 | private String code; 9 | private String password; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/utils/RedisData.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.utils; 2 | 3 | import lombok.Data; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | @Data 8 | public class RedisData { 9 | private LocalDateTime expireTime; 10 | private Object data; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/dto/ScrollResult.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.dto; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | @Data 8 | public class ScrollResult { 9 | private List list; 10 | private Long minTime; 11 | private Integer offset; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/mapper/BlogMapper.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.mapper; 2 | 3 | import com.hmdp.entity.Blog; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * Mapper 接口 9 | *

10 | * 11 | */ 12 | public interface BlogMapper extends BaseMapper { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/mapper/ShopMapper.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.mapper; 2 | 3 | import com.hmdp.entity.Shop; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * Mapper 接口 9 | *

10 | * 11 | */ 12 | public interface ShopMapper extends BaseMapper { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.mapper; 2 | 3 | import com.hmdp.entity.User; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * Mapper 接口 9 | *

10 | * 11 | */ 12 | public interface UserMapper extends BaseMapper { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/mapper/FollowMapper.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.mapper; 2 | 3 | import com.hmdp.entity.Follow; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * Mapper 接口 9 | *

10 | * 11 | */ 12 | public interface FollowMapper extends BaseMapper { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/mapper/UserInfoMapper.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.mapper; 2 | 3 | import com.hmdp.entity.UserInfo; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * Mapper 接口 9 | *

10 | * 11 | */ 12 | public interface UserInfoMapper extends BaseMapper { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/IUserInfoService.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service; 2 | 3 | import com.hmdp.entity.UserInfo; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | 6 | /** 7 | *

8 | * 服务类 9 | *

10 | * 11 | */ 12 | public interface IUserInfoService extends IService { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/mapper/BlogCommentsMapper.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.mapper; 2 | 3 | import com.hmdp.entity.BlogComments; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * Mapper 接口 9 | *

10 | * 11 | */ 12 | public interface BlogCommentsMapper extends BaseMapper { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/mapper/VoucherOrderMapper.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.mapper; 2 | 3 | import com.hmdp.entity.VoucherOrder; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * Mapper 接口 9 | *

10 | * 11 | */ 12 | public interface VoucherOrderMapper extends BaseMapper { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/utils/ILock.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.utils; 2 | 3 | public interface ILock { 4 | 5 | /** 6 | * 尝试获取锁 7 | * @param timeoutSec 锁持有的超时时间,过期后自动释放 8 | * @return true代表获取锁成功; false代表获取锁失败 9 | */ 10 | boolean tryLock(long timeoutSec); 11 | 12 | /** 13 | * 释放锁 14 | */ 15 | void unlock(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/mapper/SeckillVoucherMapper.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.mapper; 2 | 3 | import com.hmdp.entity.SeckillVoucher; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * 秒杀优惠券表,与优惠券是一对一关系 Mapper 接口 9 | *

10 | * 11 | */ 12 | public interface SeckillVoucherMapper extends BaseMapper { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/mapper/ShopTypeMapper.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.mapper; 2 | 3 | import com.hmdp.dto.Result; 4 | import com.hmdp.entity.ShopType; 5 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 6 | 7 | /** 8 | *

9 | * Mapper 接口 10 | *

11 | * 12 | */ 13 | public interface ShopTypeMapper extends BaseMapper { 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/ISeckillVoucherService.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service; 2 | 3 | import com.hmdp.entity.SeckillVoucher; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | 6 | /** 7 | *

8 | * 秒杀优惠券表,与优惠券是一对一关系 服务类 9 | *

10 | * 11 | */ 12 | public interface ISeckillVoucherService extends IService { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/utils/SystemConstants.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.utils; 2 | 3 | public class SystemConstants { 4 | public static final String IMAGE_UPLOAD_DIR = "D:\\nginx\\nginx-1.18.0\\html\\hmdp\\imgs\\"; 5 | public static final String USER_NICK_NAME_PREFIX = "user_"; 6 | public static final int DEFAULT_PAGE_SIZE = 5; 7 | public static final int MAX_PAGE_SIZE = 10; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/controller/BlogCommentsController.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.controller; 2 | 3 | 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | /** 9 | * 前端控制器 10 | */ 11 | @RestController 12 | @RequestMapping("/blog-comments") 13 | public class BlogCommentsController { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/IBlogCommentsService.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service; 2 | 3 | import com.hmdp.entity.BlogComments; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | 6 | /** 7 | *

8 | * 服务类 9 | *

10 | * 11 | * @author 虎哥 12 | * @since 2021-12-22 13 | */ 14 | public interface IBlogCommentsService extends IService { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/IShopTypeService.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service; 2 | 3 | import com.hmdp.dto.Result; 4 | import com.hmdp.entity.ShopType; 5 | import com.baomidou.mybatisplus.extension.service.IService; 6 | 7 | /** 8 | *

9 | * 服务类 10 | *

11 | * 12 | */ 13 | public interface IShopTypeService extends IService { 14 | Result queryshopTypeList(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/IVoucherService.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service; 2 | 3 | import com.hmdp.dto.Result; 4 | import com.hmdp.entity.Voucher; 5 | import com.baomidou.mybatisplus.extension.service.IService; 6 | 7 | /** 8 | *

9 | * 服务类 10 | *

11 | * 12 | */ 13 | public interface IVoucherService extends IService { 14 | 15 | Result queryVoucherOfShop(Long shopId); 16 | 17 | void addSeckillVoucher(Voucher voucher); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/mapper/VoucherMapper.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.hmdp.entity.Voucher; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | *

11 | * Mapper 接口 12 | *

13 | * 14 | */ 15 | public interface VoucherMapper extends BaseMapper { 16 | 17 | List queryVoucherOfShop(@Param("shopId") Long shopId); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/IVoucherOrderService.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service; 2 | 3 | import com.hmdp.dto.Result; 4 | import com.hmdp.entity.VoucherOrder; 5 | import com.baomidou.mybatisplus.extension.service.IService; 6 | 7 | /** 8 | *

9 | * 服务类 10 | *

11 | * 12 | */ 13 | public interface IVoucherOrderService extends IService { 14 | 15 | Result seckillVoucher(Long voucherId); 16 | 17 | void createVoucherOrder(VoucherOrder voucherOrder); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/utils/UserHolder.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.utils; 2 | 3 | import com.hmdp.dto.UserDTO; 4 | 5 | public class UserHolder { 6 | private static final ThreadLocal tl = new ThreadLocal<>(); 7 | 8 | public static void saveUser(UserDTO user){ 9 | tl.set(user); 10 | } 11 | 12 | public static UserDTO getUser(){ 13 | return tl.get(); 14 | } 15 | 16 | public static void removeUser(){ 17 | tl.remove(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/IFollowService.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service; 2 | 3 | import com.hmdp.dto.Result; 4 | import com.hmdp.entity.Follow; 5 | import com.baomidou.mybatisplus.extension.service.IService; 6 | 7 | /** 8 | *

9 | * 服务类 10 | *

11 | * 12 | */ 13 | public interface IFollowService extends IService { 14 | 15 | Result follow(Long followUserId, Boolean isFollow); 16 | 17 | Result isFollow(Long followUserId); 18 | 19 | Result followCommons(Long id); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/IShopService.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service; 2 | 3 | import com.hmdp.dto.Result; 4 | import com.hmdp.entity.Shop; 5 | import com.baomidou.mybatisplus.extension.service.IService; 6 | 7 | /** 8 | *

9 | * 服务类 10 | *

11 | * 12 | */ 13 | public interface IShopService extends IService { 14 | 15 | Result queryById(Long id); 16 | 17 | Result update(Shop shop); 18 | 19 | Result queryShopByType(Integer typeId, Integer current, Double x, Double y); 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/impl/UserInfoServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service.impl; 2 | 3 | import com.hmdp.entity.UserInfo; 4 | import com.hmdp.mapper.UserInfoMapper; 5 | import com.hmdp.service.IUserInfoService; 6 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | *

11 | * 服务实现类 12 | *

13 | * 14 | */ 15 | @Service 16 | public class UserInfoServiceImpl extends ServiceImpl implements IUserInfoService { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/impl/BlogCommentsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service.impl; 2 | 3 | import com.hmdp.entity.BlogComments; 4 | import com.hmdp.mapper.BlogCommentsMapper; 5 | import com.hmdp.service.IBlogCommentsService; 6 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | *

11 | * 服务实现类 12 | *

13 | * 14 | */ 15 | @Service 16 | public class BlogCommentsServiceImpl extends ServiceImpl implements IBlogCommentsService { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/config/WebExceptionAdvice.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.config; 2 | 3 | import com.hmdp.dto.Result; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.web.bind.annotation.ExceptionHandler; 6 | import org.springframework.web.bind.annotation.RestControllerAdvice; 7 | 8 | @Slf4j 9 | @RestControllerAdvice 10 | public class WebExceptionAdvice { 11 | 12 | @ExceptionHandler(RuntimeException.class) 13 | public Result handleRuntimeException(RuntimeException e) { 14 | log.error(e.toString(), e); 15 | return Result.fail("服务器异常"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/impl/SeckillVoucherServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service.impl; 2 | 3 | import com.hmdp.entity.SeckillVoucher; 4 | import com.hmdp.mapper.SeckillVoucherMapper; 5 | import com.hmdp.service.ISeckillVoucherService; 6 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | *

11 | * 秒杀优惠券表,与优惠券是一对一关系 服务实现类 12 | *

13 | * 14 | */ 15 | @Service 16 | public class SeckillVoucherServiceImpl extends ServiceImpl implements ISeckillVoucherService { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/HmDianPingApplication.java: -------------------------------------------------------------------------------- 1 | package com.hmdp; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 7 | 8 | @EnableAspectJAutoProxy(exposeProxy = true) 9 | @MapperScan("com.hmdp.mapper") 10 | @SpringBootApplication 11 | public class HmDianPingApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(HmDianPingApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/IBlogService.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service; 2 | 3 | import com.hmdp.dto.Result; 4 | import com.hmdp.entity.Blog; 5 | import com.baomidou.mybatisplus.extension.service.IService; 6 | 7 | /** 8 | *

9 | * 服务类 10 | *

11 | * 12 | */ 13 | public interface IBlogService extends IService { 14 | 15 | Result queryHotBlog(Integer current); 16 | 17 | Result queryBlogById(Long id); 18 | 19 | Result likeBlog(Long id); 20 | 21 | Result queryBlogLikes(Long id); 22 | 23 | Result saveBlog(Blog blog); 24 | 25 | Result queryBlogOfFollow(Long max, Integer offset); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/IUserService.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.hmdp.dto.LoginFormDTO; 5 | import com.hmdp.dto.Result; 6 | import com.hmdp.entity.User; 7 | 8 | import javax.servlet.http.HttpSession; 9 | 10 | /** 11 | *

12 | * 服务类 13 | *

14 | * 15 | */ 16 | public interface IUserService extends IService { 17 | 18 | Result sendCode(String phone, HttpSession session); 19 | 20 | Result login(LoginFormDTO loginForm, HttpSession session); 21 | 22 | Result sign(); 23 | 24 | Result signCount(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/utils/RegexPatterns.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.utils; 2 | 3 | 4 | public abstract class RegexPatterns { 5 | /** 6 | * 手机号正则 7 | */ 8 | public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$"; 9 | /** 10 | * 邮箱正则 11 | */ 12 | public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$"; 13 | /** 14 | * 密码正则。4~32位的字母、数字、下划线 15 | */ 16 | public static final String PASSWORD_REGEX = "^\\w{4,32}$"; 17 | /** 18 | * 验证码正则, 6位数字或字母 19 | */ 20 | public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\\d]{6}$"; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/config/RedissonConfig.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.config; 2 | 3 | import org.redisson.Redisson; 4 | import org.redisson.api.RedissonClient; 5 | import org.redisson.config.Config; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | public class RedissonConfig { 11 | 12 | @Bean 13 | public RedissonClient redissonClient(){ 14 | // 配置 15 | Config config = new Config(); 16 | config.useSingleServer().setAddress("redis://192.168.8.130:6379").setPassword("yangroot"); 17 | // 创建RedissonClient对象 18 | return Redisson.create(config); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/mapper/VoucherMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/resources/seckill.lua: -------------------------------------------------------------------------------- 1 | 2 | local voucherId = ARGV[1] 3 | local userId = ARGV[2] 4 | local orderId = ARGV[3] 5 | 6 | local stockKey = "seckill:stock:" .. voucherId 7 | local orderKey = "seckill:order:" .. voucherId 8 | 9 | -- 判断库存是否充足(不足,返回 1) 10 | if (tonumber(redis.call('GET', stockKey)) <= 0) then 11 | return 1; 12 | end; 13 | 14 | -- 判断用户是否下单(重复下单,返回 2) 15 | if (redis.call('SISMEMBER', orderKey, userId) == 1) then 16 | return 2; 17 | end; 18 | 19 | -- 下单成功:扣减库存、保存用户。 20 | redis.call('INCRBY', stockKey, -1); 21 | redis.call('SADD', orderKey, userId); 22 | -- 发送消息到 stream.orders 队列中(*:消息的唯一ID 由 Redis 自动生成):XADD stream.orders * key field ... 23 | redis.call('XADD', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId); 24 | return 0; -------------------------------------------------------------------------------- /src/main/java/com/hmdp/config/MybatisConfig.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.config; 2 | 3 | import com.baomidou.mybatisplus.annotation.DbType; 4 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 5 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | public class MybatisConfig { 11 | @Bean 12 | public MybatisPlusInterceptor mybatisPlusInterceptor() { 13 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 14 | interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 15 | return interceptor; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/controller/ShopTypeController.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.controller; 2 | 3 | 4 | import com.hmdp.dto.Result; 5 | import com.hmdp.entity.ShopType; 6 | import com.hmdp.service.IShopTypeService; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import javax.annotation.Resource; 12 | import java.util.List; 13 | 14 | /** 15 | *

16 | * 前端控制器 17 | *

18 | * 19 | * @author 虎哥 20 | */ 21 | @RestController 22 | @RequestMapping("/shop-type") 23 | public class ShopTypeController { 24 | @Resource 25 | private IShopTypeService typeService; 26 | 27 | @GetMapping("list") 28 | public Result queryTypeList() { 29 | return typeService.queryshopTypeList(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/dto/Result.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.List; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class Result { 13 | private Boolean success; 14 | private String errorMsg; 15 | private Object data; 16 | private Long total; 17 | 18 | public static Result ok(){ 19 | return new Result(true, null, null, null); 20 | } 21 | public static Result ok(Object data){ 22 | return new Result(true, null, data, null); 23 | } 24 | public static Result ok(List data, Long total){ 25 | return new Result(true, null, data, total); 26 | } 27 | public static Result fail(String errorMsg){ 28 | return new Result(false, errorMsg, null, null); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | spring: 4 | application: 5 | name: hmdp 6 | datasource: 7 | driver-class-name: com.mysql.cj.jdbc.Driver 8 | # url: jdbc:mysql://127.0.0.1:3306/hmdp?useSSL=false&serverTimezone=UTC 9 | username: root 10 | password: 123456 11 | url: jdbc:mysql://localhost:3306/hmdp?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true 12 | redis: 13 | host: 192.168.8.130 14 | port: 6379 15 | password: yangroot 16 | lettuce: 17 | pool: 18 | max-active: 10 19 | max-idle: 10 20 | min-idle: 1 21 | time-between-eviction-runs: 10s 22 | timeout: 2000ms 23 | jackson: 24 | default-property-inclusion: non_null # JSON处理时忽略非空字段 25 | mybatis-plus: 26 | type-aliases-package: com.hmdp.entity # 别名扫描包 27 | logging: 28 | level: 29 | com.hmdp: debug 30 | pattern: 31 | dateformat: mm:ss.SSS 32 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/entity/Follow.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableName; 4 | import com.baomidou.mybatisplus.annotation.IdType; 5 | import java.time.LocalDateTime; 6 | import com.baomidou.mybatisplus.annotation.TableId; 7 | import java.io.Serializable; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | import lombok.experimental.Accessors; 11 | 12 | /** 13 | * 14 | */ 15 | @Data 16 | @EqualsAndHashCode(callSuper = false) 17 | @Accessors(chain = true) 18 | @TableName("tb_follow") 19 | public class Follow implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | /** 24 | * 主键 25 | */ 26 | @TableId(value = "id", type = IdType.AUTO) 27 | private Long id; 28 | 29 | /** 30 | * 用户id 31 | */ 32 | private Long userId; 33 | 34 | /** 35 | * 关联的用户id 36 | */ 37 | private Long followUserId; 38 | 39 | /** 40 | * 创建时间 41 | */ 42 | private LocalDateTime createTime; 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/utils/LoginInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.utils; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import com.hmdp.dto.UserDTO; 6 | import io.netty.util.internal.StringUtil; 7 | import org.springframework.data.redis.core.StringRedisTemplate; 8 | import org.springframework.web.servlet.HandlerInterceptor; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.util.Map; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | public class LoginInterceptor implements HandlerInterceptor { 16 | 17 | @Override 18 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 19 | // 1.判断是否需要拦截(ThreadLocal中是否有用户) 20 | if (UserHolder.getUser() == null) { 21 | // 没有,需要拦截,设置状态码 22 | response.setStatus(401); 23 | // 拦截 24 | return false; 25 | } 26 | // 有用户,则放行 27 | return true; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/utils/RedisConstants.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.utils; 2 | 3 | public class RedisConstants { 4 | public static final String LOGIN_CODE_KEY = "login:code:"; 5 | public static final Long LOGIN_CODE_TTL = 2L; 6 | public static final String LOGIN_USER_KEY = "login:token:"; 7 | public static final Long LOGIN_USER_TTL = 36000L; 8 | 9 | public static final Long CACHE_NULL_TTL = 2L; 10 | 11 | public static final Long CACHE_SHOP_TTL = 30L; 12 | public static final String CACHE_SHOP_KEY = "cache:shop:"; 13 | public static final String CACHE_SHOPTYPE_KEY = "cache:shoptype:"; 14 | 15 | public static final String LOCK_SHOP_KEY = "lock:shop:"; 16 | public static final Long LOCK_SHOP_TTL = 10L; 17 | 18 | public static final String SECKILL_STOCK_KEY = "seckill:stock:"; 19 | public static final String BLOG_LIKED_KEY = "blog:liked:"; 20 | public static final String FEED_KEY = "feed:"; 21 | public static final String SHOP_GEO_KEY = "shop:geo:"; 22 | public static final String USER_SIGN_KEY = "sign:"; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/controller/FollowController.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.controller; 2 | 3 | 4 | import com.hmdp.dto.Result; 5 | import com.hmdp.service.IFollowService; 6 | import org.springframework.web.bind.annotation.*; 7 | 8 | import javax.annotation.Resource; 9 | 10 | /** 11 | *

12 | * 前端控制器 13 | *

14 | * 15 | */ 16 | @RestController 17 | @RequestMapping("/follow") 18 | public class FollowController { 19 | 20 | @Resource 21 | private IFollowService followService; 22 | 23 | @PutMapping("/{id}/{isFollow}") 24 | public Result follow(@PathVariable("id") Long followUserId, @PathVariable("isFollow") Boolean isFollow) { 25 | return followService.follow(followUserId, isFollow); 26 | } 27 | 28 | @GetMapping("/or/not/{id}") 29 | public Result isFollow(@PathVariable("id") Long followUserId) { 30 | return followService.isFollow(followUserId); 31 | } 32 | 33 | @GetMapping("/common/{id}") 34 | public Result followCommons(@PathVariable("id") Long id){ 35 | return followService.followCommons(id); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/utils/RegexUtils.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.utils; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | public class RegexUtils { 5 | /** 6 | * 是否是无效手机格式 7 | * @param phone 要校验的手机号 8 | * @return true:符合,false:不符合 9 | */ 10 | public static boolean isPhoneInvalid(String phone){ 11 | return mismatch(phone, RegexPatterns.PHONE_REGEX); 12 | } 13 | /** 14 | * 是否是无效邮箱格式 15 | * @param email 要校验的邮箱 16 | * @return true:符合,false:不符合 17 | */ 18 | public static boolean isEmailInvalid(String email){ 19 | return mismatch(email, RegexPatterns.EMAIL_REGEX); 20 | } 21 | 22 | /** 23 | * 是否是无效验证码格式 24 | * @param code 要校验的验证码 25 | * @return true:符合,false:不符合 26 | */ 27 | public static boolean isCodeInvalid(String code){ 28 | return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX); 29 | } 30 | 31 | // 校验是否不符合正则格式 32 | private static boolean mismatch(String str, String regex){ 33 | if (StrUtil.isBlank(str)) { 34 | return true; 35 | } 36 | return !str.matches(regex); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/controller/VoucherOrderController.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.controller; 2 | 3 | 4 | import com.hmdp.dto.Result; 5 | import com.hmdp.entity.VoucherOrder; 6 | import com.hmdp.service.IVoucherOrderService; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import javax.annotation.Resource; 15 | 16 | /** 17 | *

18 | * 前端控制器 19 | *

20 | */ 21 | @Slf4j 22 | @RestController 23 | @RequestMapping("/voucher-order") 24 | public class VoucherOrderController { 25 | 26 | @Resource 27 | private IVoucherOrderService voucherOrderService; 28 | 29 | @PostMapping("seckill/{id}") 30 | public Result seckillVoucher(@PathVariable("id") Long voucherId) { 31 | log.info(voucherId.toString()); 32 | Result result = voucherOrderService.seckillVoucher(voucherId); 33 | System.out.println(result.toString()); 34 | 35 | return voucherOrderService.seckillVoucher(voucherId); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/utils/PasswordEncoder.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.utils; 2 | 3 | 4 | import cn.hutool.core.util.RandomUtil; 5 | import org.springframework.util.DigestUtils; 6 | 7 | import java.nio.charset.StandardCharsets; 8 | 9 | public class PasswordEncoder { 10 | 11 | public static String encode(String password) { 12 | // 生成盐 13 | String salt = RandomUtil.randomString(20); 14 | // 加密 15 | return encode(password,salt); 16 | } 17 | private static String encode(String password, String salt) { 18 | // 加密 19 | return salt + "@" + DigestUtils.md5DigestAsHex((password + salt).getBytes(StandardCharsets.UTF_8)); 20 | } 21 | public static Boolean matches(String encodedPassword, String rawPassword) { 22 | if (encodedPassword == null || rawPassword == null) { 23 | return false; 24 | } 25 | if(!encodedPassword.contains("@")){ 26 | throw new RuntimeException("密码格式不正确!"); 27 | } 28 | String[] arr = encodedPassword.split("@"); 29 | // 获取盐 30 | String salt = arr[0]; 31 | // 比较 32 | return encodedPassword.equals(encode(rawPassword, salt)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/hmdp/NormalTest.java: -------------------------------------------------------------------------------- 1 | package com.hmdp; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | public class NormalTest { 6 | 7 | @Test 8 | void testBitMap() { 9 | int i = 0b1110111111111111111111111; 10 | 11 | long t1 = System.nanoTime(); 12 | int count = 0; 13 | while (true){ 14 | if ((i & 1) == 0){ 15 | break; 16 | }else{ 17 | count++; 18 | } 19 | i >>>= 1; 20 | } 21 | long t2 = System.nanoTime(); 22 | System.out.println("time1 = " + (t2 - t1)); 23 | System.out.println("count = " + count); 24 | 25 | i = 0b1110111111111111111111111; 26 | long t3 = System.nanoTime(); 27 | int count2 = 0; 28 | while (true) { 29 | if(i >>> 1 << 1 == i){ 30 | // 未签到,结束 31 | break; 32 | }else{ 33 | // 说明签到了 34 | count2++; 35 | } 36 | 37 | i >>>= 1; 38 | } 39 | long t4 = System.nanoTime(); 40 | System.out.println("time2 = " + (t4 - t3)); 41 | System.out.println("count2 = " + count2); 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/com/hmdp/entity/SeckillVoucher.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | import java.time.LocalDateTime; 12 | 13 | /** 14 | *

15 | * 秒杀优惠券表,与优惠券是一对一关系 16 | *

16 | * 17 | *

18 | * 19 | */ 20 | @Data 21 | @EqualsAndHashCode(callSuper = false) 22 | @Accessors(chain = true) 23 | @TableName("tb_shop_type") 24 | public class ShopType implements Serializable { 25 | 26 | private static final long serialVersionUID = 1L; 27 | 28 | /** 29 | * 主键 30 | */ 31 | @TableId(value = "id", type = IdType.AUTO) 32 | private Long id; 33 | 34 | /** 35 | * 类型名称 36 | */ 37 | private String name; 38 | 39 | /** 40 | * 图标 41 | */ 42 | private String icon; 43 | 44 | /** 45 | * 顺序 46 | */ 47 | private Integer sort; 48 | 49 | /** 50 | * 创建时间 51 | */ 52 | @JsonIgnore 53 | private LocalDateTime createTime; 54 | 55 | /** 56 | * 更新时间 57 | */ 58 | @JsonIgnore 59 | private LocalDateTime updateTime; 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.experimental.Accessors; 10 | 11 | import java.io.Serializable; 12 | import java.time.LocalDateTime; 13 | 14 | /** 15 | *

16 | * 17 | *

18 | * 19 | */ 20 | @Data 21 | @EqualsAndHashCode(callSuper = false) 22 | @Accessors(chain = true) 23 | @TableName("tb_user") 24 | public class User implements Serializable { 25 | 26 | private static final long serialVersionUID = 1L; 27 | /** 28 | * 主键 29 | */ 30 | @TableId(value = "id", type = IdType.AUTO) 31 | private Long id; 32 | /** 33 | * 手机号码 34 | */ 35 | private String phone; 36 | /** 37 | * 密码,加密存储 38 | */ 39 | private String password; 40 | /** 41 | * 昵称,默认是随机字符 42 | */ 43 | private String nickName; 44 | /** 45 | * 用户头像 46 | */ 47 | private String icon = ""; 48 | /** 49 | * 创建时间 50 | */ 51 | private LocalDateTime createTime; 52 | /** 53 | * 更新时间 54 | */ 55 | private LocalDateTime updateTime; 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/config/MvcConfig.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.config; 2 | 3 | import com.hmdp.utils.LoginInterceptor; 4 | import com.hmdp.utils.RefreshTokenInterceptor; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.redis.core.StringRedisTemplate; 7 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 9 | 10 | import javax.annotation.Resource; 11 | 12 | @Configuration 13 | public class MvcConfig implements WebMvcConfigurer { 14 | 15 | @Resource 16 | private StringRedisTemplate stringRedisTemplate; 17 | 18 | @Override 19 | public void addInterceptors(InterceptorRegistry registry) { 20 | // 登录拦截器 21 | registry.addInterceptor(new LoginInterceptor()) 22 | .excludePathPatterns( 23 | "/shop/**", 24 | "/voucher/**", 25 | "/shop-type/**", 26 | "/upload/**", 27 | "/blog/hot", 28 | "/user/code", 29 | "/user/login" 30 | ).order(1); 31 | // token刷新的拦截器 32 | registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/utils/RedisIdWorker.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.utils; 2 | 3 | import org.springframework.data.redis.core.StringRedisTemplate; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.time.LocalDateTime; 7 | import java.time.ZoneOffset; 8 | import java.time.format.DateTimeFormatter; 9 | 10 | @Component 11 | public class RedisIdWorker { 12 | /** 13 | * 开始时间戳 14 | */ 15 | private static final long BEGIN_TIMESTAMP = 1640995200L; 16 | /** 17 | * 序列号的位数 18 | */ 19 | private static final int COUNT_BITS = 32; 20 | 21 | private StringRedisTemplate stringRedisTemplate; 22 | 23 | public RedisIdWorker(StringRedisTemplate stringRedisTemplate) { 24 | this.stringRedisTemplate = stringRedisTemplate; 25 | } 26 | 27 | public long nextId(String keyPrefix) { 28 | // 1.生成时间戳 29 | LocalDateTime now = LocalDateTime.now(); 30 | long nowSecond = now.toEpochSecond(ZoneOffset.UTC); 31 | long timestamp = nowSecond - BEGIN_TIMESTAMP; 32 | 33 | // 2.生成序列号 34 | // 2.1.获取当前日期,精确到天 35 | String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd")); 36 | // 2.2.自增长 37 | long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date); 38 | 39 | // 3.拼接并返回 40 | return timestamp << COUNT_BITS | count; 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/controller/VoucherController.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.controller; 2 | 3 | 4 | import com.hmdp.dto.Result; 5 | import com.hmdp.entity.Voucher; 6 | import com.hmdp.service.IVoucherService; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | import javax.annotation.Resource; 10 | 11 | /** 12 | *

13 | * 前端控制器 14 | *

15 | * 16 | * @author 虎哥 17 | */ 18 | @RestController 19 | @RequestMapping("/voucher") 20 | public class VoucherController { 21 | 22 | @Resource 23 | private IVoucherService voucherService; 24 | 25 | /** 26 | * 新增秒杀券 27 | * @param voucher 优惠券信息,包含秒杀信息 28 | * @return 优惠券id 29 | */ 30 | @PostMapping("seckill") 31 | public Result addSeckillVoucher(@RequestBody Voucher voucher) { 32 | voucherService.addSeckillVoucher(voucher); 33 | return Result.ok(voucher.getId()); 34 | } 35 | 36 | /** 37 | * 新增普通券 38 | * @param voucher 优惠券信息 39 | * @return 优惠券id 40 | */ 41 | @PostMapping 42 | public Result addVoucher(@RequestBody Voucher voucher) { 43 | voucherService.save(voucher); 44 | return Result.ok(voucher.getId()); 45 | } 46 | 47 | 48 | /** 49 | * 查询店铺的优惠券列表 50 | * @param shopId 店铺id 51 | * @return 优惠券列表 52 | */ 53 | @GetMapping("/list/{shopId}") 54 | public Result queryVoucherOfShop(@PathVariable("shopId") Long shopId) { 55 | return voucherService.queryVoucherOfShop(shopId); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/entity/BlogComments.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableName; 4 | import com.baomidou.mybatisplus.annotation.IdType; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import java.time.LocalDateTime; 7 | import java.io.Serializable; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | import lombok.experimental.Accessors; 11 | 12 | /** 13 | * 14 | */ 15 | @Data 16 | @EqualsAndHashCode(callSuper = false) 17 | @Accessors(chain = true) 18 | @TableName("tb_blog_comments") 19 | public class BlogComments implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | /** 24 | * 主键 25 | */ 26 | @TableId(value = "id", type = IdType.AUTO) 27 | private Long id; 28 | 29 | /** 30 | * 用户id 31 | */ 32 | private Long userId; 33 | 34 | /** 35 | * 探店id 36 | */ 37 | private Long blogId; 38 | 39 | /** 40 | * 关联的1级评论id,如果是一级评论,则值为0 41 | */ 42 | private Long parentId; 43 | 44 | /** 45 | * 回复的评论id 46 | */ 47 | private Long answerId; 48 | 49 | /** 50 | * 回复的内容 51 | */ 52 | private String content; 53 | 54 | /** 55 | * 点赞数 56 | */ 57 | private Integer liked; 58 | 59 | /** 60 | * 状态,0:正常,1:被举报,2:禁止查看 61 | */ 62 | private Boolean status; 63 | 64 | /** 65 | * 创建时间 66 | */ 67 | private LocalDateTime createTime; 68 | 69 | /** 70 | * 更新时间 71 | */ 72 | private LocalDateTime updateTime; 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/com/hmdp/RedissonTest.java: -------------------------------------------------------------------------------- 1 | package com.hmdp; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.redisson.api.RLock; 7 | import org.redisson.api.RedissonClient; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | import javax.annotation.Resource; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | @Slf4j 14 | @SpringBootTest 15 | class RedissonTest { 16 | 17 | @Resource 18 | private RedissonClient redissonClient; 19 | 20 | private RLock lock; 21 | 22 | @BeforeEach 23 | void setUp() { 24 | lock = redissonClient.getLock("order"); 25 | } 26 | 27 | @Test 28 | void method1() throws InterruptedException { 29 | // 尝试获取锁 30 | boolean isLock = lock.tryLock(1L, TimeUnit.SECONDS); 31 | if (!isLock) { 32 | log.error("获取锁失败 .... 1"); 33 | return; 34 | } 35 | try { 36 | log.info("获取锁成功 .... 1"); 37 | method2(); 38 | log.info("开始执行业务 ... 1"); 39 | } finally { 40 | log.warn("准备释放锁 .... 1"); 41 | lock.unlock(); 42 | } 43 | } 44 | void method2() { 45 | // 尝试获取锁 46 | boolean isLock = lock.tryLock(); 47 | if (!isLock) { 48 | log.error("获取锁失败 .... 2"); 49 | return; 50 | } 51 | try { 52 | log.info("获取锁成功 .... 2"); 53 | log.info("开始执行业务 ... 2"); 54 | } finally { 55 | log.warn("准备释放锁 .... 2"); 56 | lock.unlock(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/entity/VoucherOrder.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | import java.time.LocalDateTime; 12 | 13 | /** 14 | *

15 | * 16 | *

17 | */ 18 | @Data 19 | @EqualsAndHashCode(callSuper = false) 20 | @Accessors(chain = true) 21 | @TableName("tb_voucher_order") 22 | public class VoucherOrder implements Serializable { 23 | 24 | private static final long serialVersionUID = 1L; 25 | 26 | /** 27 | * 主键 28 | */ 29 | @TableId(value = "id", type = IdType.INPUT) 30 | private Long id; 31 | 32 | /** 33 | * 下单的用户id 34 | */ 35 | private Long userId; 36 | 37 | /** 38 | * 购买的代金券id 39 | */ 40 | private Long voucherId; 41 | 42 | /** 43 | * 支付方式 1:余额支付;2:支付宝;3:微信 44 | */ 45 | private Integer payType; 46 | 47 | /** 48 | * 订单状态,1:未支付;2:已支付;3:已核销;4:已取消;5:退款中;6:已退款 49 | */ 50 | private Integer status; 51 | 52 | /** 53 | * 下单时间 54 | */ 55 | private LocalDateTime createTime; 56 | 57 | /** 58 | * 支付时间 59 | */ 60 | private LocalDateTime payTime; 61 | 62 | /** 63 | * 核销时间 64 | */ 65 | private LocalDateTime useTime; 66 | 67 | /** 68 | * 退款时间 69 | */ 70 | private LocalDateTime refundTime; 71 | 72 | /** 73 | * 更新时间 74 | */ 75 | private LocalDateTime updateTime; 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/entity/UserInfo.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | import java.time.LocalDate; 12 | import java.time.LocalDateTime; 13 | 14 | /** 15 | *

16 | * 17 | *

18 | * 19 | */ 20 | @Data 21 | @EqualsAndHashCode(callSuper = false) 22 | @Accessors(chain = true) 23 | @TableName("tb_user_info") 24 | public class UserInfo implements Serializable { 25 | 26 | private static final long serialVersionUID = 1L; 27 | 28 | /** 29 | * 主键,用户id 30 | */ 31 | @TableId(value = "user_id", type = IdType.AUTO) 32 | private Long userId; 33 | 34 | /** 35 | * 城市名称 36 | */ 37 | private String city; 38 | 39 | /** 40 | * 个人介绍,不要超过128个字符 41 | */ 42 | private String introduce; 43 | 44 | /** 45 | * 粉丝数量 46 | */ 47 | private Integer fans; 48 | 49 | /** 50 | * 关注的人的数量 51 | */ 52 | private Integer followee; 53 | 54 | /** 55 | * 性别,0:男,1:女 56 | */ 57 | private Boolean gender; 58 | 59 | /** 60 | * 生日 61 | */ 62 | private LocalDate birthday; 63 | 64 | /** 65 | * 积分 66 | */ 67 | private Integer credits; 68 | 69 | /** 70 | * 会员级别,0~9级,0代表未开通会员 71 | */ 72 | private Boolean level; 73 | 74 | /** 75 | * 创建时间 76 | */ 77 | private LocalDateTime createTime; 78 | 79 | /** 80 | * 更新时间 81 | */ 82 | private LocalDateTime updateTime; 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/entity/Blog.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.experimental.Accessors; 10 | 11 | import java.io.Serializable; 12 | import java.time.LocalDateTime; 13 | 14 | @Data 15 | @EqualsAndHashCode(callSuper = false) 16 | @Accessors(chain = true) 17 | @TableName("tb_blog") 18 | public class Blog implements Serializable { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | /** 23 | * 主键 24 | */ 25 | @TableId(value = "id", type = IdType.AUTO) 26 | private Long id; 27 | /** 28 | * 商户id 29 | */ 30 | private Long shopId; 31 | /** 32 | * 用户id 33 | */ 34 | private Long userId; 35 | /** 36 | * 用户图标 37 | */ 38 | @TableField(exist = false) 39 | private String icon; 40 | /** 41 | * 用户姓名 42 | */ 43 | @TableField(exist = false) 44 | private String name; 45 | /** 46 | * 是否点赞过了 47 | */ 48 | @TableField(exist = false) 49 | private Boolean isLike; 50 | 51 | /** 52 | * 标题 53 | */ 54 | private String title; 55 | 56 | /** 57 | * 探店的照片,最多9张,多张以","隔开 58 | */ 59 | private String images; 60 | 61 | /** 62 | * 探店的文字描述 63 | */ 64 | private String content; 65 | 66 | /** 67 | * 点赞数量 68 | */ 69 | private Integer liked; 70 | 71 | /** 72 | * 评论数量 73 | */ 74 | private Integer comments; 75 | 76 | /** 77 | * 创建时间 78 | */ 79 | private LocalDateTime createTime; 80 | 81 | /** 82 | * 更新时间 83 | */ 84 | private LocalDateTime updateTime; 85 | 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/utils/RefreshTokenInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.utils; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import com.hmdp.dto.UserDTO; 6 | import org.springframework.data.redis.core.StringRedisTemplate; 7 | import org.springframework.web.servlet.HandlerInterceptor; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.util.Map; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY; 15 | import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL; 16 | 17 | public class RefreshTokenInterceptor implements HandlerInterceptor { 18 | 19 | private StringRedisTemplate stringRedisTemplate; 20 | 21 | public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) { 22 | this.stringRedisTemplate = stringRedisTemplate; 23 | } 24 | 25 | @Override 26 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 27 | // 1.获取请求头中的token 28 | String token = request.getHeader("authorization"); 29 | if (StrUtil.isBlank(token)) { 30 | return true; 31 | } 32 | // 2.基于TOKEN获取redis中的用户 33 | String key = LOGIN_USER_KEY + token; 34 | Map userMap = stringRedisTemplate.opsForHash().entries(key); 35 | // 3.判断用户是否存在 36 | if (userMap.isEmpty()) { 37 | return true; 38 | } 39 | // 5.将查询到的hash数据转为UserDTO 40 | UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false); 41 | // 6.存在,保存用户信息到 ThreadLocal 42 | UserHolder.saveUser(userDTO); 43 | // 7.刷新token有效期 44 | stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES); 45 | // 8.放行 46 | return true; 47 | } 48 | 49 | @Override 50 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 51 | // 移除用户 52 | UserHolder.removeUser(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/entity/Voucher.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.experimental.Accessors; 10 | 11 | import java.io.Serializable; 12 | import java.time.LocalDateTime; 13 | 14 | /** 15 | *

16 | * 17 | *

18 | * 19 | */ 20 | @Data 21 | @EqualsAndHashCode(callSuper = false) 22 | @Accessors(chain = true) 23 | @TableName("tb_voucher") 24 | public class Voucher implements Serializable { 25 | 26 | private static final long serialVersionUID = 1L; 27 | 28 | /** 29 | * 主键 30 | */ 31 | @TableId(value = "id", type = IdType.AUTO) 32 | private Long id; 33 | 34 | /** 35 | * 商铺id 36 | */ 37 | private Long shopId; 38 | 39 | /** 40 | * 代金券标题 41 | */ 42 | private String title; 43 | 44 | /** 45 | * 副标题 46 | */ 47 | private String subTitle; 48 | 49 | /** 50 | * 使用规则 51 | */ 52 | private String rules; 53 | 54 | /** 55 | * 支付金额 56 | */ 57 | private Long payValue; 58 | 59 | /** 60 | * 抵扣金额 61 | */ 62 | private Long actualValue; 63 | 64 | /** 65 | * 优惠券类型 66 | */ 67 | private Integer type; 68 | 69 | /** 70 | * 优惠券类型 71 | */ 72 | private Integer status; 73 | /** 74 | * 库存 75 | */ 76 | @TableField(exist = false) 77 | private Integer stock; 78 | 79 | /** 80 | * 生效时间 81 | */ 82 | @TableField(exist = false) 83 | private LocalDateTime beginTime; 84 | 85 | /** 86 | * 失效时间 87 | */ 88 | @TableField(exist = false) 89 | private LocalDateTime endTime; 90 | 91 | /** 92 | * 创建时间 93 | */ 94 | private LocalDateTime createTime; 95 | 96 | 97 | /** 98 | * 更新时间 99 | */ 100 | private LocalDateTime updateTime; 101 | 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/impl/VoucherServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.hmdp.dto.Result; 5 | import com.hmdp.entity.SeckillVoucher; 6 | import com.hmdp.entity.Voucher; 7 | import com.hmdp.mapper.VoucherMapper; 8 | import com.hmdp.service.ISeckillVoucherService; 9 | import com.hmdp.service.IVoucherService; 10 | import org.springframework.data.redis.core.StringRedisTemplate; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.transaction.annotation.Transactional; 13 | 14 | import javax.annotation.Resource; 15 | import java.util.List; 16 | 17 | import static com.hmdp.utils.RedisConstants.SECKILL_STOCK_KEY; 18 | 19 | /** 20 | *

21 | * 服务实现类 22 | *

23 | * 24 | */ 25 | @Service 26 | public class VoucherServiceImpl extends ServiceImpl implements IVoucherService { 27 | 28 | @Resource 29 | private ISeckillVoucherService seckillVoucherService; 30 | @Resource 31 | private StringRedisTemplate stringRedisTemplate; 32 | 33 | @Override 34 | public Result queryVoucherOfShop(Long shopId) { 35 | // 查询优惠券信息 36 | List vouchers = getBaseMapper().queryVoucherOfShop(shopId); 37 | System.out.println("查询到的优惠券"+vouchers.toString()); 38 | // 返回结果 39 | return Result.ok(vouchers); 40 | } 41 | 42 | @Override 43 | @Transactional 44 | public void addSeckillVoucher(Voucher voucher) { 45 | // 保存优惠券 46 | save(voucher); 47 | // 保存秒杀信息 48 | SeckillVoucher seckillVoucher = new SeckillVoucher(); 49 | seckillVoucher.setVoucherId(voucher.getId()); 50 | seckillVoucher.setStock(voucher.getStock()); 51 | seckillVoucher.setBeginTime(voucher.getBeginTime()); 52 | seckillVoucher.setEndTime(voucher.getEndTime()); 53 | seckillVoucherService.save(seckillVoucher); 54 | System.out.println("添加的券"+seckillVoucher.toString()); 55 | // 保存秒杀库存到Redis中 56 | stringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEY + voucher.getId(), voucher.getStock().toString()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/entity/Shop.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.experimental.Accessors; 10 | 11 | import java.io.Serializable; 12 | import java.time.LocalDateTime; 13 | 14 | /** 15 | *

16 | * 17 | *

18 | * 19 | */ 20 | @Data 21 | @EqualsAndHashCode(callSuper = false) 22 | @Accessors(chain = true) 23 | @TableName("tb_shop") 24 | public class Shop implements Serializable { 25 | 26 | private static final long serialVersionUID = 1L; 27 | 28 | /** 29 | * 主键 30 | */ 31 | @TableId(value = "id", type = IdType.AUTO) 32 | private Long id; 33 | 34 | /** 35 | * 商铺名称 36 | */ 37 | private String name; 38 | 39 | /** 40 | * 商铺类型的id 41 | */ 42 | private Long typeId; 43 | 44 | /** 45 | * 商铺图片,多个图片以','隔开 46 | */ 47 | private String images; 48 | 49 | /** 50 | * 商圈,例如陆家嘴 51 | */ 52 | private String area; 53 | 54 | /** 55 | * 地址 56 | */ 57 | private String address; 58 | 59 | /** 60 | * 经度 61 | */ 62 | private Double x; 63 | 64 | /** 65 | * 维度 66 | */ 67 | private Double y; 68 | 69 | /** 70 | * 均价,取整数 71 | */ 72 | private Long avgPrice; 73 | 74 | /** 75 | * 销量 76 | */ 77 | private Integer sold; 78 | 79 | /** 80 | * 评论数量 81 | */ 82 | private Integer comments; 83 | 84 | /** 85 | * 评分,1~5分,乘10保存,避免小数 86 | */ 87 | private Integer score; 88 | 89 | /** 90 | * 营业时间,例如 10:00-22:00 91 | */ 92 | private String openHours; 93 | 94 | /** 95 | * 创建时间 96 | */ 97 | private LocalDateTime createTime; 98 | 99 | /** 100 | * 更新时间 101 | */ 102 | private LocalDateTime updateTime; 103 | 104 | 105 | @TableField(exist = false) 106 | private Double distance; 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/utils/SimpleRedisLock.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.utils; 2 | 3 | import cn.hutool.core.lang.UUID; 4 | import org.springframework.core.io.ClassPathResource; 5 | import org.springframework.data.redis.core.StringRedisTemplate; 6 | import org.springframework.data.redis.core.script.DefaultRedisScript; 7 | 8 | import java.util.Collections; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | public class SimpleRedisLock implements ILock { 12 | 13 | private String name; 14 | private StringRedisTemplate stringRedisTemplate; 15 | 16 | public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) { 17 | this.name = name; 18 | this.stringRedisTemplate = stringRedisTemplate; 19 | } 20 | 21 | private static final String KEY_PREFIX = "lock:"; 22 | private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-"; 23 | private static final DefaultRedisScript UNLOCK_SCRIPT; 24 | static { 25 | UNLOCK_SCRIPT = new DefaultRedisScript<>(); 26 | UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua")); 27 | UNLOCK_SCRIPT.setResultType(Long.class); 28 | } 29 | 30 | @Override 31 | public boolean tryLock(long timeoutSec) { 32 | // 获取线程标示 33 | String threadId = ID_PREFIX + Thread.currentThread().getId(); 34 | // 获取锁 35 | Boolean success = stringRedisTemplate.opsForValue() 36 | .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS); 37 | return Boolean.TRUE.equals(success); 38 | } 39 | 40 | @Override 41 | public void unlock() { 42 | // 调用lua脚本 43 | stringRedisTemplate.execute( 44 | UNLOCK_SCRIPT, 45 | Collections.singletonList(KEY_PREFIX + name), 46 | ID_PREFIX + Thread.currentThread().getId()); 47 | } 48 | /*@Override 49 | public void unlock() { 50 | // 获取线程标示 51 | String threadId = ID_PREFIX + Thread.currentThread().getId(); 52 | // 获取锁中的标示 53 | String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name); 54 | // 判断标示是否一致 55 | if(threadId.equals(id)) { 56 | // 释放锁 57 | stringRedisTemplate.delete(KEY_PREFIX + name); 58 | } 59 | }*/ 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/controller/UploadController.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.controller; 2 | 3 | import cn.hutool.core.io.FileUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import com.hmdp.dto.Result; 6 | import com.hmdp.utils.SystemConstants; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.web.bind.annotation.*; 9 | import org.springframework.web.multipart.MultipartFile; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.util.UUID; 14 | 15 | @Slf4j 16 | @RestController 17 | @RequestMapping("upload") 18 | public class UploadController { 19 | 20 | @PostMapping("/blog") 21 | public Result uploadImage(@RequestParam("file") MultipartFile image) { 22 | try { 23 | // 获取原始文件名称 24 | String originalFilename = image.getOriginalFilename(); 25 | // 生成新文件名 26 | String fileName = createNewFileName(originalFilename); 27 | // 保存文件 28 | image.transferTo(new File(SystemConstants.IMAGE_UPLOAD_DIR, fileName)); 29 | // 返回结果 30 | log.debug("文件上传成功,{}", fileName); 31 | return Result.ok(fileName); 32 | } catch (IOException e) { 33 | throw new RuntimeException("文件上传失败", e); 34 | } 35 | } 36 | 37 | @GetMapping("/blog/delete") 38 | public Result deleteBlogImg(@RequestParam("name") String filename) { 39 | File file = new File(SystemConstants.IMAGE_UPLOAD_DIR, filename); 40 | if (file.isDirectory()) { 41 | return Result.fail("错误的文件名称"); 42 | } 43 | FileUtil.del(file); 44 | return Result.ok(); 45 | } 46 | 47 | private String createNewFileName(String originalFilename) { 48 | // 获取后缀 49 | String suffix = StrUtil.subAfter(originalFilename, ".", true); 50 | // 生成目录 51 | String name = UUID.randomUUID().toString(); 52 | int hash = name.hashCode(); 53 | int d1 = hash & 0xF; 54 | int d2 = (hash >> 4) & 0xF; 55 | // 判断目录是否存在 56 | File dir = new File(SystemConstants.IMAGE_UPLOAD_DIR, StrUtil.format("/blogs/{}/{}", d1, d2)); 57 | if (!dir.exists()) { 58 | dir.mkdirs(); 59 | } 60 | // 生成文件名 61 | return StrUtil.format("/blogs/{}/{}/{}.{}", d1, d2, name, suffix); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/impl/ShopTypeServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service.impl; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import cn.hutool.json.JSONUtil; 6 | import com.hmdp.dto.Result; 7 | import com.hmdp.entity.Shop; 8 | import com.hmdp.entity.ShopType; 9 | import com.hmdp.mapper.ShopTypeMapper; 10 | import com.hmdp.service.IShopTypeService; 11 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 12 | import org.springframework.data.redis.core.StringRedisTemplate; 13 | import org.springframework.stereotype.Service; 14 | 15 | import javax.annotation.Resource; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import static com.hmdp.utils.RedisConstants.CACHE_SHOPTYPE_KEY; 20 | import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY; 21 | 22 | /** 23 | *

24 | * 服务实现类 25 | *

26 | */ 27 | @Service 28 | public class ShopTypeServiceImpl extends ServiceImpl 29 | implements IShopTypeService { 30 | @Resource 31 | private StringRedisTemplate stringRedisTemplate; 32 | public Result queryshopTypeList(){ 33 | //展示所有的店铺信息 34 | String key =CACHE_SHOPTYPE_KEY ; 35 | //1.从redis查询商铺缓存 36 | // String shopJson = stringRedisTemplate.opsForValue().get(key); 37 | List strshopTypeList = stringRedisTemplate.opsForList().range(key, 0, -1); 38 | ArrayList shopTypes = new ArrayList<>(); 39 | //2.判断是否存在 40 | if (!strshopTypeList.isEmpty()){ 41 | //3.存在直接返回 42 | 43 | for (String s:strshopTypeList) { 44 | ShopType shopType = JSONUtil.toBean(s, ShopType.class); 45 | shopTypes.add(shopType); 46 | } 47 | return Result.ok(shopTypes); 48 | } 49 | 50 | //4.不存在,查询数据库 51 | List typeList = query().orderByAsc("sort").list(); 52 | //5.不存在直接返回错误 53 | if(typeList.isEmpty()){ 54 | return Result.fail("不存在分类"); 55 | } 56 | //6.存在写入rdis 57 | for (ShopType s:typeList) { 58 | String shopjson = JSONUtil.toJsonStr(s); 59 | strshopTypeList.add(shopjson); 60 | } 61 | stringRedisTemplate.opsForList().rightPushAll(key,strshopTypeList); 62 | 63 | return Result.ok(typeList); 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/controller/ShopController.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.controller; 2 | 3 | 4 | import cn.hutool.core.util.StrUtil; 5 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 6 | import com.hmdp.dto.Result; 7 | import com.hmdp.entity.Shop; 8 | import com.hmdp.service.IShopService; 9 | import com.hmdp.utils.SystemConstants; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import javax.annotation.Resource; 13 | 14 | /** 15 | *

16 | * 前端控制器 17 | *

18 | * 19 | * @author 虎哥 20 | */ 21 | @RestController 22 | @RequestMapping("/shop") 23 | public class ShopController { 24 | 25 | @Resource 26 | public IShopService shopService; 27 | 28 | /** 29 | * 根据id查询商铺信息 30 | * @param id 商铺id 31 | * @return 商铺详情数据 32 | */ 33 | @GetMapping("/{id}") 34 | public Result queryShopById(@PathVariable("id") Long id) { 35 | return shopService.queryById(id); 36 | } 37 | 38 | /** 39 | * 新增商铺信息 40 | * @param shop 商铺数据 41 | * @return 商铺id 42 | */ 43 | @PostMapping 44 | public Result saveShop(@RequestBody Shop shop) { 45 | // 写入数据库 46 | shopService.save(shop); 47 | // 返回店铺id 48 | return Result.ok(shop.getId()); 49 | } 50 | 51 | /** 52 | * 更新商铺信息 53 | * @param shop 商铺数据 54 | * @return 无 55 | */ 56 | @PutMapping 57 | public Result updateShop(@RequestBody Shop shop) { 58 | // 写入数据库 59 | return shopService.update(shop); 60 | } 61 | 62 | /** 63 | * 根据商铺类型分页查询商铺信息 64 | * @param typeId 商铺类型 65 | * @param current 页码 66 | * @return 商铺列表 67 | */ 68 | @GetMapping("/of/type") 69 | public Result queryShopByType( 70 | @RequestParam("typeId") Integer typeId, 71 | @RequestParam(value = "current", defaultValue = "1") Integer current, 72 | @RequestParam(value = "x", required = false) Double x, 73 | @RequestParam(value = "y", required = false) Double y 74 | ) { 75 | return shopService.queryShopByType(typeId, current, x, y); 76 | } 77 | 78 | /** 79 | * 根据商铺名称关键字分页查询商铺信息 80 | * @param name 商铺名称关键字 81 | * @param current 页码 82 | * @return 商铺列表 83 | */ 84 | @GetMapping("/of/name") 85 | public Result queryShopByName( 86 | @RequestParam(value = "name", required = false) String name, 87 | @RequestParam(value = "current", defaultValue = "1") Integer current 88 | ) { 89 | // 根据类型分页查询 90 | Page page = shopService.query() 91 | .like(StrUtil.isNotBlank(name), "name", name) 92 | .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE)); 93 | System.out.println("shop/of/name,name="+page.getRecords()); 94 | // 返回数据 95 | return Result.ok(page.getRecords()); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/controller/BlogController.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.controller; 2 | 3 | 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import com.hmdp.dto.Result; 6 | import com.hmdp.dto.UserDTO; 7 | import com.hmdp.entity.Blog; 8 | import com.hmdp.service.IBlogService; 9 | import com.hmdp.utils.SystemConstants; 10 | import com.hmdp.utils.UserHolder; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import javax.annotation.Resource; 14 | import java.util.List; 15 | 16 | /** 17 | *

18 | * 前端控制器 19 | *

20 | * 21 | * @author 虎哥 22 | */ 23 | @RestController 24 | @RequestMapping("/blog") 25 | public class BlogController { 26 | 27 | @Resource 28 | private IBlogService blogService; 29 | 30 | @PostMapping 31 | public Result saveBlog(@RequestBody Blog blog) { 32 | 33 | return Result.ok(blog); 34 | } 35 | 36 | @PutMapping("/like/{id}") 37 | public Result likeBlog(@PathVariable("id") Long id) { 38 | return blogService.likeBlog(id); 39 | } 40 | 41 | @GetMapping("/of/me") 42 | public Result queryMyBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) { 43 | // 获取登录用户 44 | UserDTO user = UserHolder.getUser(); 45 | // 根据用户查询 46 | Page page = blogService.query() 47 | .eq("user_id", user.getId()).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE)); 48 | // 获取当前页数据 49 | List records = page.getRecords(); 50 | return Result.ok(records); 51 | } 52 | 53 | @GetMapping("/hot") 54 | public Result queryHotBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) { 55 | return blogService.queryHotBlog(current); 56 | } 57 | 58 | @GetMapping("/{id}") 59 | public Result queryBlogById(@PathVariable("id") Long id) { 60 | return blogService.queryBlogById(id); 61 | } 62 | 63 | @GetMapping("/likes/{id}") 64 | public Result queryBlogLikes(@PathVariable("id") Long id) { 65 | return blogService.queryBlogLikes(id); 66 | } 67 | 68 | @GetMapping("/of/user") 69 | public Result queryBlogByUserId( 70 | @RequestParam(value = "current", defaultValue = "1") Integer current, 71 | @RequestParam("id") Long id) { 72 | // 根据用户查询 73 | Page page = blogService.query() 74 | .eq("user_id", id).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE)); 75 | // 获取当前页数据 76 | List records = page.getRecords(); 77 | return Result.ok(records); 78 | } 79 | 80 | @GetMapping("/of/follow") 81 | public Result queryBlogOfFollow( 82 | @RequestParam("lastId") Long max, @RequestParam(value = "offset", defaultValue = "0") Integer offset){ 83 | return blogService.queryBlogOfFollow(max, offset); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.controller; 2 | 3 | 4 | import cn.hutool.core.bean.BeanUtil; 5 | import com.hmdp.dto.LoginFormDTO; 6 | import com.hmdp.dto.Result; 7 | import com.hmdp.dto.UserDTO; 8 | import com.hmdp.entity.User; 9 | import com.hmdp.entity.UserInfo; 10 | import com.hmdp.service.IUserInfoService; 11 | import com.hmdp.service.IUserService; 12 | import com.hmdp.utils.UserHolder; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | import javax.annotation.Resource; 17 | import javax.servlet.http.HttpSession; 18 | 19 | /** 20 | *

21 | * 前端控制器 22 | */ 23 | @Slf4j 24 | @RestController 25 | @RequestMapping("/user") 26 | public class UserController { 27 | 28 | @Resource 29 | private IUserService userService; 30 | 31 | @Resource 32 | private IUserInfoService userInfoService; 33 | 34 | /** 35 | * 发送手机验证码 36 | */ 37 | @PostMapping("code") 38 | public Result sendCode(@RequestParam("phone") String phone, HttpSession session) { 39 | // 发送短信验证码并保存验证码 40 | return userService.sendCode(phone, session); 41 | } 42 | 43 | /** 44 | * 登录功能 45 | * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码 46 | */ 47 | @PostMapping("/login") 48 | public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){ 49 | // 实现登录功能 50 | return userService.login(loginForm, session); 51 | } 52 | 53 | /** 54 | * 登出功能 55 | * @return 无 56 | */ 57 | @PostMapping("/logout") 58 | public Result logout(){ 59 | UserHolder.removeUser(); 60 | return Result.fail("退出登录"); 61 | } 62 | 63 | @GetMapping("/me") 64 | public Result me(){ 65 | // 获取当前登录的用户并返回 66 | UserDTO user = UserHolder.getUser(); 67 | return Result.ok(user); 68 | } 69 | 70 | @GetMapping("/info/{id}") 71 | public Result info(@PathVariable("id") Long userId){ 72 | // 查询详情 73 | UserInfo info = userInfoService.getById(userId); 74 | if (info == null) { 75 | // 没有详情,应该是第一次查看详情 76 | return Result.ok(); 77 | } 78 | info.setCreateTime(null); 79 | info.setUpdateTime(null); 80 | // 返回 81 | return Result.ok(info); 82 | } 83 | 84 | @GetMapping("/{id}") 85 | public Result queryUserById(@PathVariable("id") Long userId){ 86 | // 查询详情 87 | User user = userService.getById(userId); 88 | if (user == null) { 89 | return Result.ok(); 90 | } 91 | UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); 92 | // 返回 93 | return Result.ok(userDTO); 94 | } 95 | 96 | @PostMapping("/sign") 97 | public Result sign(){ 98 | return userService.sign(); 99 | } 100 | 101 | @GetMapping("/sign/count") 102 | public Result signCount(){ 103 | return userService.signCount(); 104 | } 105 | } -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/impl/FollowServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service.impl; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import com.hmdp.dto.Result; 7 | import com.hmdp.dto.UserDTO; 8 | import com.hmdp.entity.Follow; 9 | import com.hmdp.mapper.FollowMapper; 10 | import com.hmdp.service.IFollowService; 11 | import com.hmdp.service.IUserService; 12 | import com.hmdp.utils.UserHolder; 13 | import org.springframework.data.redis.core.StringRedisTemplate; 14 | import org.springframework.stereotype.Service; 15 | 16 | import javax.annotation.Resource; 17 | import java.util.Collections; 18 | import java.util.List; 19 | import java.util.Set; 20 | import java.util.stream.Collectors; 21 | 22 | /** 23 | *

24 | * 服务实现类 25 | *

26 | * 27 | */ 28 | @Service 29 | public class FollowServiceImpl extends ServiceImpl implements IFollowService { 30 | 31 | @Resource 32 | private StringRedisTemplate stringRedisTemplate; 33 | @Resource 34 | private IUserService userService; 35 | 36 | @Override 37 | public Result follow(Long followUserId, Boolean isFollow) { 38 | // 1.获取登录用户 39 | Long userId = UserHolder.getUser().getId(); 40 | String key = "follows:" + userId; 41 | // 1.判断到底是关注还是取关 42 | if (isFollow) { 43 | // 2.关注,新增数据 44 | Follow follow = new Follow(); 45 | follow.setUserId(userId); 46 | follow.setFollowUserId(followUserId); 47 | boolean isSuccess = save(follow); 48 | if (isSuccess) { 49 | // 把关注用户的id,放入redis的set集合 sadd userId followerUserId 50 | stringRedisTemplate.opsForSet().add(key, followUserId.toString()); 51 | } 52 | } else { 53 | // 3.取关,删除 delete from tb_follow where user_id = ? and follow_user_id = ? 54 | boolean isSuccess = remove(new QueryWrapper() 55 | .eq("user_id", userId).eq("follow_user_id", followUserId)); 56 | if (isSuccess) { 57 | // 把关注用户的id从Redis集合中移除 58 | stringRedisTemplate.opsForSet().remove(key, followUserId.toString()); 59 | } 60 | 61 | } 62 | return Result.ok(); 63 | } 64 | 65 | @Override 66 | public Result isFollow(Long followUserId) { 67 | // 1.获取登录用户 68 | Long userId = UserHolder.getUser().getId(); 69 | // 2.查询是否关注 select count(*) from tb_follow where user_id = ? and follow_user_id = ? 70 | Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count(); 71 | // 3.判断 72 | return Result.ok(count > 0); 73 | } 74 | 75 | @Override 76 | public Result followCommons(Long id) { 77 | // 1.获取当前用户 78 | Long userId = UserHolder.getUser().getId(); 79 | String key = "follows:" + userId; 80 | // 2.求交集 81 | String key2 = "follows:" + id; 82 | Set intersect = stringRedisTemplate.opsForSet().intersect(key, key2); 83 | if (intersect == null || intersect.isEmpty()) { 84 | // 无交集 85 | return Result.ok(Collections.emptyList()); 86 | } 87 | // 3.解析id集合 88 | List ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList()); 89 | // 4.查询用户 90 | List users = userService.listByIds(ids) 91 | .stream() 92 | .map(user -> BeanUtil.copyProperties(user, UserDTO.class)) 93 | .collect(Collectors.toList()); 94 | return Result.ok(users); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/com/hmdp/HmDianPingApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.hmdp; 2 | 3 | import com.hmdp.entity.Shop; 4 | import com.hmdp.service.impl.ShopServiceImpl; 5 | import com.hmdp.utils.CacheClient; 6 | import com.hmdp.utils.RedisIdWorker; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.data.geo.Point; 10 | import org.springframework.data.redis.connection.RedisGeoCommands; 11 | import org.springframework.data.redis.core.StringRedisTemplate; 12 | 13 | import javax.annotation.Resource; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.concurrent.CountDownLatch; 18 | import java.util.concurrent.ExecutorService; 19 | import java.util.concurrent.Executors; 20 | import java.util.concurrent.TimeUnit; 21 | import java.util.stream.Collectors; 22 | 23 | import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY; 24 | import static com.hmdp.utils.RedisConstants.SHOP_GEO_KEY; 25 | 26 | @SpringBootTest 27 | class HmDianPingApplicationTests { 28 | 29 | @Resource 30 | private CacheClient cacheClient; 31 | 32 | @Resource 33 | private ShopServiceImpl shopService; 34 | 35 | @Resource 36 | private RedisIdWorker redisIdWorker; 37 | 38 | @Resource 39 | private StringRedisTemplate stringRedisTemplate; 40 | 41 | private ExecutorService es = Executors.newFixedThreadPool(500); 42 | 43 | /** 44 | * 自测 45 | * @throws InterruptedException 46 | */ 47 | 48 | 49 | 50 | @Test 51 | void testIdWorker() throws InterruptedException { 52 | CountDownLatch latch = new CountDownLatch(300); 53 | 54 | Runnable task = () -> { 55 | for (int i = 0; i < 100; i++) { 56 | long id = redisIdWorker.nextId("order"); 57 | System.out.println("id = " + id); 58 | } 59 | latch.countDown(); 60 | }; 61 | long begin = System.currentTimeMillis(); 62 | for (int i = 0; i < 300; i++) { 63 | es.submit(task); 64 | } 65 | latch.await(); 66 | long end = System.currentTimeMillis(); 67 | System.out.println("time = " + (end - begin)); 68 | } 69 | 70 | @Test 71 | void testSaveShop() throws InterruptedException { 72 | Shop shop = shopService.getById(1L); 73 | cacheClient.setWithLogicalExpire(CACHE_SHOP_KEY + 1L, shop, 10L, TimeUnit.SECONDS); 74 | } 75 | 76 | @Test 77 | void loadShopData() { 78 | // 1.查询店铺信息 79 | List list = shopService.list(); 80 | // 2.把店铺分组,按照typeId分组,typeId一致的放到一个集合 81 | Map> map = list.stream().collect(Collectors.groupingBy(Shop::getTypeId)); 82 | // 3.分批完成写入Redis 83 | for (Map.Entry> entry : map.entrySet()) { 84 | // 3.1.获取类型id 85 | Long typeId = entry.getKey(); 86 | String key = SHOP_GEO_KEY + typeId; 87 | // 3.2.获取同类型的店铺的集合 88 | List value = entry.getValue(); 89 | List> locations = new ArrayList<>(value.size()); 90 | // 3.3.写入redis GEOADD key 经度 纬度 member 91 | for (Shop shop : value) { 92 | // stringRedisTemplate.opsForGeo().add(key, new Point(shop.getX(), shop.getY()), shop.getId().toString()); 93 | locations.add(new RedisGeoCommands.GeoLocation<>( 94 | shop.getId().toString(), 95 | new Point(shop.getX(), shop.getY()) 96 | )); 97 | } 98 | stringRedisTemplate.opsForGeo().add(key, locations); 99 | } 100 | } 101 | 102 | @Test 103 | void testHyperLogLog() { 104 | String[] values = new String[1000]; 105 | int j = 0; 106 | for (int i = 0; i < 1000000; i++) { 107 | j = i % 1000; 108 | values[j] = "user_" + i; 109 | if(j == 999){ 110 | // 发送到Redis 111 | stringRedisTemplate.opsForHyperLogLog().add("hl2", values); 112 | } 113 | } 114 | // 统计数量 115 | Long count = stringRedisTemplate.opsForHyperLogLog().size("hl2"); 116 | System.out.println("count = " + count); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.5 9 | 10 | 11 | com.hmdp 12 | hm-dianping 13 | 0.0.1-SNAPSHOT 14 | hm-dianping 15 | Demo project for Spring Boot 16 | 17 | 1.8 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-data-redis 23 | 24 | 25 | spring-data-redis 26 | org.springframework.data 27 | 28 | 29 | lettuce-core 30 | io.lettuce 31 | 32 | 33 | 34 | 35 | org.springframework.data 36 | spring-data-redis 37 | 38 | 39 | org.apache.commons 40 | commons-pool2 41 | 42 | 43 | io.lettuce 44 | lettuce-core 45 | 6.1.6.RELEASE 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-web 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | mysql 60 | mysql-connector-java 61 | runtime 62 | 63 | 64 | org.projectlombok 65 | lombok 66 | true 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-starter-test 71 | test 72 | 73 | 74 | com.baomidou 75 | mybatis-plus-boot-starter 76 | 3.4.3 77 | 78 | 79 | 80 | cn.hutool 81 | hutool-all 82 | 5.7.17 83 | 84 | 85 | 86 | org.redisson 87 | redisson 88 | 3.13.6 89 | 90 | 91 | 92 | org.aspectj 93 | aspectjweaver 94 | 1.9.9.1 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | org.springframework.boot 103 | spring-boot-maven-plugin 104 | 105 | 106 | 107 | org.projectlombok 108 | lombok 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/impl/ShopServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service.impl; 2 | 3 | import cn.hutool.core.util.BooleanUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import cn.hutool.json.JSONObject; 6 | import cn.hutool.json.JSONUtil; 7 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 8 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 9 | import com.hmdp.dto.Result; 10 | import com.hmdp.entity.Shop; 11 | import com.hmdp.mapper.ShopMapper; 12 | import com.hmdp.service.IShopService; 13 | import com.hmdp.utils.CacheClient; 14 | import com.hmdp.utils.RedisData; 15 | import com.hmdp.utils.SystemConstants; 16 | import org.springframework.data.geo.Distance; 17 | import org.springframework.data.geo.GeoResult; 18 | import org.springframework.data.geo.GeoResults; 19 | import org.springframework.data.redis.connection.RedisGeoCommands; 20 | import org.springframework.data.redis.core.StringRedisTemplate; 21 | import org.springframework.data.redis.domain.geo.GeoReference; 22 | import org.springframework.stereotype.Service; 23 | import org.springframework.transaction.annotation.Transactional; 24 | 25 | import javax.annotation.Resource; 26 | import java.time.LocalDateTime; 27 | import java.util.*; 28 | import java.util.concurrent.ExecutorService; 29 | import java.util.concurrent.Executors; 30 | import java.util.concurrent.TimeUnit; 31 | 32 | import static com.hmdp.utils.RedisConstants.*; 33 | 34 | /** 35 | *

36 | * 服务实现类 37 | *

38 | * 39 | */ 40 | @Service 41 | public class ShopServiceImpl extends ServiceImpl implements IShopService { 42 | 43 | 44 | @Resource 45 | private StringRedisTemplate stringRedisTemplate; 46 | 47 | @Resource 48 | private CacheClient cacheClient; 49 | 50 | @Override 51 | public Result queryById(Long id) { 52 | 53 | // 解决缓存穿透 54 | Shop shop = cacheClient 55 | .queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES); 56 | 57 | // 互斥锁解决缓存击穿 58 | // Shop shop = cacheClient 59 | // .queryWithMutex(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES); 60 | 61 | // // 逻辑过期解决缓存击穿 62 | // Shop shop = cacheClient 63 | // .queryWithLogicalExpire(CACHE_SHOP_KEY, id, Shop.class,this::getById,CACHE_SHOP_TTL,TimeUnit.MINUTES ); 64 | if (shop==null){ 65 | return Result.fail("店铺不存在"); 66 | } 67 | // 7.返回 68 | return Result.ok(shop); 69 | } 70 | 71 | 72 | 73 | private Boolean tryLock(String key){ 74 | Boolean flag = stringRedisTemplate.opsForValue(). 75 | setIfAbsent(key, "1", 10, TimeUnit.SECONDS); 76 | 77 | return BooleanUtil.isTrue(flag); 78 | } 79 | private void unLock(String key){ 80 | stringRedisTemplate.delete(key); 81 | } 82 | //可以把前面缓存穿透的代码封装 83 | 84 | 85 | @Override 86 | @Transactional 87 | public Result update(Shop shop) { 88 | Long id = shop.getId(); 89 | if (id == null) { 90 | return Result.fail("店铺id不能为空"); 91 | } 92 | // 1.更新数据库 93 | updateById(shop); 94 | // 2.删除缓存 95 | stringRedisTemplate.delete(CACHE_SHOP_KEY + id); 96 | return Result.ok(); 97 | } 98 | 99 | @Override 100 | public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) { 101 | // 1.判断是否需要根据坐标查询 102 | if (x == null || y == null) { 103 | // 不需要坐标查询,按数据库查询 104 | Page page = query() 105 | .eq("type_id", typeId) 106 | .page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE)); 107 | // 返回数据 108 | 109 | 110 | System.out.println("返回的type/of/quershopByType的数据"+page.getRecords().toString()); 111 | return Result.ok(page.getRecords()); 112 | } 113 | 114 | // 2.计算分页参数 115 | int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE; 116 | int end = current * SystemConstants.DEFAULT_PAGE_SIZE; 117 | 118 | // 3.查询redis、按照距离排序、分页。结果:shopId、distance 119 | String key = SHOP_GEO_KEY + typeId; 120 | GeoResults> results = stringRedisTemplate.opsForGeo() // GEOSEARCH key BYLONLAT x y BYRADIUS 10 WITHDISTANCE 121 | .search( 122 | key, 123 | GeoReference.fromCoordinate(x, y), 124 | new Distance(5000), 125 | RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end) 126 | ); 127 | // 4.解析出id 128 | if (results == null) { 129 | return Result.ok(Collections.emptyList()); 130 | } 131 | List>> list = results.getContent(); 132 | if (list.size() <= from) { 133 | // 没有下一页了,结束 134 | return Result.ok(Collections.emptyList()); 135 | } 136 | // 4.1.截取 from ~ end的部分 137 | List ids = new ArrayList<>(list.size()); 138 | Map distanceMap = new HashMap<>(list.size()); 139 | list.stream().skip(from).forEach(result -> { 140 | // 4.2.获取店铺id 141 | String shopIdStr = result.getContent().getName(); 142 | ids.add(Long.valueOf(shopIdStr)); 143 | // 4.3.获取距离 144 | Distance distance = result.getDistance(); 145 | distanceMap.put(shopIdStr, distance); 146 | }); 147 | // 5.根据id查询Shop 148 | String idStr = StrUtil.join(",", ids); 149 | List shops = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list(); 150 | for (Shop shop : shops) { 151 | shop.setDistance(distanceMap.get(shop.getId().toString()).getValue()); 152 | } 153 | // 6.返回 154 | return Result.ok(shops); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service.impl; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import cn.hutool.core.bean.copier.CopyOptions; 5 | import cn.hutool.core.lang.UUID; 6 | import cn.hutool.core.util.RandomUtil; 7 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 8 | import com.hmdp.dto.LoginFormDTO; 9 | import com.hmdp.dto.Result; 10 | import com.hmdp.dto.UserDTO; 11 | import com.hmdp.entity.User; 12 | import com.hmdp.mapper.UserMapper; 13 | import com.hmdp.service.IUserService; 14 | import com.hmdp.utils.RegexUtils; 15 | import com.hmdp.utils.UserHolder; 16 | import lombok.extern.slf4j.Slf4j; 17 | import org.springframework.data.redis.connection.BitFieldSubCommands; 18 | import org.springframework.data.redis.core.StringRedisTemplate; 19 | import org.springframework.stereotype.Service; 20 | 21 | import javax.annotation.Resource; 22 | import javax.servlet.http.HttpSession; 23 | import java.time.LocalDateTime; 24 | import java.time.format.DateTimeFormatter; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | import java.util.concurrent.TimeUnit; 29 | 30 | import static com.hmdp.utils.RedisConstants.*; 31 | import static com.hmdp.utils.SystemConstants.USER_NICK_NAME_PREFIX; 32 | 33 | /** 34 | *

35 | * 服务实现类 36 | *

37 | * 38 | */ 39 | @Slf4j 40 | @Service 41 | public class UserServiceImpl extends ServiceImpl implements IUserService { 42 | 43 | @Resource 44 | private StringRedisTemplate stringRedisTemplate; 45 | 46 | @Override 47 | public Result sendCode(String phone, HttpSession session) { 48 | // 1.校验手机号 49 | if (RegexUtils.isPhoneInvalid(phone)) { 50 | // 2.如果不符合,返回错误信息 51 | return Result.fail("手机号格式错误!"); 52 | } 53 | // 3.符合,生成验证码 54 | String code = RandomUtil.randomNumbers(6); 55 | 56 | // 4.保存验证码到 session 57 | stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES); 58 | 59 | // 5.发送验证码 60 | log.debug("发送短信验证码成功,验证码:{}", code); 61 | // 返回ok 62 | return Result.ok(); 63 | } 64 | 65 | @Override 66 | public Result login(LoginFormDTO loginForm, HttpSession session) { 67 | // 1.校验手机号 68 | String phone = loginForm.getPhone(); 69 | if (RegexUtils.isPhoneInvalid(phone)) { 70 | // 2.如果不符合,返回错误信息 71 | return Result.fail("手机号格式错误!"); 72 | } 73 | // 3.从redis获取验证码并校验 74 | String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone); 75 | String code = loginForm.getCode(); 76 | if (cacheCode == null || !cacheCode.equals(code)) { 77 | // 不一致,报错 78 | return Result.fail("验证码错误"); 79 | } 80 | 81 | // 4.一致,根据手机号查询用户 select * from tb_user where phone = ? 82 | User user = query().eq("phone", phone).one(); 83 | 84 | // 5.判断用户是否存在 85 | if (user == null) { 86 | // 6.不存在,创建新用户并保存 87 | user = createUserWithPhone(phone); 88 | } 89 | 90 | // 7.保存用户信息到 redis中 91 | // 7.1.随机生成token,作为登录令牌 92 | String token = UUID.randomUUID().toString(true); 93 | // 7.2.将User对象转为HashMap存储 94 | UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); 95 | Map userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), 96 | CopyOptions.create() 97 | .setIgnoreNullValue(true) 98 | .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString())); 99 | // 7.3.存储 100 | String tokenKey = LOGIN_USER_KEY + token; 101 | stringRedisTemplate.opsForHash().putAll(tokenKey, userMap); 102 | // 7.4.设置token有效期 103 | stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES); 104 | 105 | // 8.返回token 106 | return Result.ok(token); 107 | } 108 | 109 | @Override 110 | public Result sign() { 111 | // 1.获取当前登录用户 112 | Long userId = UserHolder.getUser().getId(); 113 | // 2.获取日期 114 | LocalDateTime now = LocalDateTime.now(); 115 | // 3.拼接key 116 | String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM")); 117 | String key = USER_SIGN_KEY + userId + keySuffix; 118 | // 4.获取今天是本月的第几天 119 | int dayOfMonth = now.getDayOfMonth(); 120 | // 5.写入Redis SETBIT key offset 1 121 | stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true); 122 | return Result.ok(); 123 | } 124 | 125 | @Override 126 | public Result signCount() { 127 | // 1.获取当前登录用户 128 | Long userId = UserHolder.getUser().getId(); 129 | // 2.获取日期 130 | LocalDateTime now = LocalDateTime.now(); 131 | // 3.拼接key 132 | String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM")); 133 | String key = USER_SIGN_KEY + userId + keySuffix; 134 | // 4.获取今天是本月的第几天 135 | int dayOfMonth = now.getDayOfMonth(); 136 | // 5.获取本月截止今天为止的所有的签到记录,返回的是一个十进制的数字 BITFIELD sign:5:202203 GET u14 0 137 | List result = stringRedisTemplate.opsForValue().bitField( 138 | key, 139 | BitFieldSubCommands.create() 140 | .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0) 141 | ); 142 | if (result == null || result.isEmpty()) { 143 | // 没有任何签到结果 144 | return Result.ok(0); 145 | } 146 | Long num = result.get(0); 147 | if (num == null || num == 0) { 148 | return Result.ok(0); 149 | } 150 | // 6.循环遍历 151 | int count = 0; 152 | while (true) { 153 | // 6.1.让这个数字与1做与运算,得到数字的最后一个bit位 // 判断这个bit位是否为0 154 | if ((num & 1) == 0) { 155 | // 如果为0,说明未签到,结束 156 | break; 157 | }else { 158 | // 如果不为0,说明已签到,计数器+1 159 | count++; 160 | } 161 | // 把数字右移一位,抛弃最后一个bit位,继续下一个bit位 162 | num >>>= 1; 163 | } 164 | return Result.ok(count); 165 | } 166 | 167 | private User createUserWithPhone(String phone) { 168 | // 1.创建用户 169 | User user = new User(); 170 | user.setPhone(phone); 171 | user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10)); 172 | // 2.保存用户 173 | save(user); 174 | return user; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/utils/CacheClient.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.utils; 2 | 3 | import cn.hutool.core.util.BooleanUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import cn.hutool.json.JSONObject; 6 | import cn.hutool.json.JSONUtil; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.data.redis.core.StringRedisTemplate; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.annotation.Resource; 12 | import java.time.LocalDateTime; 13 | import java.util.concurrent.ExecutorService; 14 | import java.util.concurrent.Executors; 15 | import java.util.concurrent.TimeUnit; 16 | import java.util.function.Function; 17 | 18 | import static com.hmdp.utils.RedisConstants.CACHE_NULL_TTL; 19 | import static com.hmdp.utils.RedisConstants.LOCK_SHOP_KEY; 20 | 21 | @Slf4j 22 | @Component 23 | public class CacheClient { 24 | 25 | private final StringRedisTemplate stringRedisTemplate; 26 | 27 | private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10); 28 | 29 | public CacheClient(StringRedisTemplate stringRedisTemplate) { 30 | this.stringRedisTemplate = stringRedisTemplate; 31 | } 32 | 33 | public void set(String key, Object value, Long time, TimeUnit unit) { 34 | stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit); 35 | } 36 | 37 | public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) { 38 | // 设置逻辑过期 39 | RedisData redisData = new RedisData(); 40 | redisData.setData(value); 41 | redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time))); 42 | // 写入Redis 43 | stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData)); 44 | } 45 | 46 | public R queryWithPassThrough( 47 | String keyPrefix, ID id, Class type, Function dbFallback, Long time, TimeUnit unit){ 48 | String key = keyPrefix + id; 49 | // 1.从redis查询商铺缓存 50 | String json = stringRedisTemplate.opsForValue().get(key); 51 | // 2.判断是否存在 52 | if (StrUtil.isNotBlank(json)) { 53 | // 3.存在,直接返回 54 | return JSONUtil.toBean(json, type); 55 | } 56 | // 判断命中的是否是空值 57 | if (json != null) { 58 | // 返回一个错误信息 59 | return null; 60 | } 61 | 62 | // 4.不存在,根据id查询数据库 63 | R r = dbFallback.apply(id); 64 | // 5.不存在,返回错误 65 | if (r == null) { 66 | // 将空值写入redis 67 | stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES); 68 | // 返回错误信息 69 | return null; 70 | } 71 | // 6.存在,写入redis 72 | this.set(key, r, time, unit); 73 | return r; 74 | } 75 | 76 | public R queryWithLogicalExpire( 77 | String keyPrefix, ID id, Class type, Function dbFallback, Long time, TimeUnit unit) { 78 | String key = keyPrefix + id; 79 | // 1.从redis查询商铺缓存 80 | String json = stringRedisTemplate.opsForValue().get(key); 81 | // 2.判断是否存在 82 | if (StrUtil.isBlank(json)) { 83 | // 3.不存在,直接返回 84 | return null; 85 | } 86 | // 4.命中,需要先把json反序列化为对象 87 | RedisData redisData = JSONUtil.toBean(json, RedisData.class); 88 | R r = JSONUtil.toBean((JSONObject) redisData.getData(), type); 89 | LocalDateTime expireTime = redisData.getExpireTime(); 90 | // 5.判断是否过期 91 | if(expireTime.isAfter(LocalDateTime.now())) { 92 | // 5.1.未过期,直接返回店铺信息 93 | return r; 94 | } 95 | // 5.2.已过期,需要缓存重建 96 | // 6.缓存重建 97 | // 6.1.获取互斥锁 98 | String lockKey = LOCK_SHOP_KEY + id; 99 | boolean isLock = tryLock(lockKey); 100 | // 6.2.判断是否获取锁成功 101 | if (isLock){ 102 | // 6.3.成功,开启独立线程,实现缓存重建 103 | CACHE_REBUILD_EXECUTOR.submit(() -> { 104 | try { 105 | // 查询数据库 106 | R newR = dbFallback.apply(id); 107 | // 重建缓存 108 | this.setWithLogicalExpire(key, newR, time, unit); 109 | } catch (Exception e) { 110 | throw new RuntimeException(e); 111 | }finally { 112 | // 释放锁 113 | unlock(lockKey); 114 | } 115 | }); 116 | } 117 | // 6.4.返回过期的商铺信息 118 | return r; 119 | } 120 | 121 | public R queryWithMutex( 122 | String keyPrefix, ID id, Class type, Function dbFallback, Long time, TimeUnit unit) { 123 | String key = keyPrefix + id; 124 | // 1.从redis查询商铺缓存 125 | String shopJson = stringRedisTemplate.opsForValue().get(key); 126 | // 2.判断是否存在 127 | if (StrUtil.isNotBlank(shopJson)) { 128 | // 3.存在,直接返回 129 | return JSONUtil.toBean(shopJson, type); 130 | } 131 | // 判断命中的是否是空值 132 | if (shopJson != null) { 133 | // 返回一个错误信息 134 | return null; 135 | } 136 | 137 | // 4.实现缓存重建 138 | // 4.1.获取互斥锁 139 | String lockKey = LOCK_SHOP_KEY + id; 140 | R r = null; 141 | try { 142 | boolean isLock = tryLock(lockKey); 143 | // 4.2.判断是否获取成功 144 | if (!isLock) { 145 | // 4.3.获取锁失败,休眠并重试 146 | Thread.sleep(50); 147 | return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit); 148 | } 149 | // 4.4.获取锁成功,根据id查询数据库 150 | r = dbFallback.apply(id); 151 | // 5.不存在,返回错误 152 | if (r == null) { 153 | // 将空值写入redis 154 | stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES); 155 | // 返回错误信息 156 | return null; 157 | } 158 | // 6.存在,写入redis 159 | this.set(key, r, time, unit); 160 | } catch (InterruptedException e) { 161 | throw new RuntimeException(e); 162 | }finally { 163 | // 7.释放锁 164 | unlock(lockKey); 165 | } 166 | // 8.返回 167 | return r; 168 | } 169 | 170 | private boolean tryLock(String key) { 171 | Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS); 172 | return BooleanUtil.isTrue(flag); 173 | } 174 | 175 | private void unlock(String key) { 176 | stringRedisTemplate.delete(key); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/impl/BlogServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service.impl; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 6 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 7 | import com.hmdp.dto.Result; 8 | import com.hmdp.dto.ScrollResult; 9 | import com.hmdp.dto.UserDTO; 10 | import com.hmdp.entity.Blog; 11 | import com.hmdp.entity.Follow; 12 | import com.hmdp.entity.User; 13 | import com.hmdp.mapper.BlogMapper; 14 | import com.hmdp.service.IBlogService; 15 | import com.hmdp.service.IFollowService; 16 | import com.hmdp.service.IUserService; 17 | import com.hmdp.utils.SystemConstants; 18 | import com.hmdp.utils.UserHolder; 19 | import org.springframework.data.redis.core.StringRedisTemplate; 20 | import org.springframework.data.redis.core.ZSetOperations; 21 | import org.springframework.stereotype.Service; 22 | 23 | import javax.annotation.Resource; 24 | import java.util.ArrayList; 25 | import java.util.Collections; 26 | import java.util.List; 27 | import java.util.Set; 28 | import java.util.stream.Collectors; 29 | 30 | import static com.hmdp.utils.RedisConstants.BLOG_LIKED_KEY; 31 | import static com.hmdp.utils.RedisConstants.FEED_KEY; 32 | 33 | /** 34 | *

35 | * 服务实现类 36 | *

37 | * 38 | */ 39 | @Service 40 | public class BlogServiceImpl extends ServiceImpl implements IBlogService { 41 | 42 | @Resource 43 | private IUserService userService; 44 | 45 | @Resource 46 | private StringRedisTemplate stringRedisTemplate; 47 | 48 | @Resource 49 | private IFollowService followService; 50 | 51 | @Override 52 | public Result queryHotBlog(Integer current) { 53 | // 根据用户查询 54 | Page page = query() 55 | .orderByDesc("liked") 56 | .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE)); 57 | // 获取当前页数据 58 | List records = page.getRecords(); 59 | // 查询用户 60 | records.forEach(blog -> { 61 | this.queryBlogUser(blog); 62 | this.isBlogLiked(blog); 63 | }); 64 | return Result.ok(records); 65 | } 66 | 67 | @Override 68 | public Result queryBlogById(Long id) { 69 | // 1.查询blog 70 | Blog blog = getById(id); 71 | if (blog == null) { 72 | return Result.fail("笔记不存在!"); 73 | } 74 | // 2.查询blog有关的用户 75 | queryBlogUser(blog); 76 | // 3.查询blog是否被点赞 77 | isBlogLiked(blog); 78 | return Result.ok(blog); 79 | } 80 | 81 | private void isBlogLiked(Blog blog) { 82 | // 1.获取登录用户 83 | UserDTO user = UserHolder.getUser(); 84 | if (user == null) { 85 | // 用户未登录,无需查询是否点赞 86 | return; 87 | } 88 | Long userId = user.getId(); 89 | // 2.判断当前登录用户是否已经点赞 90 | String key = "blog:liked:" + blog.getId(); 91 | Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString()); 92 | blog.setIsLike(score != null); 93 | } 94 | 95 | @Override 96 | public Result likeBlog(Long id) { 97 | // 1.获取登录用户 98 | Long userId = UserHolder.getUser().getId(); 99 | // 2.判断当前登录用户是否已经点赞 100 | String key = BLOG_LIKED_KEY + id; 101 | Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString()); 102 | if (score == null) { 103 | // 3.如果未点赞,可以点赞 104 | // 3.1.数据库点赞数 + 1 105 | boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update(); 106 | // 3.2.保存用户到Redis的set集合 zadd key value score 107 | if (isSuccess) { 108 | stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis()); 109 | } 110 | } else { 111 | // 4.如果已点赞,取消点赞 112 | // 4.1.数据库点赞数 -1 113 | boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update(); 114 | // 4.2.把用户从Redis的set集合移除 115 | if (isSuccess) { 116 | stringRedisTemplate.opsForZSet().remove(key, userId.toString()); 117 | } 118 | } 119 | return Result.ok(); 120 | } 121 | 122 | @Override 123 | public Result queryBlogLikes(Long id) { 124 | String key = BLOG_LIKED_KEY + id; 125 | // 1.查询top5的点赞用户 zrange key 0 4 126 | Set top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4); 127 | if (top5 == null || top5.isEmpty()) { 128 | return Result.ok(Collections.emptyList()); 129 | } 130 | // 2.解析出其中的用户id 131 | List ids = top5.stream().map(Long::valueOf).collect(Collectors.toList()); 132 | String idStr = StrUtil.join(",", ids); 133 | // 3.根据用户id查询用户 WHERE id IN ( 5 , 1 ) ORDER BY FIELD(id, 5, 1) 134 | List userDTOS = userService.query() 135 | .in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list() 136 | .stream() 137 | .map(user -> BeanUtil.copyProperties(user, UserDTO.class)) 138 | .collect(Collectors.toList()); 139 | // 4.返回 140 | return Result.ok(userDTOS); 141 | } 142 | 143 | @Override 144 | public Result saveBlog(Blog blog) { 145 | // 1.获取登录用户 146 | UserDTO user = UserHolder.getUser(); 147 | blog.setUserId(user.getId()); 148 | // 2.保存探店笔记 149 | boolean isSuccess = save(blog); 150 | if(!isSuccess){ 151 | return Result.fail("新增笔记失败!"); 152 | } 153 | // 3.查询笔记作者的所有粉丝 select * from tb_follow where follow_user_id = ? 154 | List follows = followService.query().eq("follow_user_id", user.getId()).list(); 155 | // 4.推送笔记id给所有粉丝 156 | for (Follow follow : follows) { 157 | // 4.1.获取粉丝id 158 | Long userId = follow.getUserId(); 159 | // 4.2.推送 160 | String key = FEED_KEY + userId; 161 | stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis()); 162 | } 163 | // 5.返回id 164 | return Result.ok(blog.getId()); 165 | } 166 | 167 | @Override 168 | public Result queryBlogOfFollow(Long max, Integer offset) { 169 | // 1.获取当前用户 170 | Long userId = UserHolder.getUser().getId(); 171 | // 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count 172 | String key = FEED_KEY + userId; 173 | Set> typedTuples = stringRedisTemplate.opsForZSet() 174 | .reverseRangeByScoreWithScores(key, 0, max, offset, 2); 175 | // 3.非空判断 176 | if (typedTuples == null || typedTuples.isEmpty()) { 177 | return Result.ok(); 178 | } 179 | // 4.解析数据:blogId、minTime(时间戳)、offset 180 | List ids = new ArrayList<>(typedTuples.size()); 181 | long minTime = 0; // 2 182 | int os = 1; // 2 183 | for (ZSetOperations.TypedTuple tuple : typedTuples) { // 5 4 4 2 2 184 | // 4.1.获取id 185 | ids.add(Long.valueOf(tuple.getValue())); 186 | // 4.2.获取分数(时间戳) 187 | long time = tuple.getScore().longValue(); 188 | if(time == minTime){ 189 | os++; 190 | }else{ 191 | minTime = time; 192 | os = 1; 193 | } 194 | } 195 | 196 | // 5.根据id查询blog 197 | String idStr = StrUtil.join(",", ids); 198 | List blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list(); 199 | 200 | for (Blog blog : blogs) { 201 | // 5.1.查询blog有关的用户 202 | queryBlogUser(blog); 203 | // 5.2.查询blog是否被点赞 204 | isBlogLiked(blog); 205 | } 206 | 207 | // 6.封装并返回 208 | ScrollResult r = new ScrollResult(); 209 | r.setList(blogs); 210 | r.setOffset(os); 211 | r.setMinTime(minTime); 212 | 213 | return Result.ok(r); 214 | } 215 | 216 | private void queryBlogUser(Blog blog) { 217 | Long userId = blog.getUserId(); 218 | User user = userService.getById(userId); 219 | blog.setName(user.getNickName()); 220 | blog.setIcon(user.getIcon()); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/main/java/com/hmdp/service/impl/VoucherOrderServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hmdp.service.impl; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 5 | import com.hmdp.dto.Result; 6 | import com.hmdp.entity.VoucherOrder; 7 | import com.hmdp.mapper.VoucherOrderMapper; 8 | import com.hmdp.service.ISeckillVoucherService; 9 | import com.hmdp.service.IVoucherOrderService; 10 | import com.hmdp.utils.RedisIdWorker; 11 | import com.hmdp.utils.UserHolder; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.redisson.api.RLock; 14 | import org.redisson.api.RedissonClient; 15 | import org.springframework.aop.framework.AopContext; 16 | import org.springframework.core.io.ClassPathResource; 17 | import org.springframework.data.redis.connection.stream.*; 18 | import org.springframework.data.redis.core.StringRedisTemplate; 19 | import org.springframework.data.redis.core.script.DefaultRedisScript; 20 | import org.springframework.stereotype.Service; 21 | import org.springframework.transaction.annotation.Transactional; 22 | 23 | import javax.annotation.PostConstruct; 24 | import javax.annotation.Resource; 25 | import java.time.Duration; 26 | import java.util.Collections; 27 | import java.util.List; 28 | import java.util.Map; 29 | import java.util.concurrent.ExecutorService; 30 | import java.util.concurrent.Executors; 31 | 32 | /** 33 | *

34 | * 服务实现类 35 | *

36 | * 37 | */ 38 | @Slf4j 39 | @Service 40 | public class VoucherOrderServiceImpl extends ServiceImpl implements IVoucherOrderService { 41 | 42 | @Resource 43 | private ISeckillVoucherService seckillVoucherService; 44 | 45 | @Resource 46 | private RedisIdWorker redisIdWorker; 47 | @Resource 48 | private RedissonClient redissonClient; 49 | @Resource 50 | private StringRedisTemplate stringRedisTemplate; 51 | 52 | private static final DefaultRedisScript SECKILL_SCRIPT; 53 | // 代理对象 54 | private IVoucherOrderService currentProxy; 55 | 56 | static { 57 | SECKILL_SCRIPT = new DefaultRedisScript<>(); 58 | SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua")); 59 | SECKILL_SCRIPT.setResultType(Long.class); 60 | } 61 | 62 | 63 | private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor(); 64 | 65 | @PostConstruct 66 | private void init() { 67 | SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler()); 68 | } 69 | 70 | private class VoucherOrderHandler implements Runnable { 71 | 72 | @Override 73 | public void run() { 74 | while (true) { 75 | try { 76 | // 1.获取消息队列中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 > 77 | List> list = stringRedisTemplate.opsForStream().read( 78 | Consumer.from("g1", "c1"), 79 | StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)), 80 | StreamOffset.create("stream.orders", ReadOffset.lastConsumed()) 81 | ); 82 | // 2.判断订单信息是否为空 83 | if (list == null || list.isEmpty()) { 84 | // 如果为null,说明没有消息,继续下一次循环 85 | continue; 86 | } 87 | // 解析数据 88 | MapRecord record = list.get(0); 89 | Map value = record.getValue(); 90 | VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true); 91 | // 3.创建订单 92 | handleVoucherOrder(voucherOrder); 93 | // 4.确认消息 XACK 94 | stringRedisTemplate.opsForStream().acknowledge("s1", "g1", record.getId()); 95 | } catch (Exception e) { 96 | log.error("处理订单异常", e); 97 | handlePendingList(); 98 | } 99 | } 100 | } 101 | // private void handleVoucherOrder(VoucherOrder voucherOrder) { 102 | // Long userId = voucherOrder.getUserId(); 103 | // RLock lock = redissonClient.getLock("lock:order:" + userId); 104 | // boolean isLocked = lock.tryLock(); 105 | // if (!isLocked) { 106 | // log.error("不允许重复下单!"); 107 | // return; 108 | // } 109 | // try { 110 | // // 该方法非主线程调用,代理对象需要在主线程中获取。 111 | // currentProxy.createVoucherOrder(voucherOrder); 112 | // } finally { 113 | // lock.unlock(); 114 | // } 115 | // } 116 | } 117 | 118 | private void handlePendingList() { 119 | while (true) { 120 | try { 121 | // 1.获取pending-list中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 0 122 | List> list = stringRedisTemplate.opsForStream().read( 123 | Consumer.from("g1", "c1"), 124 | StreamReadOptions.empty().count(1), 125 | StreamOffset.create("stream.orders", ReadOffset.from("0")) 126 | ); 127 | // 2.判断订单信息是否为空 128 | if (list == null || list.isEmpty()) { 129 | // 如果为null,说明没有异常消息,结束循环 130 | break; 131 | } 132 | // 解析数据 133 | MapRecord record = list.get(0); 134 | Map value = record.getValue(); 135 | VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true); 136 | // 3.创建订单 137 | handleVoucherOrder(voucherOrder); 138 | // 4.确认消息 XACK 139 | stringRedisTemplate.opsForStream().acknowledge("s1", "g1", record.getId()); 140 | } catch (Exception e) { 141 | log.error("处理订单异常", e); 142 | 143 | try { 144 | // 稍微休眠一下再进行循环 145 | Thread.sleep(20); 146 | } catch (Exception ex) { 147 | ex.printStackTrace(); 148 | } 149 | 150 | } 151 | } 152 | } 153 | 154 | private void handleVoucherOrder(VoucherOrder voucherOrder) { 155 | Long userId = voucherOrder.getUserId(); 156 | RLock lock = redissonClient.getLock("lock:order:" + userId); 157 | boolean isLocked = lock.tryLock(); 158 | if (!isLocked) { 159 | log.error("不允许重复下单!"); 160 | return; 161 | } 162 | try { 163 | // 该方法非主线程调用,代理对象需要在主线程中获取。 164 | currentProxy.createVoucherOrder(voucherOrder); 165 | } finally { 166 | lock.unlock(); 167 | } 168 | } 169 | 170 | 171 | 172 | /*private BlockingQueue orderTasks = new ArrayBlockingQueue<>(1024 * 1024); 173 | private class VoucherOrderHandler implements Runnable{ 174 | 175 | @Override 176 | public void run() { 177 | while (true){ 178 | try { 179 | // 1.获取队列中的订单信息 180 | VoucherOrder voucherOrder = orderTasks.take(); 181 | // 2.创建订单 182 | createVoucherOrder(voucherOrder); 183 | } catch (Exception e) { 184 | log.error("处理订单异常", e); 185 | } 186 | } 187 | } 188 | }*/ 189 | 190 | @Transactional 191 | @Override 192 | public void createVoucherOrder(VoucherOrder voucherOrder) { 193 | Long userId = voucherOrder.getUserId(); 194 | Long voucherId = voucherOrder.getVoucherId(); 195 | // 创建锁对象 196 | RLock redisLock = redissonClient.getLock("lock:order:" + userId); 197 | // 尝试获取锁 198 | boolean isLock = redisLock.tryLock(); 199 | // 判断 200 | if (!isLock) { 201 | // 获取锁失败,直接返回失败或者重试 202 | log.error("不允许重复下单!"); 203 | return; 204 | } 205 | 206 | try { 207 | // 5.1.查询订单 208 | int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count(); 209 | // 5.2.判断是否存在 210 | if (count > 0) { 211 | // 用户已经购买过了 212 | log.error("不允许重复下单!"); 213 | return; 214 | } 215 | 216 | // 6.扣减库存 217 | boolean success = seckillVoucherService.update() 218 | .setSql("stock = stock - 1") // set stock = stock - 1 219 | .eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0 220 | .update(); 221 | if (!success) { 222 | // 扣减失败 223 | log.error("库存不足!"); 224 | return; 225 | } 226 | 227 | // 7.创建订单 228 | save(voucherOrder); 229 | } finally { 230 | // 释放锁 231 | redisLock.unlock(); 232 | } 233 | } 234 | 235 | @Override 236 | public Result seckillVoucher(Long voucherId) { 237 | Long userId = UserHolder.getUser().getId(); 238 | long orderId = redisIdWorker.nextId("order"); 239 | // 1.执行lua脚本 240 | Long result = stringRedisTemplate.execute( 241 | SECKILL_SCRIPT, 242 | Collections.emptyList(), 243 | voucherId.toString(), userId.toString(), String.valueOf(orderId) 244 | ); 245 | int r = result.intValue(); 246 | // 2.判断结果是否为0 247 | if (r != 0) { 248 | // 2.1.不为0 ,代表没有购买资格 249 | return Result.fail(r == 1 ? "库存不足" : "不能重复下单"); 250 | } 251 | // 3. 获取代理对象 252 | currentProxy = (IVoucherOrderService) AopContext.currentProxy(); 253 | // 4.返回订单id 254 | return Result.ok(orderId); 255 | } 256 | 257 | 258 | /*@Override 259 | public Result seckillVoucher(Long voucherId) { 260 | Long userId = UserHolder.getUser().getId(); 261 | // 1.执行lua脚本 262 | Long result = stringRedisTemplate.execute( 263 | SECKILL_SCRIPT, 264 | Collections.emptyList(), 265 | voucherId.toString(), userId.toString() 266 | ); 267 | int r = result.intValue(); 268 | // 2.判断结果是否为0 269 | if (r != 0) { 270 | // 2.1.不为0 ,代表没有购买资格 271 | return Result.fail(r == 1 ? "库存不足" : "不能重复下单"); 272 | } 273 | // 2.2.为0 ,有购买资格,把下单信息保存到阻塞队列 274 | VoucherOrder voucherOrder = new VoucherOrder(); 275 | // 2.3.订单id 276 | long orderId = redisIdWorker.nextId("order"); 277 | voucherOrder.setId(orderId); 278 | // 2.4.用户id 279 | voucherOrder.setUserId(userId); 280 | // 2.5.代金券id 281 | voucherOrder.setVoucherId(voucherId); 282 | // 2.6.放入阻塞队列 283 | orderTasks.add(voucherOrder); 284 | 285 | // 3.返回订单id 286 | return Result.ok(orderId); 287 | }*/ 288 | /*@Override 289 | public Result seckillVoucher(Long voucherId) { 290 | // 1.查询优惠券 291 | SeckillVoucher voucher = seckillVoucherService.getById(voucherId); 292 | // 2.判断秒杀是否开始 293 | if (voucher.getBeginTime().isAfter(LocalDateTime.now())) { 294 | // 尚未开始 295 | return Result.fail("秒杀尚未开始!"); 296 | } 297 | // 3.判断秒杀是否已经结束 298 | if (voucher.getEndTime().isBefore(LocalDateTime.now())) { 299 | // 尚未开始 300 | return Result.fail("秒杀已经结束!"); 301 | } 302 | // 4.判断库存是否充足 303 | if (voucher.getStock() < 1) { 304 | // 库存不足 305 | return Result.fail("库存不足!"); 306 | } 307 | 308 | return createVoucherOrder(voucherId); 309 | } 310 | 311 | 312 | 313 | @Transactional 314 | public Result createVoucherOrder(Long voucherId) { 315 | // 5.一人一单 316 | Long userId = UserHolder.getUser().getId(); 317 | 318 | // 创建锁对象 319 | RLock redisLock = redissonClient.getLock("lock:order:" + userId); 320 | // 尝试获取锁 321 | boolean isLock = redisLock.tryLock(); 322 | // 判断 323 | if(!isLock){ 324 | // 获取锁失败,直接返回失败或者重试 325 | return Result.fail("不允许重复下单!"); 326 | } 327 | 328 | try { 329 | // 5.1.查询订单 330 | int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count(); 331 | // 5.2.判断是否存在 332 | if (count > 0) { 333 | // 用户已经购买过了 334 | return Result.fail("用户已经购买过一次!"); 335 | } 336 | 337 | // 6.扣减库存 338 | boolean success = seckillVoucherService.update() 339 | .setSql("stock = stock - 1") // set stock = stock - 1 340 | .eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0 341 | .update(); 342 | if (!success) { 343 | // 扣减失败 344 | return Result.fail("库存不足!"); 345 | } 346 | 347 | // 7.创建订单 348 | VoucherOrder voucherOrder = new VoucherOrder(); 349 | // 7.1.订单id 350 | long orderId = redisIdWorker.nextId("order"); 351 | voucherOrder.setId(orderId); 352 | // 7.2.用户id 353 | voucherOrder.setUserId(userId); 354 | // 7.3.代金券id 355 | voucherOrder.setVoucherId(voucherId); 356 | save(voucherOrder); 357 | 358 | // 7.返回订单id 359 | return Result.ok(orderId); 360 | } finally { 361 | // 释放锁 362 | redisLock.unlock(); 363 | } 364 | 365 | }*/ 366 | /*@Transactional 367 | public Result createVoucherOrder(Long voucherId) { 368 | // 5.一人一单 369 | Long userId = UserHolder.getUser().getId(); 370 | 371 | // 创建锁对象 372 | SimpleRedisLock redisLock = new SimpleRedisLock("order:" + userId, stringRedisTemplate); 373 | // 尝试获取锁 374 | boolean isLock = redisLock.tryLock(1200); 375 | // 判断 376 | if(!isLock){ 377 | // 获取锁失败,直接返回失败或者重试 378 | return Result.fail("不允许重复下单!"); 379 | } 380 | 381 | try { 382 | // 5.1.查询订单 383 | int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count(); 384 | // 5.2.判断是否存在 385 | if (count > 0) { 386 | // 用户已经购买过了 387 | return Result.fail("用户已经购买过一次!"); 388 | } 389 | 390 | // 6.扣减库存 391 | boolean success = seckillVoucherService.update() 392 | .setSql("stock = stock - 1") // set stock = stock - 1 393 | .eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0 394 | .update(); 395 | if (!success) { 396 | // 扣减失败 397 | return Result.fail("库存不足!"); 398 | } 399 | 400 | // 7.创建订单 401 | VoucherOrder voucherOrder = new VoucherOrder(); 402 | // 7.1.订单id 403 | long orderId = redisIdWorker.nextId("order"); 404 | voucherOrder.setId(orderId); 405 | // 7.2.用户id 406 | voucherOrder.setUserId(userId); 407 | // 7.3.代金券id 408 | voucherOrder.setVoucherId(voucherId); 409 | save(voucherOrder); 410 | 411 | // 7.返回订单id 412 | return Result.ok(orderId); 413 | } finally { 414 | // 释放锁 415 | redisLock.unlock(); 416 | } 417 | 418 | }*/ 419 | 420 | /*@Transactional 421 | public Result createVoucherOrder(Long voucherId) { 422 | // 5.一人一单 423 | Long userId = UserHolder.getUser().getId(); 424 | 425 | synchronized (userId.toString().intern()) { 426 | // 5.1.查询订单 427 | int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count(); 428 | // 5.2.判断是否存在 429 | if (count > 0) { 430 | // 用户已经购买过了 431 | return Result.fail("用户已经购买过一次!"); 432 | } 433 | 434 | // 6.扣减库存 435 | boolean success = seckillVoucherService.update() 436 | .setSql("stock = stock - 1") // set stock = stock - 1 437 | .eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0 438 | .update(); 439 | if (!success) { 440 | // 扣减失败 441 | return Result.fail("库存不足!"); 442 | } 443 | 444 | // 7.创建订单 445 | VoucherOrder voucherOrder = new VoucherOrder(); 446 | // 7.1.订单id 447 | long orderId = redisIdWorker.nextId("order"); 448 | voucherOrder.setId(orderId); 449 | // 7.2.用户id 450 | voucherOrder.setUserId(userId); 451 | // 7.3.代金券id 452 | voucherOrder.setVoucherId(voucherId); 453 | save(voucherOrder); 454 | 455 | // 7.返回订单id 456 | return Result.ok(orderId); 457 | } 458 | }*/ 459 | } -------------------------------------------------------------------------------- /src/main/resources/db/hmdp.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : local 5 | Source Server Type : MySQL 6 | Source Server Version : 50622 7 | Source Host : localhost:3306 8 | Source Schema : hmdp2 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50622 12 | File Encoding : 65001 13 | 14 | Date: 02/03/2022 23:12:54 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for tb_blog 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `tb_blog`; 24 | CREATE TABLE `tb_blog` ( 25 | `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', 26 | `shop_id` bigint(20) NOT NULL COMMENT '商户id', 27 | `user_id` bigint(20) UNSIGNED NOT NULL COMMENT '用户id', 28 | `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '标题', 29 | `images` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '探店的照片,最多9张,多张以\",\"隔开', 30 | `content` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '探店的文字描述', 31 | `liked` int(8) UNSIGNED NULL DEFAULT 00000000 COMMENT '点赞数量', 32 | `comments` int(8) UNSIGNED NULL DEFAULT NULL COMMENT '评论数量', 33 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 34 | `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 35 | PRIMARY KEY (`id`) USING BTREE 36 | ) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact; 37 | 38 | -- ---------------------------- 39 | -- Records of tb_blog 40 | -- ---------------------------- 41 | INSERT INTO `tb_blog` VALUES (4, 4, 2, '无尽浪漫的夜晚丨在万花丛中摇晃着红酒杯🍷品战斧牛排🥩', '/imgs/blogs/7/14/4771fefb-1a87-4252-816c-9f7ec41ffa4a.jpg,/imgs/blogs/4/10/2f07e3c9-ddce-482d-9ea7-c21450f8d7cd.jpg,/imgs/blogs/2/6/b0756279-65da-4f2d-b62a-33f74b06454a.jpg,/imgs/blogs/10/7/7e97f47d-eb49-4dc9-a583-95faa7aed287.jpg,/imgs/blogs/1/2/4a7b496b-2a08-4af7-aa95-df2c3bd0ef97.jpg,/imgs/blogs/14/3/52b290eb-8b5d-403b-8373-ba0bb856d18e.jpg', '生活就是一半烟火·一半诗意\n手执烟火谋生活·心怀诗意以谋爱·\n当然 男朋友给不了的浪漫要学会自己给🍒\n无法重来的一生·尽量快乐.\n\n🏰「小筑里·神秘浪漫花园餐厅」🏰\n\n💯这是一家最最最美花园的西餐厅·到处都是花餐桌上是花前台是花 美好无处不在\n品一口葡萄酒,维亚红酒马瑟兰·微醺上头工作的疲惫消失无际·生如此多娇🍃\n\n📍地址:延安路200号(家乐福面)\n\n🚌交通:地铁①号线定安路B口出右转过下通道右转就到啦~\n\n--------------🥣菜品详情🥣---------------\n\n「战斧牛排」\n超大一块战斧牛排经过火焰的炙烤发出阵阵香,外焦里嫩让人垂涎欲滴,切开牛排的那一刻,牛排的汁水顺势流了出来,分熟的牛排肉质软,简直细嫩到犯规,一刻都等不了要放入嘴里咀嚼~\n\n「奶油培根意面」\n太太太好吃了💯\n我真的无法形容它的美妙,意面混合奶油香菇的香味真的太太太香了,我真的舔盘了,一丁点美味都不想浪费‼️\n\n「香菜汁烤鲈鱼」\n这个酱是辣的 真的绝好吃‼️\n鲈鱼本身就很嫩没什么刺,烤过之后外皮酥酥的,鱼肉蘸上酱料根本停不下来啊啊啊啊\n能吃辣椒的小伙伴一定要尝尝\n\n 非常可 好吃子🍽\n\n--------------🍃个人感受🍃---------------\n\n【👩🏻‍🍳服务】\n小姐姐特别耐心的给我们介绍彩票 推荐特色菜品,拍照需要帮忙也是尽心尽力配合,太爱他们了\n\n【🍃环境】\n比较有格调的西餐厅 整个餐厅的布局可称得上的万花丛生 有种在人间仙境的感觉🌸\n集美食美酒与鲜花为一体的风格店铺 令人向往\n烟火皆是生活 人间皆是浪漫', 00000013, 00000104, '2021-12-28 19:50:01', '2022-01-06 20:30:03'); 42 | INSERT INTO `tb_blog` VALUES (5, 1, 2, '人均30💰杭州这家港式茶餐厅我疯狂打call‼️', '/imgs/blogs/4/7/863cc302-d150-420d-a596-b16e9232a1a6.jpg,/imgs/blogs/11/12/8b37d208-9414-4e78-b065-9199647bb3e3.jpg,/imgs/blogs/4/1/fa74a6d6-3026-4cb7-b0b6-35abb1e52d11.jpg,/imgs/blogs/9/12/ac2ce2fb-0605-4f14-82cc-c962b8c86688.jpg,/imgs/blogs/4/0/26a7cd7e-6320-432c-a0b4-1b7418f45ec7.jpg,/imgs/blogs/15/9/cea51d9b-ac15-49f6-b9f1-9cf81e9b9c85.jpg', '又吃到一家好吃的茶餐厅🍴环境是怀旧tvb港风📺边吃边拍照片📷几十种菜品均价都在20+💰可以是很平价了!\n·\n店名:九记冰厅(远洋店)\n地址:杭州市丽水路远洋乐堤港负一楼(溜冰场旁边)\n·\n✔️黯然销魂饭(38💰)\n这碗饭我吹爆!米饭上盖满了甜甜的叉烧 还有两颗溏心蛋🍳每一粒米饭都裹着浓郁的酱汁 光盘了\n·\n✔️铜锣湾漏奶华(28💰)\n黄油吐司烤的脆脆的 上面洒满了可可粉🍫一刀切开 奶盖流心像瀑布一样流出来 满足\n·\n✔️神仙一口西多士士(16💰)\n简简单单却超级好吃!西多士烤的很脆 黄油味浓郁 面包体超级柔软 上面淋了炼乳\n·\n✔️怀旧五柳炸蛋饭(28💰)\n四个鸡蛋炸成蓬松的炸蛋!也太好吃了吧!还有大块鸡排 上淋了酸甜的酱汁 太合我胃口了!!\n·\n✔️烧味双拼例牌(66💰)\n选了烧鹅➕叉烧 他家烧腊品质真的惊艳到我!据说是每日广州发货 到店现烧现卖的黑棕鹅 每口都是正宗的味道!肉质很嫩 皮超级超级酥脆!一口爆油!叉烧肉也一点都不柴 甜甜的很入味 搭配梅子酱很解腻 !\n·\n✔️红烧脆皮乳鸽(18.8💰)\n乳鸽很大只 这个价格也太划算了吧, 肉质很有嚼劲 脆皮很酥 越吃越香~\n·\n✔️大满足小吃拼盘(25💰)\n翅尖➕咖喱鱼蛋➕蝴蝶虾➕盐酥鸡\nzui喜欢里面的咖喱鱼!咖喱酱香甜浓郁!鱼蛋很q弹~\n·\n✔️港式熊仔丝袜奶茶(19💰)\n小熊🐻造型的奶茶冰也太可爱了!颜值担当 很地道的丝袜奶茶 茶味特别浓郁~\n·', 00000002, 00000000, '2021-12-28 20:57:49', '2022-01-06 20:30:22'); 43 | INSERT INTO `tb_blog` VALUES (6, 10, 1, '杭州周末好去处|💰50就可以骑马啦🐎', '/imgs/blogs/blog1.jpg', '杭州周末好去处|💰50就可以骑马啦🐎', 00000011, 00000000, '2022-01-11 16:05:47', '2022-01-11 16:05:47'); 44 | INSERT INTO `tb_blog` VALUES (7, 10, 1, '杭州周末好去处|💰50就可以骑马啦🐎', '/imgs/blogs/blog1.jpg', '杭州周末好去处|💰50就可以骑马啦🐎', 00000011, 00000000, '2022-01-11 16:05:47', '2022-01-11 16:05:47'); 45 | 46 | -- ---------------------------- 47 | -- Table structure for tb_blog_comments 48 | -- ---------------------------- 49 | DROP TABLE IF EXISTS `tb_blog_comments`; 50 | CREATE TABLE `tb_blog_comments` ( 51 | `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', 52 | `user_id` bigint(20) UNSIGNED NOT NULL COMMENT '用户id', 53 | `blog_id` bigint(20) UNSIGNED NOT NULL COMMENT '探店id', 54 | `parent_id` bigint(20) UNSIGNED NOT NULL COMMENT '关联的1级评论id,如果是一级评论,则值为0', 55 | `answer_id` bigint(20) UNSIGNED NOT NULL COMMENT '回复的评论id', 56 | `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '回复的内容', 57 | `liked` int(8) UNSIGNED NULL DEFAULT NULL COMMENT '点赞数', 58 | `status` tinyint(1) UNSIGNED NULL DEFAULT NULL COMMENT '状态,0:正常,1:被举报,2:禁止查看', 59 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 60 | `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 61 | PRIMARY KEY (`id`) USING BTREE 62 | ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact; 63 | 64 | -- ---------------------------- 65 | -- Records of tb_blog_comments 66 | -- ---------------------------- 67 | 68 | -- ---------------------------- 69 | -- Table structure for tb_follow 70 | -- ---------------------------- 71 | DROP TABLE IF EXISTS `tb_follow`; 72 | CREATE TABLE `tb_follow` ( 73 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', 74 | `user_id` bigint(20) UNSIGNED NOT NULL COMMENT '用户id', 75 | `follow_user_id` bigint(20) UNSIGNED NOT NULL COMMENT '关联的用户id', 76 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 77 | PRIMARY KEY (`id`) USING BTREE 78 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact; 79 | 80 | -- ---------------------------- 81 | -- Records of tb_follow 82 | -- ---------------------------- 83 | 84 | -- ---------------------------- 85 | -- Table structure for tb_seckill_voucher 86 | -- ---------------------------- 87 | DROP TABLE IF EXISTS `tb_seckill_voucher`; 88 | CREATE TABLE `tb_seckill_voucher` ( 89 | `voucher_id` bigint(20) UNSIGNED NOT NULL COMMENT '关联的优惠券的id', 90 | `stock` int(8) NOT NULL COMMENT '库存', 91 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 92 | `begin_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '生效时间', 93 | `end_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '失效时间', 94 | `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 95 | PRIMARY KEY (`voucher_id`) USING BTREE 96 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '秒杀优惠券表,与优惠券是一对一关系' ROW_FORMAT = Compact; 97 | 98 | -- ---------------------------- 99 | -- Records of tb_seckill_voucher 100 | -- ---------------------------- 101 | 102 | -- ---------------------------- 103 | -- Table structure for tb_shop 104 | -- ---------------------------- 105 | DROP TABLE IF EXISTS `tb_shop`; 106 | CREATE TABLE `tb_shop` ( 107 | `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', 108 | `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '商铺名称', 109 | `type_id` bigint(20) UNSIGNED NOT NULL COMMENT '商铺类型的id', 110 | `images` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '商铺图片,多个图片以\',\'隔开', 111 | `area` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商圈,例如陆家嘴', 112 | `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '地址', 113 | `x` double UNSIGNED NOT NULL COMMENT '经度', 114 | `y` double UNSIGNED NOT NULL COMMENT '维度', 115 | `avg_price` bigint(10) UNSIGNED NULL DEFAULT NULL COMMENT '均价,取整数', 116 | `sold` int(10) UNSIGNED NOT NULL COMMENT '销量', 117 | `comments` int(10) UNSIGNED NOT NULL COMMENT '评论数量', 118 | `score` int(2) UNSIGNED NOT NULL COMMENT '评分,1~5分,乘10保存,避免小数', 119 | `open_hours` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '营业时间,例如 10:00-22:00', 120 | `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 121 | `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 122 | PRIMARY KEY (`id`) USING BTREE, 123 | INDEX `foreign_key_type`(`type_id`) USING BTREE 124 | ) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact; 125 | 126 | -- ---------------------------- 127 | -- Records of tb_shop 128 | -- ---------------------------- 129 | INSERT INTO `tb_shop` VALUES (1, '103茶餐厅', 1, 'https://qcloud.dpfile.com/pc/jiclIsCKmOI2arxKN1Uf0Hx3PucIJH8q0QSz-Z8llzcN56-_QiKuOvyio1OOxsRtFoXqu0G3iT2T27qat3WhLVEuLYk00OmSS1IdNpm8K8sG4JN9RIm2mTKcbLtc2o2vfCF2ubeXzk49OsGrXt_KYDCngOyCwZK-s3fqawWswzk.jpg,https://qcloud.dpfile.com/pc/IOf6VX3qaBgFXFVgp75w-KKJmWZjFc8GXDU8g9bQC6YGCpAmG00QbfT4vCCBj7njuzFvxlbkWx5uwqY2qcjixFEuLYk00OmSS1IdNpm8K8sG4JN9RIm2mTKcbLtc2o2vmIU_8ZGOT1OjpJmLxG6urQ.jpg', '大关', '金华路锦昌文华苑29号', 120.149192, 30.316078, 80, 0000004215, 0000003035, 37, '10:00-22:00', '2021-12-22 18:10:39', '2022-01-13 17:32:19'); 130 | INSERT INTO `tb_shop` VALUES (2, '蔡馬洪涛烤肉·老北京铜锅涮羊肉', 1, 'https://p0.meituan.net/bbia/c1870d570e73accbc9fee90b48faca41195272.jpg,http://p0.meituan.net/mogu/397e40c28fc87715b3d5435710a9f88d706914.jpg,https://qcloud.dpfile.com/pc/MZTdRDqCZdbPDUO0Hk6lZENRKzpKRF7kavrkEI99OxqBZTzPfIxa5E33gBfGouhFuzFvxlbkWx5uwqY2qcjixFEuLYk00OmSS1IdNpm8K8sG4JN9RIm2mTKcbLtc2o2vmIU_8ZGOT1OjpJmLxG6urQ.jpg', '拱宸桥/上塘', '上塘路1035号(中国工商银行旁)', 120.151505, 30.333422, 85, 0000002160, 0000001460, 46, '11:30-03:00', '2021-12-22 19:00:13', '2022-01-11 16:12:26'); 131 | INSERT INTO `tb_shop` VALUES (3, '新白鹿餐厅(运河上街店)', 1, 'https://p0.meituan.net/biztone/694233_1619500156517.jpeg,https://img.meituan.net/msmerchant/876ca8983f7395556eda9ceb064e6bc51840883.png,https://img.meituan.net/msmerchant/86a76ed53c28eff709a36099aefe28b51554088.png', '运河上街', '台州路2号运河上街购物中心F5', 120.151954, 30.32497, 61, 0000012035, 0000008045, 47, '10:30-21:00', '2021-12-22 19:10:05', '2022-01-11 16:12:42'); 132 | INSERT INTO `tb_shop` VALUES (4, 'Mamala(杭州远洋乐堤港店)', 1, 'https://img.meituan.net/msmerchant/232f8fdf09050838bd33fb24e79f30f9606056.jpg,https://qcloud.dpfile.com/pc/rDe48Xe15nQOHCcEEkmKUp5wEKWbimt-HDeqYRWsYJseXNncvMiXbuED7x1tXqN4uzFvxlbkWx5uwqY2qcjixFEuLYk00OmSS1IdNpm8K8sG4JN9RIm2mTKcbLtc2o2vmIU_8ZGOT1OjpJmLxG6urQ.jpg', '拱宸桥/上塘', '丽水路66号远洋乐堤港商城2期1层B115号', 120.146659, 30.312742, 290, 0000013519, 0000009529, 49, '11:00-22:00', '2021-12-22 19:17:15', '2022-01-11 16:12:51'); 133 | INSERT INTO `tb_shop` VALUES (5, '海底捞火锅(水晶城购物中心店)', 1, 'https://img.meituan.net/msmerchant/054b5de0ba0b50c18a620cc37482129a45739.jpg,https://img.meituan.net/msmerchant/59b7eff9b60908d52bd4aea9ff356e6d145920.jpg,https://qcloud.dpfile.com/pc/Qe2PTEuvtJ5skpUXKKoW9OQ20qc7nIpHYEqJGBStJx0mpoyeBPQOJE4vOdYZwm9AuzFvxlbkWx5uwqY2qcjixFEuLYk00OmSS1IdNpm8K8sG4JN9RIm2mTKcbLtc2o2vmIU_8ZGOT1OjpJmLxG6urQ.jpg', '大关', '上塘路458号水晶城购物中心F6', 120.15778, 30.310633, 104, 0000004125, 0000002764, 49, '10:00-07:00', '2021-12-22 19:20:58', '2022-01-11 16:13:01'); 134 | INSERT INTO `tb_shop` VALUES (6, '幸福里老北京涮锅(丝联店)', 1, 'https://img.meituan.net/msmerchant/e71a2d0d693b3033c15522c43e03f09198239.jpg,https://img.meituan.net/msmerchant/9f8a966d60ffba00daf35458522273ca658239.jpg,https://img.meituan.net/msmerchant/ef9ca5ef6c05d381946fe4a9aa7d9808554502.jpg', '拱宸桥/上塘', '金华南路189号丝联166号', 120.148603, 30.318618, 130, 0000009531, 0000007324, 46, '11:00-13:50,17:00-20:50', '2021-12-22 19:24:53', '2022-01-11 16:13:09'); 135 | INSERT INTO `tb_shop` VALUES (7, '炉鱼(拱墅万达广场店)', 1, 'https://img.meituan.net/msmerchant/909434939a49b36f340523232924402166854.jpg,https://img.meituan.net/msmerchant/32fd2425f12e27db0160e837461c10303700032.jpg,https://img.meituan.net/msmerchant/f7022258ccb8dabef62a0514d3129562871160.jpg', '北部新城', '杭行路666号万达商业中心4幢2单元409室(铺位号4005)', 120.124691, 30.336819, 85, 0000002631, 0000001320, 47, '00:00-24:00', '2021-12-22 19:40:52', '2022-01-11 16:13:19'); 136 | INSERT INTO `tb_shop` VALUES (8, '浅草屋寿司(运河上街店)', 1, 'https://img.meituan.net/msmerchant/cf3dff697bf7f6e11f4b79c4e7d989e4591290.jpg,https://img.meituan.net/msmerchant/0b463f545355c8d8f021eb2987dcd0c8567811.jpg,https://img.meituan.net/msmerchant/c3c2516939efaf36c4ccc64b0e629fad587907.jpg', '运河上街', '拱墅区金华路80号运河上街B1', 120.150526, 30.325231, 88, 0000002406, 0000001206, 46, ' 11:00-21:30', '2021-12-22 19:51:06', '2022-01-11 16:13:25'); 137 | INSERT INTO `tb_shop` VALUES (9, '羊老三羊蝎子牛仔排北派炭火锅(运河上街店)', 1, 'https://p0.meituan.net/biztone/163160492_1624251899456.jpeg,https://img.meituan.net/msmerchant/e478eb16f7e31a7f8b29b5e3bab6de205500837.jpg,https://img.meituan.net/msmerchant/6173eb1d18b9d70ace7fdb3f2dd939662884857.jpg', '运河上街', '台州路2号运河上街购物中心F5', 120.150598, 30.325251, 101, 0000002763, 0000001363, 44, '11:00-21:30', '2021-12-22 19:53:59', '2022-01-11 16:13:34'); 138 | INSERT INTO `tb_shop` VALUES (10, '开乐迪KTV(运河上街店)', 2, 'https://p0.meituan.net/joymerchant/a575fd4adb0b9099c5c410058148b307-674435191.jpg,https://p0.meituan.net/merchantpic/68f11bf850e25e437c5f67decfd694ab2541634.jpg,https://p0.meituan.net/dpdeal/cb3a12225860ba2875e4ea26c6d14fcc197016.jpg', '运河上街', '台州路2号运河上街购物中心F4', 120.149093, 30.324666, 67, 0000026891, 0000000902, 37, '00:00-24:00', '2021-12-22 20:25:16', '2021-12-22 20:25:16'); 139 | INSERT INTO `tb_shop` VALUES (11, 'INLOVE KTV(水晶城店)', 2, 'https://p0.meituan.net/dpmerchantpic/53e74b200211d68988a4f02ae9912c6c1076826.jpg,https://qcloud.dpfile.com/pc/4iWtIvzLzwM2MGgyPu1PCDb4SWEaKqUeHm--YAt1EwR5tn8kypBcqNwHnjg96EvT_Gd2X_f-v9T8Yj4uLt25Gg.jpg,https://qcloud.dpfile.com/pc/WZsJWRI447x1VG2x48Ujgu7vwqksi_9WitdKI4j3jvIgX4MZOpGNaFtM93oSSizbGybIjx5eX6WNgCPvcASYAw.jpg', '水晶城', '上塘路458号水晶城购物中心6层', 120.15853, 30.310002, 75, 0000035977, 0000005684, 47, '11:30-06:00', '2021-12-22 20:29:02', '2021-12-22 20:39:00'); 140 | INSERT INTO `tb_shop` VALUES (12, '魅(杭州远洋乐堤港店)', 2, 'https://p0.meituan.net/dpmerchantpic/63833f6ba0393e2e8722420ef33f3d40466664.jpg,https://p0.meituan.net/dpmerchantpic/ae3c94cc92c529c4b1d7f68cebed33fa105810.png,', '远洋乐堤港', '丽水路58号远洋乐堤港F4', 120.14983, 30.31211, 88, 0000006444, 0000000235, 46, '10:00-02:00', '2021-12-22 20:34:34', '2021-12-22 20:34:34'); 141 | INSERT INTO `tb_shop` VALUES (13, '讴K拉量贩KTV(北城天地店)', 2, 'https://p1.meituan.net/merchantpic/598c83a8c0d06fe79ca01056e214d345875600.jpg,https://qcloud.dpfile.com/pc/HhvI0YyocYHRfGwJWqPQr34hRGRl4cWdvlNwn3dqghvi4WXlM2FY1te0-7pE3Wb9_Gd2X_f-v9T8Yj4uLt25Gg.jpg,https://qcloud.dpfile.com/pc/F5ZVzZaXFE27kvQzPnaL4V8O9QCpVw2nkzGrxZE8BqXgkfyTpNExfNG5CEPQX4pjGybIjx5eX6WNgCPvcASYAw.jpg', 'D32天阳购物中心', '湖州街567号北城天地5层', 120.130453, 30.327655, 58, 0000018997, 0000001857, 41, '12:00-02:00', '2021-12-22 20:38:54', '2021-12-22 20:40:04'); 142 | INSERT INTO `tb_shop` VALUES (14, '星聚会KTV(拱墅区万达店)', 2, 'https://p0.meituan.net/dpmerchantpic/f4cd6d8d4eb1959c3ea826aa05a552c01840451.jpg,https://p0.meituan.net/dpmerchantpic/2efc07aed856a8ab0fc75c86f4b9b0061655777.jpg,https://qcloud.dpfile.com/pc/zWfzzIorCohKT0bFwsfAlHuayWjI6DBEMPHHncmz36EEMU9f48PuD9VxLLDAjdoU_Gd2X_f-v9T8Yj4uLt25Gg.jpg', '北部新城', '杭行路666号万达广场C座1-2F', 120.128958, 30.337252, 60, 0000017771, 0000000685, 47, '10:00-22:00', '2021-12-22 20:48:54', '2021-12-22 20:48:54'); 143 | 144 | -- ---------------------------- 145 | -- Table structure for tb_shop_type 146 | -- ---------------------------- 147 | DROP TABLE IF EXISTS `tb_shop_type`; 148 | CREATE TABLE `tb_shop_type` ( 149 | `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', 150 | `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '类型名称', 151 | `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标', 152 | `sort` int(3) UNSIGNED NULL DEFAULT NULL COMMENT '顺序', 153 | `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 154 | `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 155 | PRIMARY KEY (`id`) USING BTREE 156 | ) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact; 157 | 158 | -- ---------------------------- 159 | -- Records of tb_shop_type 160 | -- ---------------------------- 161 | INSERT INTO `tb_shop_type` VALUES (1, '美食', '/types/ms.png', 1, '2021-12-22 20:17:47', '2021-12-23 11:24:31'); 162 | INSERT INTO `tb_shop_type` VALUES (2, 'KTV', '/types/KTV.png', 2, '2021-12-22 20:18:27', '2021-12-23 11:24:31'); 163 | INSERT INTO `tb_shop_type` VALUES (3, '丽人·美发', '/types/lrmf.png', 3, '2021-12-22 20:18:48', '2021-12-23 11:24:31'); 164 | INSERT INTO `tb_shop_type` VALUES (4, '健身运动', '/types/jsyd.png', 10, '2021-12-22 20:19:04', '2021-12-23 11:24:31'); 165 | INSERT INTO `tb_shop_type` VALUES (5, '按摩·足疗', '/types/amzl.png', 5, '2021-12-22 20:19:27', '2021-12-23 11:24:31'); 166 | INSERT INTO `tb_shop_type` VALUES (6, '美容SPA', '/types/spa.png', 6, '2021-12-22 20:19:35', '2021-12-23 11:24:31'); 167 | INSERT INTO `tb_shop_type` VALUES (7, '亲子游乐', '/types/qzyl.png', 7, '2021-12-22 20:19:53', '2021-12-23 11:24:31'); 168 | INSERT INTO `tb_shop_type` VALUES (8, '酒吧', '/types/jiuba.png', 8, '2021-12-22 20:20:02', '2021-12-23 11:24:31'); 169 | INSERT INTO `tb_shop_type` VALUES (9, '轰趴馆', '/types/hpg.png', 9, '2021-12-22 20:20:08', '2021-12-23 11:24:31'); 170 | INSERT INTO `tb_shop_type` VALUES (10, '美睫·美甲', '/types/mjmj.png', 4, '2021-12-22 20:21:46', '2021-12-23 11:24:31'); 171 | 172 | -- ---------------------------- 173 | -- Table structure for tb_user 174 | -- ---------------------------- 175 | DROP TABLE IF EXISTS `tb_user`; 176 | CREATE TABLE `tb_user` ( 177 | `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', 178 | `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '手机号码', 179 | `password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密码,加密存储', 180 | `nick_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '昵称,默认是用户id', 181 | `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '人物头像', 182 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 183 | `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 184 | PRIMARY KEY (`id`) USING BTREE, 185 | UNIQUE INDEX `uniqe_key_phone`(`phone`) USING BTREE 186 | ) ENGINE = InnoDB AUTO_INCREMENT = 1010 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact; 187 | 188 | -- ---------------------------- 189 | -- Records of tb_user 190 | -- ---------------------------- 191 | INSERT INTO `tb_user` VALUES (1, '13686869696', '', '小鱼同学', '/imgs/blogs/blog1.jpg', '2021-12-24 10:27:19', '2022-01-11 16:04:00'); 192 | INSERT INTO `tb_user` VALUES (2, '13838411438', '', '可可今天不吃肉', '/imgs/icons/kkjtbcr.jpg', '2021-12-24 15:14:39', '2021-12-28 19:58:04'); 193 | INSERT INTO `tb_user` VALUES (4, '13456789011', '', 'user_slxaxy2au9f3tanffaxr', '', '2022-01-07 12:07:53', '2022-01-07 12:07:53'); 194 | INSERT INTO `tb_user` VALUES (5, '13456789001', '', 'user_n0bb8mwwg4', '', '2022-01-07 16:11:33', '2022-01-07 16:11:33'); 195 | 196 | -- ---------------------------- 197 | -- Table structure for tb_user_info 198 | -- ---------------------------- 199 | DROP TABLE IF EXISTS `tb_user_info`; 200 | CREATE TABLE `tb_user_info` ( 201 | `user_id` bigint(20) UNSIGNED NOT NULL COMMENT '主键,用户id', 202 | `city` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '城市名称', 203 | `introduce` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '个人介绍,不要超过128个字符', 204 | `fans` int(8) UNSIGNED NULL DEFAULT 0 COMMENT '粉丝数量', 205 | `followee` int(8) UNSIGNED NULL DEFAULT 0 COMMENT '关注的人的数量', 206 | `gender` tinyint(1) UNSIGNED NULL DEFAULT 0 COMMENT '性别,0:男,1:女', 207 | `birthday` date NULL DEFAULT NULL COMMENT '生日', 208 | `credits` int(8) UNSIGNED NULL DEFAULT 0 COMMENT '积分', 209 | `level` tinyint(1) UNSIGNED NULL DEFAULT 0 COMMENT '会员级别,0~9级,0代表未开通会员', 210 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 211 | `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 212 | PRIMARY KEY (`user_id`) USING BTREE 213 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact; 214 | 215 | -- ---------------------------- 216 | -- Records of tb_user_info 217 | -- ---------------------------- 218 | 219 | -- ---------------------------- 220 | -- Table structure for tb_voucher 221 | -- ---------------------------- 222 | DROP TABLE IF EXISTS `tb_voucher`; 223 | CREATE TABLE `tb_voucher` ( 224 | `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', 225 | `shop_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '商铺id', 226 | `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '代金券标题', 227 | `sub_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '副标题', 228 | `rules` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '使用规则', 229 | `pay_value` bigint(10) UNSIGNED NOT NULL COMMENT '支付金额,单位是分。例如200代表2元', 230 | `actual_value` bigint(10) NOT NULL COMMENT '抵扣金额,单位是分。例如200代表2元', 231 | `type` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '0,普通券;1,秒杀券', 232 | `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '1,上架; 2,下架; 3,过期', 233 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 234 | `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 235 | PRIMARY KEY (`id`) USING BTREE 236 | ) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact; 237 | 238 | -- ---------------------------- 239 | -- Records of tb_voucher 240 | -- ---------------------------- 241 | INSERT INTO `tb_voucher` VALUES (1, 1, '50元代金券', '周一至周日均可使用', '全场通用\\n无需预约\\n可无限叠加\\不兑现、不找零\\n仅限堂食', 4750, 5000, 0, 1, '2022-01-04 09:42:39', '2022-01-04 09:43:31'); 242 | 243 | -- ---------------------------- 244 | -- Table structure for tb_voucher_order 245 | -- ---------------------------- 246 | DROP TABLE IF EXISTS `tb_voucher_order`; 247 | CREATE TABLE `tb_voucher_order` ( 248 | `id` bigint(20) NOT NULL COMMENT '主键', 249 | `user_id` bigint(20) UNSIGNED NOT NULL COMMENT '下单的用户id', 250 | `voucher_id` bigint(20) UNSIGNED NOT NULL COMMENT '购买的代金券id', 251 | `pay_type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '支付方式 1:余额支付;2:支付宝;3:微信', 252 | `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '订单状态,1:未支付;2:已支付;3:已核销;4:已取消;5:退款中;6:已退款', 253 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间', 254 | `pay_time` timestamp NULL DEFAULT NULL COMMENT '支付时间', 255 | `use_time` timestamp NULL DEFAULT NULL COMMENT '核销时间', 256 | `refund_time` timestamp NULL DEFAULT NULL COMMENT '退款时间', 257 | `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 258 | PRIMARY KEY (`id`) USING BTREE 259 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact; 260 | 261 | -- ---------------------------- 262 | -- Records of tb_voucher_order 263 | -- ---------------------------- 264 | 265 | SET FOREIGN_KEY_CHECKS = 1; 266 | --------------------------------------------------------------------------------