> 技术文档 > 苍穹外卖笔记集锦

苍穹外卖笔记集锦


Build 建造者模式
  • Lombok提供
实现:

在类上添加 @Builder 注解:

import lombok.Builder;import lombok.Data;@Data@Builderpublic class EmployeeLoginVO { private Long id; private String userName; private String name; private String token;}

使用 Builder 创建对象

EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder() .id(employee.getId()) .userName(employee.getUsername()) .name(employee.getName()) .token(token) .build();
多个pom文件的管理

使用父POM: 只在父级pom文件中添加.
如果在子pom中多次添加,可能会导致不能启动项目的问题.

maven聚合工程
  • 聚合器POM(父工程)
    聚合工程中要指定pom,用于聚合多个模块,并通过 锁定各个依赖版本。并在标签内列出所有的子模块

  • 模块间的依赖
    如果一个子模块需要依赖另一个子模块,在pom.xml中添加对该子模块的依赖:​

<dependency> <groupId>com.example</groupId> <artifactId>module1</artifactId> <version>1.0-SNAPSHOT</version></dependency>
  • 聚合器模块不要作为依赖:

    <dependency> <groupId>Warren</groupId> <artifactId>Common</artifactId> <version>0.0.1-SNAPSHOT</version></dependency>

Common模块是一个POM类型的聚合器(包含Common-utilService-util)。聚合器模块一般不产生jar包,作为依赖引用可能会引起问题;如果需要使用,引用聚合器的具体模块(例如Common-utilService-util)即可.

Query参数
  • URL? 后面的键值对传递的请求参数。比如/employee/page?name=zhangsan 中的name=zhangsan 就是Query参数。

  • 使用@RequestParam 注解来获取Query参数

Pagehelper分页

PageHelper是由GitHub上的pagehelper组织维护的MyBatis通用分页插件。

  • 使用这个方法之后就不用导入MP的配置类了

  • PageHelper.startPage(page, pageSize) : page 表示当前页码,pageSize 是每页显示的记录数。

  • 在调用该方法之后,紧跟着执行的查询会被拦截,并在生成的SQL中添加分页相关的语句,注意后面跟随的方法要返回一个Page

PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize()); Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);//后续定义

需要导入依赖:

<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.6</version></dependency>
生成主键

MyBatisXML映射文件中的两个属性

  • useGeneratedKeys=\"true\"
    自动生成主键。并在执行插入操作后,返回主键id

  • keyProperty=\"id\"
    指定生成的主键值应对应到Java对象中的哪个属性上。

设置时间自动填充:
  • 在数据库表中设置时间自动填充,就不用在实体类上指定自动填充时间了

  • 数据库中时间类字段使用timestamp,实体类中使用String.

`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT \'创建时间\', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \'更新时间\',
jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

设置时间正确显示,如果不加这个配置,时间显示的不是中国时间.

使用AOP实现公共字段填充

实现思路:

  1. 自定义一个注解 @AutoFill用于标识需要自动填充公共字段的方法,并指定对应的数据库操作类型(INSERTUPDATE)。

  2. 定义枚举 OperationType:枚举数据库操作类型,包括INSERTUPDATE

  3. 创建切面类 AutoFillAspect:在该切面中,拦截标注了@AutoFill注解的方法,获取方法参数(即实体对象),并通过反射为其公共字段赋值。

具体步骤:

  1. 定义 @AutoFill 注解:

    import com.sky.enumeration.OperationType;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface AutoFill { //指定数据库操作类型:INSERT或UPDATE OperationType value();}
  2. 定义枚举:

 package com.sky.enumeration; public enum OperationType { INSERT, UPDATE }
  1. 创建切面类 AutoFillAspect

    package com.sky.aspect;import com.sky.annotation.AutoFill;import com.sky.constant.AutoFillConstant;import com.sky.context.BaseContext;import com.sky.enumeration.OperationType;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.stereotype.Component;import java.lang.reflect.Method;import java.time.LocalDateTime;/** * 切面类,实现公共字段的自动填充 */@Aspect@Component@Slf4jpublic class AutoFillAspect { /** * 定义切入点,拦截所有在mapper包下,标注了@AutoFill注解的方法 */ @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 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]; // 获取当前时间和当前用户ID LocalDateTime now = LocalDateTime.now(); Long currentId = BaseContext.getCurrentId(); try { if (operationType == OperationType.INSERT) { // 插入操作,设置创建和更新相关字段 setFieldValue(entity, AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class, now); setFieldValue(entity, AutoFillConstant.SET_CREATE_USER, Long.class, currentId); setFieldValue(entity, AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class, now); setFieldValue(entity, AutoFillConstant.SET_UPDATE_USER, Long.class, currentId); } else if (operationType == OperationType.UPDATE) { // 更新操作,设置更新时间和更新人字段 setFieldValue(entity, AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class, now); setFieldValue(entity, AutoFillConstant.SET_UPDATE_USER, Long.class, currentId); } } catch (Exception e) { log.error(\"公共字段自动填充失败\", e); } } /** * 通过反射为实体对象的指定字段赋值 */ private void setFieldValue(Object entity, String methodName, Class<?> parameterType, Object value) throws Exception { Method method = entity.getClass().getDeclaredMethod(methodName, parameterType); method.invoke(entity, value); }}

说明:

  • @Pointcut:定义切入点,拦截com.sky.mapper包下所有标注了@AutoFill注解的方法。

  • @Before:在目标方法执行前进行公共字段的赋值操作。

  • setFieldValue方法:通过反射调用实体对象的setter方法,为公共字段赋值。

  1. Mapper方法上使用 @AutoFill 注解:

    package com.sky.mapper;import com.sky.annotation.AutoFill;import com.sky.entity.Employee;import com.sky.enumeration.OperationType;import org.apache.ibatis.annotations.Insert;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Update;@Mapperpublic interface EmployeeMapper { /** * 插入员工数据 */ @Insert(\"INSERT INTO employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user, status) \" + \"VALUES (#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}, #{status})\") @AutoFill(OperationType.INSERT) void insert(Employee employee); /** * 更新员工数据 */ @Update(\"UPDATE employee SET name = #{name}, username = #{username}, password = #{password}, phone = #{phone}, sex = #{sex}, id_number = #{idNumber}, \" + \"update_time = 

HttpClient

  • 用于在java程序中构造并发送http请求

苍穹外卖笔记集锦

第三方 HTTP 客户端库 Apache HttpComponents
  1. HttpClient

    • 提供HTTP客户端相关的核心功能,比如创建请求、发送请求、处理响应、连接池管理等。
  2. HttpCore

    • 提供低层次的HTTP传输组件和协议处理,HttpClient库在此之上进行更高级封装。
  3. HttpMime

    • 用于处理多部分(multipart)请求或文件上传等场景。

引入依赖:

<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version></dependency>
主要类:

CloseableHttpClient:

最核心的客户端类,负责创建并执行请求。获取方式:

CloseableHttpClient httpClient = HttpClients.createDefault();

也可以使用 HttpClientBuilder 自定义配置:

CloseableHttpClient httpClient = HttpClientBuilder.create() .setMaxConnTotal(100) // 设置最大连接数 .setMaxConnPerRoute(20) // 每个路由的最大连接数 // ... 其他自定义配置 .build();

请求方法

  • HttpGet:执行 GET 请求,不带请求体

  • HttpPost:执行 POST 请求,可带表单或 JSON 等请求体

  • HttpPut:执行 PUT 请求

  • HttpDelete:执行 DELETE 请求

  • HttpPatch:执行 PATCH 请求(需要 4.2+ 版本支持)

 /** * 测试通过httpclient发送GET方式的请求 */ @Test public void testGET() throws Exception{ //创建httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); //创建请求对象 HttpGet httpGet = new HttpGet(\"http://localhost:8080/user/shop/status\"); //发送请求,接受响应结果 CloseableHttpResponse response = httpClient.execute(httpGet); //获取服务端返回的状态码 int statusCode = response.getStatusLine().getStatusCode(); System.out.println(\"服务端返回的状态码为:\" + statusCode); HttpEntity entity = response.getEntity(); String body = EntityUtils.toString(entity); System.out.println(\"服务端返回的数据为:\" + body); //关闭资源 response.close(); httpClient.close();}
/** * 测试通过httpclient发送POST方式的请求 */ @Test public void testPOST() throws Exception{ // 创建httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); //创建请求对象 HttpPost httpPost = new HttpPost(\"http://localhost:8080/admin/employee/login\"); 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(); }

文件上传(HttpMime)

如果要上传文件,可以使用 HttpMime 提供的 MultipartEntityBuilder

MultipartEntityBuilder builder = MultipartEntityBuilder.create();builder.addTextBody(\"description\", \"测试文件\");builder.addBinaryBody(\"file\", new File(\"test.png\"), ContentType.DEFAULT_BINARY, \"test.png\");HttpEntity multipart = builder.build();HttpPost httpPost = new HttpPost(\"https://example.com/upload\");httpPost.setEntity(multipart);// 执行并处理响应
MyBatis条件查询问题
List<Dish> list(Dish dish);
<select id=\"list\" resultType=\"com.sky.entity.Dish\" parameterType=\"com.sky.entity.Dish\"> select * from dish <where> <if test=\"name != null\">  and name like concat(\'%\',#{name},\'%\') </if> <if test=\"categoryId != null\">  and category_id = #{categoryId}  </if> <if test=\"status != null\">  and status = #{status}  </if> </where> order by create_time desc</select>

这里虽然只传入了一个dish参数,但是MyBatis在 SQL 里会使用 dish 的属性来构造查询条件.

Redis 匹配模式
  • *
    匹配任意数量的字符(包括空字符)。
    例如:

    • user* 能匹配所有以 “user” 开头的键,如 user1username 等。
  • ?
    匹配任意一个单独的字符。
    例如:

    • user? 可以匹配 user1userA,但不能匹配 user12user
  • [abc]
    匹配方括号中任意一个字符。
    例如:

    • user[123] 可以匹配 user1user2user3
  • [a-z]
    通过指定一个字符范围,匹配该范围内的任意字符。
    例如:

    • file[0-9] 能匹配 file0file9 之间的任何一个文件名。
Spring el表达式

使用注解(比如 @Cacheable@CacheEvict@CachePut)进行缓存操作时,需要通过SpEL动态计算缓存键(key)或执行条件(condition )。

key 属性指定生成缓存键的规则
condition 属性指定执行缓存操作的条件

@Cacheable( value = \"users\", key = \"#id\",  // 使用 SpEL,生成 key condition = \"#id > 0\" // 只有当 id>0 时才执行缓存)public User getUserById(int id) { // ... return user;}
  • key = \"#id\" 表示缓存键就是方法第一个参数 id 的值。
  • 如果不指定 key,默认情况下Spring会将所有方法参数组合起来作为缓存键。
访问方法参数
@Cacheable(value = \"users\", key = \"#p0\") public User findUserById(Long id) { // ...}
  • #p0#a0 都表示第一个参数 id 的值。第二个是#p1#a1.
使用方法的返回值做缓存键:
@CachePut(value = \"users\", key = \"#result.id\")public User updateUser(User user) { // 修改用户并返回更新后的 user return user;}
  • 这里 #result 是方法的返回值 User,再取 id 作为缓存键。

使用 condition 属性来决定是否执行缓存操作:

@Cacheable(value = \"numbers\", key = \"#number\", condition = \"#number > 10\")public int processNumber(int number) { // 只有当 number > 10 时才会执行缓存 return number * 2;}
spring task
  • 任务调度工具,按照约定时间自动执行代码逻辑

苍穹外卖笔记集锦

周的位置表示星期几,日和周一般只出现一个,另一个使用?表示,因为不能保证某一天的具体是星期几

苍穹外卖笔记集锦

websocket
  • 一次握手实现双向数据传输
apache poi
  • 读写excel,word等文件
日期相关
  • 指定参数中日期的格式
@DateTimeFormat(pattern = \"yyyy-MM-dd\") LocalDate begin, @DateTimeFormat(pattern = \"yyyy-MM-dd\") LocalDate end
  • 获得某一天的起始时间和结束时间,即0时0分0秒和23时59分59秒
LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN); LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
使用map作为sql查询参数

MyBatis中传入一个 Map 作为参数时,MyBatis会将 Map中的键key视为参数名

Map map = new HashMap();map.put(\"begin\", beginTime);map.put(\"end\", endTime);map.put(\"status\", Orders.COMPLETED);Double turnover = orderMapper.sumByMap(map);
<select id=\"sumByMap\" resultType=\"java.lang.Double\"> select sum(amount) from orders <where> <if test=\"begin != null\">  and order_time > #{begin} </if> <if test=\"end != null\">  and order_time < #{end} </if> <if test=\"status != null\">  and status = #{status} </if> </where> </select>
StringUtils.join()拼接方法
import org.apache.commons.lang3.StringUtils;import java.util.ArrayList;import java.util.List;public class JoinExample { public static void main(String[] args) { // 使用 List 拼接 List<String> list = new ArrayList<>(); list.add(\"张三\"); list.add(\"李四\"); list.add(\"王五\"); String joinedList = StringUtils.join(list, \",\"); System.out.println(\"拼接后的 List:\" + joinedList); // 输出:张三,李四,王五 // 使用数组拼接 String[] arr = {\"苹果\", \"香蕉\", \"橘子\"}; String joinedArray = StringUtils.join(arr, \"-\"); System.out.println(\"拼接后的数组:\" + joinedArray); // 输出:苹果-香蕉-橘子 }}