暂无
Redis是一个基于内存的 key-value 结构数据库。
基于内存存储,读写性能高适合存储热点数据(热点商品、资讯、新闻)企业应用广泛官网:https://redis.io
中文网:Redis中文网
服务启动命令:redis-server.exe redis.windows.conf
Redis服务默认端口号为 6379 ,通过快捷键Ctrl + C 即可停止Redis服务
客户端连接命令:redis-cli.exe
通过redis-cli.exe命令默认连接的是本地的redis服务,并且使用默认6379端口。也可以通过指定如下参数连接:
-h ip地址
-p 端口号
-a 密码(如果需要)
Redis存储的是key-value结构的数据,其中key是字符串类型,value有5种常用的数据类型:
• 字符串 string• 哈希 hash• 列表 list• 集合 set• 有序集合 sorted set / zset
• 字符串(string):普通字符串, Redis 中最简单的数据类型•• 哈希(hash):也叫散列,类似于 Java 中的 HashMap 结构•• 列表(list):按照插入顺序排序,可以有重复元素,类似于 Java 中的 LinkedList•• 集合(set):无序集合,没有重复元素,类似于 Java 中的 HashSet•• 有序集合(sorted set / zset):集合中每个元素关联一个分数(score),根据分数升序排序,没有重复元素
Redis 字符串类型常用命令:
SET key value 设置指定key的值
GET key 获取指定key的值
SETEX key seconds value 设置指定key的值,并将 key 的过期时间设为 seconds 秒
SETNX key value 只有在 key 不存在时设置 key 的值
Redis hash 是一个string类型的 field 和 value 的映射表,hash特别适合用于存储对象,常用命令:
HSET key field value 将哈希表 key 中的字段 field 的值设为 value
HGET key field 获取存储在哈希表中指定字段的值
HDEL key field 删除存储在哈希表中的指定字段
HKEYS key 获取哈希表中所有字段
HVALS key 获取哈希表中所有值
Redis 列表是简单的字符串列表,按照插入顺序排序,常用命令:
LPUSH key value1 [value2] 将一个或多个值插入到列表头部(左边)
LRANGE key start stop 获取列表指定范围内的元素
RPOP key 移除并获取列表最后一个元素(右边)
LLEN key 获取列表长度
Redis set 是string类型的无序集合。集合成员是唯一的,集合中不能出现重复的数据,常用命令:
SADD key member1 [member2] 向集合添加一个或多个成员
SMEMBERS key 返回集合中的所有成员
SCARD key 获取集合的成员数
SINTER key1 [key2] 返回给定所有集合的交集
SUNION key1 [key2] 返回所有给定集合的并集
SREM key member1 [member2] 删除集合中一个或多个成员
Redis有序集合是string类型元素的集合,且不允许有重复成员。每个元素都会关联一个double类型的分数。常用命令:
ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员
ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合中指定区间内的成员
ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment
ZREM key member [member ...] 移除有序集合中的一个或多个成员
Redis的通用命令是不分数据类型的,都可以使用的命令:
KEYS pattern 查找所有符合给定模式( pattern)的 key
EXISTS key 检查给定 key 是否存在
TYPE key 返回 key 所储存的值的类型
DEL key 该命令用于在 key 存在是删除 key
Redis 的 Java 客户端很多,常用的几种:
Jedis
Lettuce
Spring Data Redis
Spring Data Redis 是 Spring 的一部分,对 Redis 底层开发包进行了高度封装。
在 Spring 项目中,可以使用Spring Data Redis来简化操作。
操作步骤:
① 导入 Spring Data Redis 的 maven 坐标② 配置 Redis 数据源③ 编写配置类,创建 RedisTemplate 对象④ 通过 RedisTemplate 对象操作 Redis
org.springframework.boot
spring-boot-starter-data-redis
package com.sky.config;/*
*
* @author pengjx
*
* */
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@Slf4j
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate redisTemplate = new RedisTemplate();
//设置Redis的连接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置Redis中key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
package com.sky.test;/*
*
* @author pengjx
*
* */
import com.sky.config.RedisConfiguration;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.*;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
//@SpringBootTest
public class SpringDataRedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testRedisTemplate(){
System.out.println(redisTemplate);
ValueOperations valueOperations = redisTemplate.opsForValue();
HashOperations hashOperations = redisTemplate.opsForHash();
ListOperations listOperations = redisTemplate.opsForList();
SetOperations setOperations = redisTemplate.opsForSet();
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
}
@Test
public void testString(){
//set get setnx setex
redisTemplate.opsForValue().set("city","赣州");
String city = (String) redisTemplate.opsForValue().get("city");
System.out.println(city);
redisTemplate.opsForValue().set("code","1234",2L, TimeUnit.MINUTES);
redisTemplate.opsForValue().setIfAbsent("gender",12);
}
@Test
public void testHash(){
redisTemplate.opsForHash().put("100","name","tom");
redisTemplate.opsForHash().put("100","age",12);
String name = (String) redisTemplate.opsForHash().get("100", "name");
System.out.println(name);
Integer age = (Integer) redisTemplate.opsForHash().get("100", "age");
System.out.println(age);
Set keys = redisTemplate.opsForHash().keys("100");
System.out.println(keys);
List values = redisTemplate.opsForHash().values("100");
System.out.println(values);
redisTemplate.opsForHash().delete("100","name");
}
@Test
public void testList(){
//lpush rpop llen lrange
ListOperations listOperations = redisTemplate.opsForList();
listOperations.rightPushAll("mylist", 'a', 'b', 'c', 'd');
listOperations.leftPush("mylist",'e');
List mylist = listOperations.range("mylist", 0, -1);
System.out.println(mylist);
Long size = listOperations.size("mylist");
System.out.println(size);
listOperations.remove("mylist",1,'b');
List range = listOperations.range("mylist", 0, -1);
System.out.println(range);
}
@Test
public void testSet(){
//sadd smembers scard sinter sunion srem
SetOperations setOperations = redisTemplate.opsForSet();
setOperations.add("set1",'a','b','c','d');
setOperations.add("set2",'a','b','x','y');
Set set1 = setOperations.members("set1");
System.out.println(set1);
Long size = setOperations.size("set1");
System.out.println(size);
Set intersect = setOperations.intersect("set1", "set2");
System.out.println(intersect);
Set union = setOperations.union("set1", "set2");
System.out.println(union);
setOperations.remove("set1",'a');
}
@Test
public void testZset(){
// zadd zrange zcard zrange zincrby
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
zSetOperations.add("zset1",'a',10);
zSetOperations.add("zset1",'b',9);
zSetOperations.add("zset1",'c',7);
Set zset1 = zSetOperations.range("zset1", 0, -1);
System.out.println(zset1);
Long size = zSetOperations.size("zset1");
System.out.println(size);
zSetOperations.incrementScore("zset1",'c',3);
Set range = zSetOperations.range("zset1", 0, -1);
System.out.println(range);
}
@Test
public void testCommon(){
//keys exists type del
Set keys = redisTemplate.keys("*");
System.out.println(keys);
Boolean hasKey = redisTemplate.hasKey("100");
System.out.println(hasKey);
keys.forEach(key->{
DataType type = redisTemplate.type(key);
System.out.println(type);
});
}
}
接口设计:
• 设置营业状态• 管理端查询营业状态• 用户端查询营业状态
本项目约定:
• 管理端 发出的请求,统一使用 /admin 作为前缀• 用户端 发出的请求,统一使用 /user 作为前缀
营业状态数据存储方式:基于Redis的字符串来进行存储
约定:1表示营业 0表示打烊
package com.sky.controller.admin;/*
*
* @author pengjx
*
* */
import com.sky.result.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
@RestController("adminShopController")
@RequestMapping("/admin/shop")
@Slf4j
@Api(tags = "店铺相关接口")
public class ShopController {
@Autowired
private RedisTemplate redisTemplate;
@PutMapping("/{status}")
@ApiOperation("设置店铺营业状态")
public Result setStatus(@PathVariable Integer status){
log.info("设置店铺营业状态:{}",status==1?"营业中":"打烊");
redisTemplate.opsForValue().set("SHOP_STATUS",status);
return Result.success();
}
@GetMapping("status")
@ApiOperation("查询店铺营业状态")
public Result getStatus(){
Integer status = (Integer) redisTemplate.opsForValue().get("SHOP_STATUS");
log.info("当前店铺营业状态:{}",status==1?"营业中":"打烊");
return Result.success(status);
}
}
package com.sky.controller.user;/*
*
* @author pengjx
*
* */
import com.sky.result.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
@RestController("userShopController")
@RequestMapping("/user/shop")
@Slf4j
@Api(tags = "店铺相关接口")
public class ShopController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("status")
@ApiOperation("查询店铺营业状态")
public Result getStatus(){
Integer status = (Integer) redisTemplate.opsForValue().get("SHOP_STATUS");
log.info("当前店铺营业状态:{}",status==1?"营业中":"打烊");
return Result.success(status);
}
}
HttpClient是Apache的一个子项目,是高效的、功能丰富的支持HTTP协议的客户端编程工具包。
HttpClient作用:
• 发送 HTTP 请求• 接收响应数据
HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。
org.apache.httpcomponents httpclient 4.5.13 核心API:
• HttpClient• HttpClients• CloseableHttpClient• HttpGet• HttpPost发送请求步骤:
• 创建 HttpClient 对象• 创建 Http 请求对象• 调用 HttpClient 的 execute 方法发送请求
package com.sky.test;/*
*
* @author pengjx
*
* */
import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
@SpringBootTest
public class HttpClientTest {
@Test
public void testGet() throws Exception {
//http客户端对象,可以发送http请求
CloseableHttpClient httpClient = HttpClients.createDefault();
//构造Get请求
HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");
//发送请求
CloseableHttpResponse response = httpClient.execute(httpGet);
//http响应码
int statusCode = response.getStatusLine().getStatusCode();
System.out.println(statusCode);
//http相应体
HttpEntity entity = response.getEntity();
//将响应体转换为String对象
String body = EntityUtils.toString(entity);
System.out.println(body);
response.close();
httpClient.close();
}
@Test
public void testPost() throws Exception{
//创建HttpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建post请求对象
HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login");
//JSONObject
JSONObject jsonObject = new JSONObject();
jsonObject.put("username","admin");
jsonObject.put("password","123456");
StringEntity entity = new StringEntity(jsonObject.toString());
//指定编码格式
entity.setContentEncoding("utf-8");
//指定数据格式
entity.setContentType("application/json");
httpPost.setEntity(entity);
//发送请求
CloseableHttpResponse response = httpClient.execute(httpPost);
int statusCode = response.getStatusLine().getStatusCode();
System.out.println(statusCode);
HttpEntity entity1 = response.getEntity();
String body = EntityUtils.toString(entity1);
System.out.println(body);
response.close();
httpClient.close();
}
}
• 了解小程序目录结构
{{msg}}
昵称:{{nickname}}
头像:
{{code}}
// index.js
Page({
data:{
msg:"Hello World",
nickname:'',
url:'',
code:''
},
getUserInfo(){
wx.getUserProfile({
desc: '获取用户信息',
success:(res)=>{
console.log(res.userInfo)
this.setData({
nickname:res.userInfo.nickName,
url:res.userInfo.avatarUrl
})
}
})
},
wxLogin(){
wx.login({
success: (res) => {
console.log(res.code)
this.setData({
code:res.code
})
},
})
},
sendRequest(){
wx.request({
url: 'http://localhost:8080/user/shop/status',
success:(res)=>{
console.log(res.data)
}
})
}
})
业务规则:
接口设计
数据库设计(user表):
字段名 |
数据类型 |
说明 |
备注 |
id |
bigint |
主键 |
自增 |
openid |
varchar(45) |
微信用户的唯一标识 |
|
name |
varchar(32) |
用户姓名 |
|
phone |
varchar(11) |
手机号 |
|
sex |
varchar(2) |
性别 |
|
id_number |
varchar(18) |
身份证号 |
|
avatar |
varchar(500) |
微信用户头像路径 |
|
create_time |
datetime |
注册时间 |
配置微信登录所需配置项:
wechat:
appid: ${sky.wechat.appid}
secret: ${sky.wechat.secret}
配置为微信用户生成jwt令牌时使用的配置项:
package com.sky.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "sky.jwt")
@Data
public class JwtProperties {
/**
* 管理端员工生成jwt令牌相关配置
*/
private String adminSecretKey;
private long adminTtl;
private String adminTokenName;
/**
* 用户端微信用户生成jwt令牌相关配置
*/
private String userSecretKey;
private long userTtl;
private String userTokenName;
}
根据接口定义创建UserController的login方法:
package com.sky.controller.user;/*
*
* @author pengjx
*
* */
import com.sky.constant.JwtClaimsConstant;
import com.sky.dto.UserLoginDTO;
import com.sky.entity.User;
import com.sky.properties.JwtProperties;
import com.sky.result.Result;
import com.sky.service.UserService;
import com.sky.utils.JwtUtil;
import com.sky.vo.UserLoginVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController("userUserController")
@RequestMapping("/user/user")
@Slf4j
@Api(tags = "微信用户接口")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private JwtProperties jwtProperties;
/**
* @description: 微信登录
* @date: 2023/12/21 8:56
* @param: userLoginDTO
* @return: com.sky.result.Result
**/
@PostMapping("/login")
@ApiOperation("用户登录")
public Result login(@RequestBody UserLoginDTO userLoginDTO){
log.info("用户登录:{}",userLoginDTO.getCode());
User user = userService.wxLogin(userLoginDTO);
Map map=new HashMap<>();
map.put(JwtClaimsConstant.USER_ID,user.getId());
String jwt = JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), map);
UserLoginVO build = UserLoginVO.builder()
.id(user.getId())
.openid(user.getOpenid())
.token(jwt)
.build();
return Result.success(build);
}
}
创建UserService接口:
package com.sky.service;/*
*
* @author pengjx
*
* */
import com.sky.dto.UserLoginDTO;
import com.sky.entity.User;
import com.sky.vo.UserLoginVO;
import org.springframework.stereotype.Service;
@Service
public interface UserService {
User wxLogin(UserLoginDTO userLoginDTO);
}
创建UserServiceImpl实现类,在UserServiceImpl中创建私有方法getOpenid,完善UserServiceImpl的wxLogin方法::
package com.sky.service.impl;/*
*
* @author pengjx
*
* */
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.constant.MessageConstant;
import com.sky.dto.UserLoginDTO;
import com.sky.entity.User;
import com.sky.exception.LoginFailedException;
import com.sky.mapper.UserMapper;
import com.sky.properties.WeChatProperties;
import com.sky.service.UserService;
import com.sky.utils.HttpClientUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@Service
@Slf4j
public class UserServiceImpl implements UserService {
private static final String WX_LOGIN="https://api.weixin.qq.com/sns/jscode2session";
@Autowired
private WeChatProperties weChatProperties;
@Autowired
private UserMapper userMapper;
/**
* @description:微信登录
* @date: 2023/12/21 8:57
* @param: userLoginDTO
* @return: com.sky.entity.User
**/
@Override
public User wxLogin(UserLoginDTO userLoginDTO) {
//调用微信接口服务,获得当前的微信用户的openid
String openid = getOpenid(userLoginDTO.getCode());
//判断openid是否为空,如果为空表示登陆失败,提出业务异常
if(openid==null){
throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
}
//判断当前用户是否为新用户
User user=userMapper.getByOpenid(openid);
//如果为新用户,则自动完成注册
if(user==null){
user=User.builder()
.openid(openid)
.createTime(LocalDateTime.now())
.build();
userMapper.insert(user);
}
return user;
}
private String getOpenid(String code){
Map map=new HashMap<>();
map.put("appid",weChatProperties.getAppid());
map.put("secret",weChatProperties.getSecret());
map.put("js_code",code);
map.put("grant_type","authorization_code");
String json = HttpClientUtil.doGet(WX_LOGIN, map);
JSONObject jsonObject = JSON.parseObject(json);
String openid = jsonObject.getString("openid");
return openid;
}
}
创建UserMapper接口:
package com.sky.mapper;/*
*
* @author pengjx
*
* */
import com.sky.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
@Select("select * from user where openid=#{openid}")
User getByOpenid(String openid);
void insert(User user);
}
创建UserMapper.xml映射文件:
insert into user
(openid,name,phone,sex,id_number,avatar,create_time)
values
(#{openid},#{name},#{phone},#{sex},#{idNumber},#{avatar},#{createTime})
编写拦截器JwtTokenUserInterceptor,统一拦截用户端发送的请求并进行jwt校验:
package com.sky.interceptor;
import com.sky.constant.JwtClaimsConstant;
import com.sky.context.BaseContext;
import com.sky.properties.JwtProperties;
import com.sky.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* jwt令牌校验的拦截器
*/
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
/**
* 校验jwt
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getUserTokenName());
//2、校验令牌
try {
log.info("jwt校验:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
log.info("当前员工id:{}", userId);
BaseContext.setCurrentId(userId);
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
}
在WebMvcConfiguration配置类中注册拦截器:
registry.addInterceptor(jwtTokenUserInterceptor)
.addPathPatterns("/user/**")
.excludePathPatterns("/user/user/login")
.excludePathPatterns("/user/shop/status");
接口设计:
• 查询分类• 根据分类 id 查询菜品• 根据分类 id 查询套餐• 根据套餐 id 查询包含的菜品
package com.sky.controller.user;/*
*
* @author pengjx
*
* */
import com.sky.entity.Category;
import com.sky.result.Result;
import com.sky.service.CategoryService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController("userCategoryController")
@RequestMapping("/user/category")
@Slf4j
@Api(tags = "微信端分类相关接口")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* @description:查询分类
* @date: 2023/12/21 14:27
* @param: type
* @return: com.sky.result.Result>
**/
@GetMapping("/list")
@ApiOperation("查询分类")
public Result> list(Integer type){
log.info("查询分类:{}",type);
List list = categoryService.list(type);
return Result.success(list);
}
}
package com.sky.controller.user;/*
*
* @author pengjx
*
* */
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController("userDishController")
@RequestMapping("/user/dish")
@Slf4j
@Api(tags = "微信菜品相关接口")
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private RedisTemplate redisTemplate;
/**
* @description:根据分类id查询菜品
* @date: 2023/12/21 14:38
* @param: categoryId
* @return: com.sky.result.Result
**/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result> getByCategoryId(Long categoryId){
log.info("根据分类id查询菜品:{}",categoryId);
String key="key_"+categoryId;
List list = (List) redisTemplate.opsForValue().get(key);
if(list!=null&&list.size()>0){
return Result.success(list);
}
List dishVOS=dishService.listWithFlavor(categoryId);
redisTemplate.opsForValue().set(key,dishVOS);
return Result.success(dishVOS);
}
}
/**
* @description:根据分类id查询菜品
* @date: 2023/12/21 14:43
* @param: categoryId
* @return: java.util.List
**/
List listWithFlavor(Long categoryId);
/**
* @description:根据分类id查询菜品
* @date: 2023/12/21 14:43
* @param: categoryId
* @return: java.util.List
**/
@Override
public List listWithFlavor(Long categoryId) {
Dish dish = Dish.builder()
.categoryId(categoryId)
.status(StatusConstant.ENABLE)
.build();
List dishList = dishMapper.list(dish);
List dishVOList = new ArrayList<>();
for (Dish d : dishList) {
DishVO dishVO = new DishVO();
BeanUtils.copyProperties(d,dishVO);
//根据菜品id查询对应的口味
List flavors = dishFlavorMapper.getByDishId(d.getId());
dishVO.setFlavors(flavors);
dishVOList.add(dishVO);
}
@Autowired
private SetmealService setmealService;
/**
* @description:根据分类id查询套餐
* @date: 2023/12/21 14:59
* @param: categoryId
* @return: com.sky.result.Result>
**/
@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
public Result> getByCategoryId(Long categoryId){
log.info("根据分类id查询套餐:{}",categoryId);
List setmeals=setmealService.getByCategoryId(categoryId);
return Result.success(setmeals);
}
/**
* @description:根据分类id查询套餐
* @date: 2023/12/21 14:59
* @param: categoryId
* @return: java.util.List
**/
List getByCategoryId(Long categoryId);
/**
* @description:根据分类id查询套餐
* @date: 2023/12/21 14:59
* @param: categoryId
* @return: com.sky.result.Result>
**/
@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
public Result> getByCategoryId(Long categoryId){
log.info("根据分类id查询套餐:{}",categoryId);
List setmeals=setmealService.getByCategoryId(categoryId);
return Result.success(setmeals);
}
•根据套餐id查询包含的菜品 接口
**
* @description:根据套餐id查询包含的菜品
* @date: 2023/12/21 19:19
* @param: id
* @return: com.sky.result.Result>
**/
@GetMapping("/dish/{id}")
@ApiOperation("根据套餐id查询包含的菜品")
public Result> getSetmealDish(@PathVariable Long id ){
log.info("根据套餐id查询包含的菜品:{}",id);
List setmealDishes=setmealService.getDishItemById(id);
return Result.success(setmealDishes);
/**
* @description:根据套餐id查询包含的菜品
* @date: 2023/12/21 19:19
* @param: id
* @return: java.util.List
**/
List getDishItemById(Long id);
/**
* @description:根据套餐id查询包含的菜品
* @date: 2023/12/21 19:19
* @param: id
* @return: java.util.List
**/
@Override
public List getDishItemById(Long id) {
List dishItemVOS= setmealMapper.getDishItemBySetmealId(id);
return dishItemVOS;
}
@Select("select sd.copies,d.name,d.image,d.description from setmeal_dish sd left join dish d on sd.dish_id = d.id where sd.setmeal_id=#{setmealId}")
List getDishItemBySetmealId(Long setmealId);
用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。
结果:系统响应慢、用户体验差
通过Redis来缓存菜品数据,减少数据库查询操作。
缓存逻辑分析:
• 每个分类下的菜品保存一份缓存数据• 数据库中菜品数据有变更时清理缓存数据
修改用户端接口 DishController 的 list 方法,加入缓存处理逻辑:
/**
* @description:根据分类id查询菜品
* @date: 2023/12/21 14:38
* @param: categoryId
* @return: com.sky.result.Result
**/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result> getByCategoryId(Long categoryId){
log.info("根据分类id查询菜品:{}",categoryId);
String key="key_"+categoryId;
List list = (List) redisTemplate.opsForValue().get(key);
if(list!=null&&list.size()>0){
return Result.success(list);
}
List dishVOS=dishService.listWithFlavor(categoryId);
redisTemplate.opsForValue().set(key,dishVOS);
return Result.success(dishVOS);
}
修改管理端接口 DishController 的相关方法,加入清理缓存的逻辑,需要改造的方法:
• 新增菜品• 修改菜品• 批量删除菜品• 起售、停售菜品
抽取清理缓存的方法:
private void clearCache(String patten){
Set keys = redisTemplate.keys(patten);
redisTemplate.delete(keys);
}
调用清理缓存的方法,保证数据一致性:
package com.sky.controller.admin;/*
*
* @author pengjx
*
* */
import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Set;
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private RedisTemplate redisTemplate;
/**
* @description: 新增菜品
* @date: 2023/12/16 8:54
* @param: dishDTO
* @return: com.sky.result.Result
**/
@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishDTO){
log.info("新增菜品:{}",dishDTO);
dishService.saveWithFlavor(dishDTO);
String key="key_"+dishDTO.getCategoryId();
clearCache(key);
return Result.success();
}
/**
* @description: 菜品分页查询
* @date: 2023/12/16 19:17
* @param: dishPageQueryDTO
* @return: com.sky.result.Result
**/
@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result page(DishPageQueryDTO dishPageQueryDTO){
log.info("菜品分页查询:{}",dishPageQueryDTO);
PageResult pageResult=dishService.pageQuery(dishPageQueryDTO);
return Result.success(pageResult);
}
/**
* @description: 批量删除菜品
* @date: 2023/12/16 20:12
* @param: ids
* @return: com.sky.result.Result
**/
@DeleteMapping
@ApiOperation("批量删除菜品")
public Result delete(@RequestParam List ids){
log.info("批量删除菜品:{}",ids);
dishService.deleteBatch(ids);
clearCache("key_*");
return Result.success();
}
/**
* @description: 根据id查询菜品
* @date: 2023/12/17 8:48
* @param: id
* @return: com.sky.result.Result
**/
@GetMapping("/{id}")
@ApiOperation("根据id查询菜品")
public Result getById(@PathVariable Long id){
log.info("根据id查询菜品:{}",id);
DishVO dishVO=dishService.getByIdWithFlavor(id);
return Result.success(dishVO);
}
/**
* @description: 修改菜品
* @date: 2023/12/17 9:05
* @param: dishDTO
* @return: com.sky.result.Result
**/
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO){
log.info("修改菜品:{}",dishDTO);
dishService.update(dishDTO);
clearCache("key_*");
return Result.success();
}
/**
* @description: 根据分类id查询菜品
* @date: 2023/12/18 20:16
* @param: categoryId
* @return: com.sky.result.Result>
**/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result> getByCategoryId(Long categoryId){
log.info("根据分类id查询菜品");
List dishes=dishService.getByCategoryId(categoryId);
return Result.success(dishes);
}
private void clearCache(String patten){
Set keys = redisTemplate.keys(patten);
redisTemplate.delete(keys);
}
}
Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。
Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,例如:
• EHCache• Caffeine• Redis
org.springframework.boot
spring-boot-starter-cache
2.7.3
常用注解:
注解 |
说明 |
@EnableCaching |
开启缓存注解功能,通常加在启动类上 |
@Cacheable |
在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中 |
@CachePut |
将方法的返回值放到缓存中 |
@CacheEvict |
将一条或多条数据从缓存中删除 |
@PostMapping
@CachePut(cacheNames = "userCache",key = "#user.id")
// @CachePut(cacheNames = "userCache",key = "#result.id")
// @CachePut(cacheNames = "userCache",key = "#p0.id")
// @CachePut(cacheNames = "userCache",key = "#a0.id")
// @CachePut(cacheNames = "userCache",key = "#root.args[0].id")
public User save(@RequestBody User user){
userMapper.insert(user);
return user;
}
@GetMapping
@Cacheable(cacheNames = "userCache",key = "#id")
public User getById(Long id){
User user = userMapper.getById(id);
return user;
}
@DeleteMapping("/delAll")
@CacheEvict(cacheNames = "userCache",allEntries = true)
public void deleteAll(){
userMapper.deleteAll();
}
@GetMapping
@Cacheable(cacheNames = "userCache",key = "#id")
public User getById(Long id){
User user = userMapper.getById(id);
return user;
}
具体的实现思路如下:
• 导入 Spring Cache 和 Redis 相关 maven 坐标• 在启动类上加入 @ EnableCaching注解,开启缓存注解功能• 在用户端接口 SetmealController 的 list 方法 上加入 @Cacheable 注解• 在 管理端接口 SetmealController 的 save 、 delete 、 update 、 startOrStop 等方法上加入CacheEvict注解
在用户端接口SetmealController的 list 方法上加入@Cacheable注解:
在管理端接口SetmealController的 save、delete、update、startOrStop等方法上加入CacheEvict注解:
接口设计:
• 请求方式: POST• 请求路径: /user/ shoppingCart /add• 请求参数:套餐 id 、菜品 id 、口味• 返回结果: code 、 data 、 msg
数据库设计:
• 作用:暂时存放所选商品的地方• 选的什么商品• 每个商品都买了几个• 不同用户的购物车需要区分开
数据库设计(shopping_cart表):
字段名 |
数据类型 |
说明 |
备注 |
id |
bigint |
主键 |
自增 |
name |
varchar(32) |
商品名称 |
冗余字段 |
image |
varchar(255) |
商品图片路径 |
冗余字段 |
user_id |
bigint |
用户id |
逻辑外键 |
dish_id |
bigint |
菜品id |
逻辑外键 |
setmeal_id |
bigint |
套餐id |
逻辑外键 |
dish_flavor |
varchar(50) |
菜品口味 |
|
number |
int |
商品数量 |
|
amount |
decimal(10,2) |
商品单价 |
冗余字段 |
create_time |
datetime |
创建时间 |
根据添加购物车接口创建ShoppingCartController:
/**
* @description:加入购物车
* @date: 2023/12/22 20:24
* @param: shoppingCartDTO
* @return: com.sky.result.Result
**/
@PostMapping("/add")
@ApiOperation("加入购物车")
public Result add(@RequestBody ShoppingCartDTO shoppingCartDTO){
log.info("加入购物车:{}",shoppingCartDTO);
shoppingCartService.addShoppingCart(shoppingCartDTO);
return Result.success();
}
创建ShoppingCartService接口:
/**
* @description:加入购物车
* @date: 2023/12/22 20:27
* @param: shoppingCartDTO
**/
void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
创建ShoppingCartServiceImpl实现类,并实现add方法:
/**
* @description:加入购物车
* @date: 2023/12/22 20:27
* @param: shoppingCartDTO
**/
@Override
public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {
//查询购物车表中是否有数据
ShoppingCart shoppingCart=new ShoppingCart();
BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
shoppingCart.setUserId(BaseContext.getCurrentId());
List shoppingCarts=shoppingCartMapper.list(shoppingCart);
//如果存在,则只更改商品数量
if(shoppingCarts!=null && shoppingCarts.size()==1){
shoppingCart = shoppingCarts.get(0);
shoppingCart.setNumber(shoppingCart.getNumber()+1);
shoppingCartMapper.updateNumberById(shoppingCart);
return;
}
//如果不存在,则要插入到购物车数据库中
Long dishId = shoppingCart.getDishId();
Long setmealId = shoppingCart.getSetmealId();
if(dishId!=null){
Dish dish = dishMapper.getById(dishId);
shoppingCart.setName(dish.getName());
shoppingCart.setImage(dish.getImage());
shoppingCart.setAmount(dish.getPrice());
}else {
Setmeal setmeal = setmealMapper.getById(setmealId);
shoppingCart.setName(setmeal.getName());
shoppingCart.setImage(setmeal.getImage());
shoppingCart.setAmount(setmeal.getPrice());
}
shoppingCart.setNumber(1);
shoppingCart.setCreateTime(LocalDateTime.now());
shoppingCartMapper.insert(shoppingCart);
}
创建ShoppingCartMapper接口:
List list(ShoppingCart shoppingCart);
@Update("update shopping_cart set number = #{number} where id = #{id}")
void updateNumberById(ShoppingCart shoppingCart);
/**
* 插入购物车数据
* @param shoppingCart
*/
@Insert("insert into shopping_cart (name, user_id, dish_id, setmeal_id, dish_flavor, number, amount, image, create_time) " +
" values (#{name},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number},#{amount},#{image},#{createTime})")
void insert(ShoppingCart shoppingCart);
创建ShoppingCartMapper.xml:
接口设计:
在ShoppingCartController中创建查看购物车的方法:
/**
* @description:查看购物车
* @date: 2023/12/23 8:41
* @return: com.sky.result.Result
**/
@GetMapping("/list")
@ApiOperation("查看购物车")
public Result> list(){
log.info("查看购物车");
List list=shoppingCartService.showShoppingCart();
return Result.success(list);
}
在ShoppingCartServiceImpl中实现查看购物车的方法:
/**
* @description:查看购物车
* @date: 2023/12/23 8:43
* @return: java.util.List
**/
@Override
public List showShoppingCart() {
ShoppingCart shoppingCart = ShoppingCart.builder()
.userId(BaseContext.getCurrentId())
.build();
List list = shoppingCartMapper.list(shoppingCart);
return list;
}
在ShoppingCartController中创建清空购物车的方法:
/**
* @description:清除购物车
* @date: 2023/12/23 8:58
* @return: com.sky.result.Result
**/
@DeleteMapping("/clean")
@ApiOperation("清除购物车")
public Result clean(){
log.info("清除购物车");
shoppingCartService.cleanShoppingCart();
return Result.success();
}
在ShoppingCartServiceImpl中实现清空购物车的方法:
/**
* @description:清除购物车
* @date: 2023/12/23 8:59
**/
@Override
public void cleanShoppingCart() {
Long userId = BaseContext.getCurrentId();
shoppingCartMapper.deleteByUserId(userId);
}
在ShoppingCartMapper接口中创建删除购物车数据的方法:
@Delete("delete from shopping_cart where user_id=#{userId}")
void deleteByUserId(Long userId);
用户下单业务说明:
在电商系统中,用户是通过下单的方式通知商家,用户已经购买了商品,需要商家进行备货和发货。
用户下单后会产生订单相关数据,订单数据需要能够体现如下信息:
用户点餐业务流程:
提交订单接口设计(分析):
请求方式:POST
请求路径:/user/order/submit
参数:
• 地址簿 id• 配送状态(立即送出、选择送出时间)• 打包费• 总金额• 备注• 餐具数量返回数据:
• 下单时间• 订单总金额• 订单号• 订单 id
接口设计:
创建OrderController并提供用户下单方法:
package com.sky.controller.user;/*
*
* @author pengjx
*
* */
import com.sky.dto.OrdersSubmitDTO;
import com.sky.result.Result;
import com.sky.service.OrderService;
import com.sky.vo.OrderSubmitVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController("userOrderController")
@RequestMapping("/user/order")
@Slf4j
@Api(tags = "订单相关接口")
public class OrderController {
@Autowired
private OrderService orderService;
/**
* @description:提交订单
* @date: 2023/12/23 20:44
* @param: ordersSubmitDTO
* @return: com.sky.result.Result
**/
@PostMapping("/submit")
@ApiOperation("提交订单")
public Result submit(@RequestBody OrdersSubmitDTO ordersSubmitDTO){
log.info("提交订单:{}",ordersSubmitDTO);
OrderSubmitVO orderSubmitVO=orderService.submitOrder(ordersSubmitDTO);
return Result.success(orderSubmitVO);
}
}
创建OrderService接口,并声明用户下单方法:
package com.sky.service;/*
*
* @author pengjx
*
* */
import com.sky.dto.OrdersSubmitDTO;
import com.sky.vo.OrderSubmitVO;
import org.springframework.stereotype.Service;
@Service
public interface OrderService {
/**
* @description:提交订单
* @date: 2023/12/23 20:48
* @param: ordersSubmitDTO
* @return: com.sky.vo.OrderSubmitVO
**/
OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO);
}
创建OrderServiceImpl实现OrderService接口:
package com.sky.service.impl;/*
*
* @author pengjx
*
* */
import com.sky.constant.MessageConstant;
import com.sky.context.BaseContext;
import com.sky.dto.OrdersSubmitDTO;
import com.sky.entity.AddressBook;
import com.sky.entity.OrderDetail;
import com.sky.entity.Orders;
import com.sky.entity.ShoppingCart;
import com.sky.exception.AddressBookBusinessException;
import com.sky.exception.ShoppingCartBusinessException;
import com.sky.mapper.AddressBookMapper;
import com.sky.mapper.OrderDetailMapper;
import com.sky.mapper.OrderMapper;
import com.sky.mapper.ShoppingCartMapper;
import com.sky.service.OrderService;
import com.sky.vo.OrderSubmitVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private AddressBookMapper addressBookMapper;
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private OrderDetailMapper orderDetailMapper;
/**
* @description:提交订单
* @date: 2023/12/23 20:48
* @param: ordersSubmitDTO
* @return: com.sky.vo.OrderSubmitVO
**/
@Override
@Transactional
public OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) {
//如果地址簿为空,则要抛出异常
AddressBook addressBook = addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());
if(addressBook==null){
throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);
}
//如果购物车为空,则要抛出异常
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.setUserId(BaseContext.getCurrentId());
List shoppingCartList = shoppingCartMapper.list(shoppingCart);
if(shoppingCartList==null || shoppingCartList.size()==0){
throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
}
//构建订单表,并插入数据
Orders orders=new Orders();
BeanUtils.copyProperties(ordersSubmitDTO,orders);
orders.setUserId(BaseContext.getCurrentId());
orders.setOrderTime(LocalDateTime.now());
orders.setConsignee(addressBook.getConsignee());
orders.setNumber(String.valueOf(System.currentTimeMillis()));
orders.setPhone(addressBook.getPhone());
orders.setStatus(Orders.PENDING_PAYMENT);
orders.setPayStatus(Orders.UN_PAID);
orders.setAddress(addressBook.getDetail());
orderMapper.insert(orders);
//构建订单明细表,并构建数据
List orderDetailList=new ArrayList<>();
shoppingCartList.forEach(shoppingCart1->{
OrderDetail orderDetail = new OrderDetail();
BeanUtils.copyProperties(shoppingCart1,orderDetail);
orderDetail.setOrderId(orders.getId());
orderDetailList.add(orderDetail);
});
orderDetailMapper.insertBatch(orderDetailList);
//清空购物车
shoppingCartMapper.deleteByUserId(BaseContext.getCurrentId());
//返回VO对象
OrderSubmitVO orderSubmitVO = new OrderSubmitVO();
orderSubmitVO.setId(orders.getId());
orderSubmitVO.setOrderNumber(orders.getNumber());
orderSubmitVO.setOrderAmount(orders.getAmount());
orderSubmitVO.setOrderTime(orders.getOrderTime());
return orderSubmitVO;
}
}
创建OrderMapper接口和对应的xml映射文件:
package com.sky.mapper;/*
*
* @author pengjx
*
* */
import com.sky.entity.Orders;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OrderMapper {
void insert(Orders orders);
}
insert into orders
(number, status, user_id, address_book_id, order_time, checkout_time, pay_method, pay_status,
amount, remark,phone, address, consignee, estimated_delivery_time, delivery_status,
pack_amount, tableware_number,tableware_status)
values
(#{number}, #{status}, #{userId}, #{addressBookId}, #{orderTime}, #{checkoutTime}, #{payMethod},#{payStatus},
#{amount}, #{remark}, #{phone},#{address}, #{consignee},#{estimatedDeliveryTime}, #{deliveryStatus},
#{packAmount}, #{tablewareNumber}, #{tablewareStatus})
创建OrderDetailMapper接口和对应的xml映射文件:
package com.sky.mapper;/*
*
* @author pengjx
*
* */
import com.sky.entity.OrderDetail;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface OrderDetailMapper {
void insertBatch(List orderDetailList);
}
insert into order_detail (name, order_id, dish_id, setmeal_id, dish_flavor, number, amount, image)
values
(#{od.name},#{od.orderId},#{od.dishId},#{od.setmealId},#{od.dishFlavor},#{od.number},#{od.amount},#{od.image})
有需要再看