java项目笔记(苍穹外卖)
MD5 加密方式
//进行md5加密
password = DigestUtils.md5DigestAsHex("123456".getBytes());
对于时间格式化的两种方式
-
采用注解的方式
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
缺点:只能对一个属性起作用,也就是有几个需要进行时间处理的变量,我们就需要添加几个注解
-
自定义消息转换器
可以对全局变量有效,因为这是我们自定义的配置类,会在全局生效
WebMvcConfiguration.java @Override protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { log.info("扩展消息转换器"); //创建一个消息转换器对象 MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); //需要为消息转换器指定对象转换器 对象转换器课可以将java对象序列化为Json对象 converter.setObjectMapper(new JacksonObjectMapper()); //将自定义的消息转换器加入到容器中 converters.add(0, converter); }
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; /** * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象] * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON] */ public class JacksonObjectMapper extends ObjectMapper { public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd"; //public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm"; public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss"; public JacksonObjectMapper() { super(); //收到未知属性时不报异常 this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); //反序列化时,属性不存在的兼容处理 this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); SimpleModule simpleModule = new SimpleModule() .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))) .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))) .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))) .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))) .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))) .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))); //注册功能模块 例如,可以添加自定义序列化器和反序列化器 this.registerModule(simpleModule); } }
Swagger
Knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。
使用步骤
-
导入Knifeej的maven坐标:
<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.2</version> </dependency>
-
在配置类中加入knife4j相关配置
//WebMvcConfiguration.java中 @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; }
-
设置静态资源映射,否则接口文档页面无法访问
//WebMvcConfiguration.java中 /** * 设置静态资源映射 * @param registry */ protected void addResourceHandlers(ResourceHandlerRegistry registry) { log.info("开始设置静态资源映射..."); registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); }
-
访问文档接口:访问路径
http://ip:port/doc.html
YAPI 与 swagger对比
- Yapi 是在设计阶段使用的工具,用于管理和维护接口
- Swagger是在开发阶段使用的框架,用于帮助后端开发人员做后端的接口测试
swagger常用注解
注解 | 说明 | |
---|---|---|
@Api | 用在类上,例如controller类 | |
@ApiModel | 用在pojo上,例如entity,DTO,VO,表示对pojo类的说明 | |
@ApiModelProperty | 用在属性上,描述属性信息 | |
@ApiOperation | 用在方法上,例如controller的方法,说明方法的用途和作用 |
ThreadLocal
-
ThreadLocal 并不是一个Thread,而是Thread的局部变量
-
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
(客户端每次发起请求,后端的Tomcat服务器都会分配一个单独的线程来处理请求)
ThreadLocal 常用方法
方法 | 说明 |
---|---|
public void set(T value) | 设置当前线程的线程局部变量值 |
public T get() | 返回当前线程对应的线程局部变量的值 |
public void remove() | 移除当前线程的线程局部变量 |
例如:
//BaseContext.java中
public class BaseContext {
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
public static Long getCurrentId() {
return threadLocal.get();
}
public static void removeCurrentId() {
threadLocal.remove();
}
}
公共字段自动填充
例如:
- create_time
- create_user
- update_time
- update_user
这些字段在多处被执行相同的操作,导致代码冗余,不便于后期维护
实现思路
枚举,注解,AOP,反射
- 自定义注解 AutoFill,用于表示需要进行公共字段自动填充的方法
- 自定义切面类AutoFillAspect,统一拦截加入了AutoFill 注解的方法,通过反射为公共字段赋值。
- 在Mapper的方法上加入AutoFill注解
编码实现
-
自定义注解AutoFill
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AutoFill { //数据库操作类型:UPDATE INSERT OperationType value(); }
-
自定义切面AutoFillAspect
@Aspect @Component @Slf4j public class AutoFillAspect { /** * 切入点 */ @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") public void autoFillPointcut() {} /** * 前置通知,在通知中进行公共字段的赋值 */ @Before("autoFillPointcut()") public void autoFill(JoinPoint joinPoint) { log.info("开始进行公共字段的自动填充..."); //获取到当前被拦截的方法上的数据库操作类型 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //方法签名对象 AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); //获得方法签名上的注解对象 OperationType operationType = autoFill.value(); //获得数据库操作类型 //获取到当前被拦截的方法的参数——实体对象 Object[] args = joinPoint.getArgs(); //约定:实体对象始终在形参列表的第一个位置 if (args == null || args.length == 0) { return; } Object entity = args[0]; //准备赋值的数据 LocalDateTime now = LocalDateTime.now(); Long currentId = BaseContext.getCurrentId(); //根据当前不同的操作类型,为对应的属性通过反射来赋值 if (operationType == OperationType.INSERT) { //为四个公共字段赋值 try { 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); } catch (Exception e) { e.printStackTrace(); } } else if (operationType == OperationType.UPDATE) { //为两个公共字段赋值 try { Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); //通过反射为对象赋值 setUpdateTime.invoke(entity, now); setUpdateUser.invoke(entity, currentId); } catch (Exception e) { e.printStackTrace(); } } } }
-
在Mapper方法上加入AutoFill注解
//CategoryMapper.java @Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, " + "update_user) VALUES (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, " + "#{createUser}, #{updateUser})") @AutoFill(OperationType.INSERT) void insert(Category category); @AutoFill(OperationType.UPDATE) void update(Category category);
HttpClient
HttpClient 可以用来提供高效的,最新的,功能丰富的HTTP协议的客户端编程工具,并且支持http协议最新的版本和协议
-
Maven坐标
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency>
-
核心API:HttpClient、HttpClients、CloseableHttpClient、HttpGet、HttpPost
-
发送请求步骤:
- 创建HttpClient对象
- 创建Http请求对象
- 调用HttpClient的execute方法发送请求
-
-
Get请求
//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(); //http响应体 HttpEntity entity = response.getEntity(); //将响应体转为String字符串 String body = EntityUtils.toString(entity); System.out.println(body); //关闭资源 response.close(); httpClient.close();
-
Post请求
CloseableHttpClient httpClient = HttpClients.createDefault(); //Post方式请求 HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login"); //构造json数据 JSONObject jsonObject = new JSONObject(); jsonObject.put("username","admin"); jsonObject.put("password", "123456"); //构造请求体 StringEntity stringEntity = new StringEntity(jsonObject.toString()); //设置请求编码 stringEntity.setContentEncoding("utf-8"); //设置数据类型 stringEntity.setContentType("application/json"); //设置当前Post请求的请求体 httpPost.setEntity(stringEntity); //发送请求 CloseableHttpResponse response = httpClient.execute(httpPost); //http响应码 int statusCode = response.getStatusLine().getStatusCode(); //http响应体 HttpEntity entity = response.getEntity(); //将响应体转为String字符串 String body = EntityUtils.toString(entity); System.out.println(body); //关闭资源 response.close(); httpClient.close();
Spring cache
Spring cache 是一个框架,实现了基于注解的缓存功能,只需简单的加一个注解,就能实现缓存功能。
Spring cache 提供了一层抽象,底层可以切换不同的缓存实现,例如:EHCache,Caffeine,Redis
-
Maven坐标为
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> <version>2.7.3</version> </dependency>
-
常用注解
常用注解 说明 @EnableCaching 开启缓存注解功能,通常加在启动类上 @Cacheable 在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中 @CachePut 将方法的返回值放到缓存中 @CacheEvict 将一条或多条数据从缓存中删除 -
用法
- CachePut(cacheNames = "cachename", key = "#variable.property"),使用Spring Cache缓存数据时,key的生成规则为:cachename::{variable.property的值},其中key按照spEL(spring Expression Language)的规则来写,其中一种写法是目标变量.目标属性,这个"."叫做对象导航。
- CacheEvict(cacheNames = "cachename", allEntries = true),使用allEntries = true即可删除cachename下的所有键值对。
-
实现思路
- 导入Spring Cache和Redis相关maven坐标。
- 在启动类上加入@EnableCaching注解,开启缓存注解功能。
- 在用户端接口SetmealController的 list 方法上加入@Cacheable注解。
- 在管理端接口SetmealController的 save、delete、update、startOrStop等方法上加入CacheEvict注解。
-
Spring Task
Spring task是Spring框架提供的任务调度工具,可以按照预定的时间自动执行某个代码逻辑
应用场景
- 信用卡每月还款提醒
- 火车售票系统处理未支付订单
- 入职纪念日为用户发送通知
只要是需要定时处理的场景都可以使用Spring task
cron表达式
- cron表达式其实就是一个字符串,通过cron表达式可以定义任务触发的时间。
- 构成规则:分为6或7个域,由空格分隔开,每个域代表一个含义。
- 每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)。
- 一般日和周不用同时指定,没指定的那个域可以使用?占位。
- cron表达式在线生成器:https://cron.qqe2.com/。
Spring task使用步骤
-
导入maven坐标 spring-context(Spring Task包含在spring-context中)。
-
启动类添加注解 @EnableScheduling 开启任务调度。
-
自定义定时任务类。
-
具体处理逻辑,定义处理方法,方法没有返回值
@Component @Slf4j public class OrderTask { @Autowired private OrderMapper orderMapper; @Scheduled(cron = "0 * * * * ?") //每分钟触发一次 public void processTimeoutOrder() { LocalDateTime now = LocalDateTime.now(); log.info("定时处理超时订单:{}", now); //处理15分钟前创建的未付款订单 LocalDateTime time = now.minusMinutes(15); List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, time); //修改订单状态、取消时间、取消原因并批量更新 if (ordersList != null && !ordersList.isEmpty()) { for (Orders orders : ordersList) { orders.setStatus(Orders.CANCELLED); orders.setCancelTime(now); orders.setCancelReason("订单超时,自动取消"); } orderMapper.updateBatch(ordersList); } } @Scheduled(cron = "0 0 1 * * ?") //每天凌晨1点触发一次 public void processDeliveryOrder() { LocalDateTime now = LocalDateTime.now(); log.info("定时处理处于派送中的订单:{}", now); //处理前一天创建的处于派送中的订单 LocalDateTime time = now.minusHours(1); List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.DELIVERY_IN_PROGRESS, time); //修改订单状态并批量更新 if (ordersList != null && !ordersList.isEmpty()) { for (Orders orders : ordersList) { orders.setStatus(Orders.COMPLETED); } orderMapper.updateBatch(ordersList); } } }
WebSocket
websocket 是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工通宵。
浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。
应用场景
- 视频弹幕。
- 网页聊天。
- 体育实况更新。
- 股票基金报价实时更新。
使用步骤
-
编写html页面作为WebSocket客户端。
-
导入WebSocket的maven坐标:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
-
编写WebSocket服务端用于和客户端通信的类。
@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的服务端组件。
@Configuration public class WebSocketConfiguration { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
-
自定义与客户端通信的方法。
@Autowired private WebSocketServer webSocketServer; public void paySuccess(String outTradeNo) { ... //通过WebSocket向客户端浏览器推送消息 Map map = new HashMap(); map.put("type", 1); //1表示来单提醒,2表示客户催单 map.put("orderId", ordersDB.getId()); map.put("content", "订单号:" + outTradeNo); String json = JSON.toJSONString(map); webSocketServer.sendToAllClient(json); }
Apache ECharts
- Apache ECharts是一款基于Javascript的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。
- 官网地址:https://echarts.apache.org/zh/index.html。
- 使用Echarts,重点在于研究当前图表所需的数据格式。通常是需要后端提供符合格式要求的动态数据,然后响应给前端来展示图表。