先简单挑重点写一点,后面有空补充
业务逻辑
员工登陆
- 根据用户名查询数据库的用户信息,如果不存在,抛出异常,如果存在,进行密码比对,查看是否被锁定。
- 登陆成功后,将用户id和jwt密钥、过期时间、令牌名称一起封装生成jwt令牌。再将令牌和用户信息返回给前端。(前端每次发送请求都要携带这个令牌,否则请求不生效被拦截,令牌校验在拦截器中进行:用JwtUtil.parseJWT用配置类的jwt密钥解析token,如果过程失败就被拦截,否则放行,并将解析出来的员工id放入ThreadLocal中。)
新增菜品
新增菜品的同时要新增口味,所以新建一个菜品口味表存放菜品id和对应的口味
删除菜品
- 判断当前菜品是否能够删除---是否存在起售中的菜---判断状态
- 判断当前菜品是否能够删除---是否被套餐关联了--- select setmeal_id from setmeal_dish where dish_id in #{dish_id} 如果setmeal_id为null就没关联
- 删除菜品,同时删除菜品口味表中的数据
新增套餐
新增套餐要关联菜品,所以新建一个套餐菜品表存放套餐id和对应的菜品id
删除套餐
- 起售中的套餐不能删除
- 删除套餐表中的数据同时删除套餐菜品表的数据
起售套餐
判断套餐里有无菜品停售,有则不能起售套餐
添加购物车
传入参数(菜品id,套餐id,菜品口味)
- 判断当前加入到购物车中的商品是否已经存在了,存在就+1
- 添加菜品或者套餐
店铺状态设置
直接用redis缓存来实现 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
序列化
@Configuration
@Slf4j
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
log.info("开始创建redis模板对象...");
RedisTemplate redisTemplate = new RedisTemplate();
//设置redis的连接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置redis key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
/**
* 设置店铺的营业状态
* @param status
* @return
*/
@PutMapping("/{status}")
@ApiOperation("设置店铺的营业状态")
public Result setStatus(@PathVariable Integer status){
log.info("设置店铺的营业状态为:{}",status == 1 ? "营业中" : "打烊中");
redisTemplate.opsForValue().set(KEY,status);
return Result.success();
}
/**
* 获取店铺的营业状态
* @return
*/
@GetMapping("/status")
@ApiOperation("获取店铺的营业状态")
public Result<Integer> getStatus(){
Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
log.info("获取到店铺的营业状态为:{}",status == 1 ? "营业中" : "打烊中");
return Result.success(status);
}
统计每日总用户数量和新增用户数量
参数(LocalDate begin, LocalDate end)
- 将选择的日期区间中的每天的日期放入map中
//存放从begin到end之间的每天对应的日期
List<LocalDate> dateList = new ArrayList<>();
dateList.add(begin);
while (!begin.equals(end)) {
begin = begin.plusDays(1);
dateList.add(begin);
}
- 遍历这些日期,begin=每天的开始时间,end=每天的结束时间
- 每日总人数:数据库中查询区间在end之前的count
- 每日新增人数:数据库中查询区间在begin和end之间的count
List<Integer> newUserList = new ArrayList<>();//每日新增
List<Integer> totalUserList = new ArrayList<>();//每日总人数
for (LocalDate date : dateList) {
LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
Map map = new HashMap();
map.put("end", endTime);
//总用户数量
Integer totalUser = userMapper.countByMap(map);
map.put("begin", beginTime);
//新增用户数量
Integer newUser = userMapper.countByMap(map);
totalUserList.add(totalUser);
newUserList.add(newUser);
}
销量前十排名
<select id="getSalesTop10" resultType="com.sky.dto.GoodsSalesDTO">
select od.name, sum(od.number) number
from order_detail od,orders o
where od.order_id = o.id and o.status = 5
<if test="begin != null">
and o.order_time > #{begin}
</if>
<if test="end != null">
and o.order_time < #{end}
</if>
group by od.name
order by number desc
limit 0,10
</select>
技术要点
技术栈:Swagger、AOP、SpringCache、HttpClient、SpringTask、WebSocket
Swagger
仅用postman测试会因为参数过多而不方便,可以利用swagger生成接口文档并进行接口测试。 Knife4j是为SpringMVC集成swagger生成文档的增强解决方案。 1.导入maven坐标
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
2.配置Knife4j
@Bean
public Docket docket() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}
3.设置静态资源映射
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
打开http://localhost:8080/doc.html#/home
添加全局参数-token,否则每次发送请求会被拦截器拦截
AOP实现自动填充
一些字段比如updateTime、updateUser、createTime、createUser总要重复去赋值生成对象,代码比较垄余,因此用AOP实现自动填充。
思路: 1.创建一个名为autofill的注解,作用是做标记
@Target(ElementType.METHOD)//作用在方法上
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
OperationType value();
}
2.创建一个切面类,用于拦截有autofill注解的方法,通过反射为公共字段赋值
@Aspect
@Component
@Slf4j
public class AutoFillAspect
/**
* 切入点
*/
@Pointcut("execution(* com.sky.service.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointcut(){}
@Before("autoFillPointcut()")
public void autoFill(JoinPoint joinPoint){
log.info("开始公共字段的自动填充");
}
填充过程:
- 获取实体对象(方法的入参参数)
Object[] args = joinPoint.getArgs();
Object entity = args[0];
- 获取属性方法
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
- 利用反射赋值
setCreateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
用MP实现自动补充数据
之前是用AOP反射实现,这次换一个更简单的方式
@Component //此注解表示 将其交给spring去管理
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
this.setFieldValByName("createUser", BaseContext.getCurrentId(), metaObject);
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
this.setFieldValByName("updateUser", BaseContext.getCurrentId(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
this.setFieldValByName("updateUser", BaseContext.getCurrentId(), metaObject);
}
属性加上注解
@TableField(fill = FieldFill.INSERT)
//创建时间
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
//更新时间
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
//创建人
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
//修改人
private Long updateUser;
SpringCache
是对SpringRedisTemplate的简化 实现步骤:
- 导入坐标
- 启动类上加@EnableCaching注解
- 在需要缓存的方法上加上@Cacheable注解
- 在需要完成修改添加删除功能的方法上加上@CacheEvict和注解
HttpClient
使用HttpClient可以用代码构造请求,并将请求发送出去。 发送请求步骤:
- 创建HttpClient对象
- 创建Http对象
- 调用HttpClient的excute方法发送请求
//创建HttpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建Http请求
HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");
//利用对象的excute发送请求
CloseableHttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
String string = EntityUtils.toString(entity);
System.out.println(string);//{"code":1,"msg":null,"data":0}
工具类
public class HttpClientUtil {
static final int TIMEOUT_MSEC = 5 * 1000;
/**
* 发送GET方式请求
* @param url
* @param paramMap
* @return
*/
public static String doGet(String url,Map<String,String> paramMap){
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
String result = "";
CloseableHttpResponse response = null;
try{
URIBuilder builder = new URIBuilder(url);
if(paramMap != null){
for (String key : paramMap.keySet()) {
builder.addParameter(key,paramMap.get(key));
}
}
URI uri = builder.build();
//创建GET请求
HttpGet httpGet = new HttpGet(uri);
//发送请求
response = httpClient.execute(httpGet);
//判断响应状态
if(response.getStatusLine().getStatusCode() == 200){
result = EntityUtils.toString(response.getEntity(),"UTF-8");
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
response.close();
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
/**
* 发送POST方式请求
* @param url
* @param paramMap
* @return
* @throws IOException
*/
public static String doPost(String url, Map<String, String> paramMap) throws IOException {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建参数列表
if (paramMap != null) {
List<NameValuePair> paramList = new ArrayList();
for (Map.Entry<String, String> param : paramMap.entrySet()) {
paramList.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
httpPost.setEntity(entity);
}
httpPost.setConfig(builderRequestConfig());
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
} catch (Exception e) {
throw e;
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
/**
* 发送POST方式请求
* @param url
* @param paramMap
* @return
* @throws IOException
*/
public static String doPost4Json(String url, Map<String, String> paramMap) throws IOException {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
if (paramMap != null) {
//构造json格式数据
JSONObject jsonObject = new JSONObject();
for (Map.Entry<String, String> param : paramMap.entrySet()) {
jsonObject.put(param.getKey(),param.getValue());
}
StringEntity entity = new StringEntity(jsonObject.toString(),"utf-8");
//设置请求编码
entity.setContentEncoding("utf-8");
//设置数据类型
entity.setContentType("application/json");
httpPost.setEntity(entity);
}
httpPost.setConfig(builderRequestConfig());
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
} catch (Exception e) {
throw e;
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
private static RequestConfig builderRequestConfig() {
return RequestConfig.custom()
.setConnectTimeout(TIMEOUT_MSEC)
.setConnectionRequestTimeout(TIMEOUT_MSEC)
.setSocketTimeout(TIMEOUT_MSEC).build();
}
}
SpringTask
应用场景:定时任务(处理超时订单)
/**
* 处理超时订单的方法
*/
@Scheduled(cron = "0 * * * * ? ") //每分钟触发一次
public void processTimeoutOrder(){
log.info("定时处理超时订单:{}", LocalDateTime.now());
LocalDateTime time = LocalDateTime.now().plusMinutes(-15);
// select * from orders where status = ? and order_time < (当前时间 - 15分钟)
List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, time);
if(ordersList != null && ordersList.size() > 0){
for (Orders orders : ordersList) {
orders.setStatus(Orders.CANCELLED);
orders.setCancelReason("订单超时,自动取消");
orders.setCancelTime(LocalDateTime.now());
orderMapper.update(orders);
}
}
}
WebSocket
实现客户端和服务端的双向通信,一次握手,二者就可以持久性连接
和Http的对比:
- Http是短连接,WebSocket是长连接
- Http是单向通信,基于请求响应
- 两者底层都是基于TCP
应用场景
数据实时更新(不用刷新页面得到的信息,服务器主动推上来):比如b站弹幕、实时聊天、体育赛事比分、股价
配置工具类
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
//存放会话对象
private static Map<String, Session> sessionMap = new HashMap();
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
System.out.println("客户端:" + sid + "建立连接");
sessionMap.put(sid, session);
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, @PathParam("sid") String sid) {
System.out.println("收到来自客户端:" + sid + "的信息:" + message);
}
/**
* 连接关闭调用的方法
*
* @param sid
*/
@OnClose
public void onClose(@PathParam("sid") String sid) {
System.out.println("连接断开:" + sid);
sessionMap.remove(sid);
}
/**
* 群发
*
* @param message
*/
public void sendToAllClient(String message) {
Collection<Session> sessions = sessionMap.values();
for (Session session : sessions) {
try {
//服务器向客户端发送消息
session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* WebSocket配置类,用于注册WebSocket的Bean
*/
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
11.22-11.27