小怪兽的实习日记
pdf 转图片
前景提示 需要用ai去校验数据,但是ai接口只支持单个图片,但是用户可以上传pdf,所以需要pdf转图片
设计分析
- url判断是否是 pdf
- 否 直接调用ai接口
- 是 进行pdf下载
- pdf转图片 上传阿里云 获取到 图片链接
- 用图片链接调用ai接口
- pdf转图片 上传阿里云 获取到 图片链接
1 | |
pdf转图片方法
1 | |
webclient 异步调用ai接口
因为需要校验的数据很多,为了较短 的响应时间 所以采用异步的方式
- Mono 和 Flux 的定义
它们来自 Project Reactor(Spring WebFlux 的响应式编程库):
- Mono
- 表示 一个异步计算的结果,这个结果要么是:恰好一个值(比如一个字符串,一个对象)或者没有值(empty)
- Flux
- 表示 一个异步的数据流,可能包含 0、1 或多个元素。
Mono
- Mono.just(…)
- 表示创建一个 Mono,里面包着一个已经有的值
1
2Mono<String> mono = Mono.just("Hello");
mono.subscribe(System.out::println); // 打印 Hello
- 表示创建一个 Mono,里面包着一个已经有的值
- Tuples.of(field, data)
- Spring Reactor 提供了一个 Tuples 工具类,可以创建 Tuple(二元组,三元组…)。
- Tuple2
表示两个元素。 - Tuple3
表示三个元素。 1
2
3Tuple2<String, Integer> t = Tuples.of("age", 18);
System.out.println(t.getT1()); // age
System.out.println(t.getT2()); // 18
- Tuple2
- Spring Reactor 提供了一个 Tuples 工具类,可以创建 Tuple(二元组,三元组…)。
(阻塞 vs 响应式)
- 传统阻塞写法
1
2
3
4
5
6// 同步等待结果,直到 WebClient 完成请求
String result = client.get()
.uri("/hello")
.retrieve()
.bodyToMono(String.class) // Mono<String>
.block(); // 阻塞等待,返回 String - 响应式写法
1
2
3
4
5
6
7
8Mono<String> resultMono = client.get()
.uri("/hello")
.retrieve()
.bodyToMono(String.class);
//这里 .subscribe(...) 就像注册了一个“回调”,等结果异步到了才处理。
resultMono.subscribe(data -> {
System.out.println("收到结果: " + data);
});最终写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43// 1) 用来保存每个异步任务(每个任务最终产出 Tuple2<field,data>)
List<Mono<Tuple2<String, String>>> tasksWithField = new ArrayList<>();
// 2) WebClient 只构建一次,复用连接池
WebClient client = WebClient.builder()
.baseUrl(BASE_URL)
.build();
for (String imagePath : imagePaths) {
for (String field : fields) {
String prompt = "解析出图片上的" + field + ",我只要答案,不要其他字";
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("imageUrl", imagePath);
requestBody.put("prompt", prompt);
requestBody.put("responseFormat", "");
requestBody.put("serviceProvider", 1);
// 3) 每个任务:调用远程 AI 服务,解析响应,如果有有效 data 则返回 Tuple2(field,data),否则返回 empty
Mono<Tuple2<String, String>> task = client.post()
.uri(QUALIFICATIONS_AI_URI)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(requestBody)
.retrieve()
.bodyToMono(JsonNode.class)
.flatMap(response -> {
String success = response.has("success") ? response.get("success").asText() : "false";
String data = response.has("data") ? response.get("data").asText() : null;
if ("true".equals(success) && data != null && !"无".equals(data)) {
return Mono.just(Tuples.of(field, data)); // 带上 field 的有效结果
}
return Mono.empty(); // 无效 -> 不发出值
})
.onErrorResume(e -> Mono.empty()); // 网络/解析异常也转成 empty,避免流中断
tasksWithField.add(task);
}
}
// 4) 合并所有任务,收集成 Map<field,data>,再 block() 阻塞获取
Map<String, String> resultMap = Flux.merge(tasksWithField)
.collectMap(Tuple2::getT1, Tuple2::getT2)
.block();
结果
1 | |
PageHelper
前景提示
在今天准备上线的日子,遇见的一个关于PageHelper的坑,以后要注意一下
PageHelper开启分页后会对下面的 sql就行分页,一开始我是放到了第二个SQL上面,但是这样并不合理,因为第一个SQL可能也会查出来很多的数据,这样对第二个分页就没有意义了,但是当我把 PageHelper.startPage(param.getPageNum(), param.getPageSize()) .setReasonable(false) ; 放到一个SQL上面时,skuList是有数据的,但是在最后一行使用 pageHelper 进行构建分页结果的时候 会构建失败 返回空数据
1 | |
- 原因:Pager.build4Mybatis 需要一个page对象,但是我的开启分页放到第一个SQL上面,第二个SQL查询出来的就是一个普通集合了,所以会构建失败
1 | |
总结
- PageHelper.startPage 需要放到最上面的SQL,因为可能会小表驱动大表
- Pager.build4Mybatis 需要一个page类型的参数
线上数据太多导致查询失败
前景提示
做了一个查询功能,中间设计了多个表的查询,在测试环境上数据比较少只有几千条数据,所以测试环境上一直测试的都没问题,结果到了上线的时候,发现线上查不出来数据,只有加索引的几个条件 查询会很快。在无条件查询的情况下 就会出现查询不出来数据的情况
解决
最后解决是 将加索引的几个搜索条件变成了必填条件,这样就会走索引了,这解决方案不是很好,毕竟是限制了用户,但是这是当时紧急情况下的最好的解决方案考虑了,
因为 搜索条件有很多,很多字段是没有索引的,例如创建时间,模糊查询名称,一个模糊查询走索引不慢。但是数据量会查出来两百万条数据。
总结
线上数据量太大,不加条件,会进行全表扫面 速度极慢,让用户必须输入一些条件查询,进行走索引,加快查询速度
MYSQL UPDATE 知识补充
在 UPDATE 中不能在子查询里再次引用同一张表。 例如:
1 | |
为什么不可以呢?
- MySQL 的执行方式是“一边读一边写”
- 而 MySQL 在执行 UPDATE 时是这样工作的:按顺序扫描表 → 找到符合条件的行 → 更新它 。但同时,你又要求 MySQL 在更新过程中,再去读同一张可能正在被更新的表。
- MySQL 的扫描和更新顺序会破坏子查询结果
- 由于 UPDATE 会修改表内容,子查询读取的数据可能:
- 被 UPDATE 刚刚修改过
- 还没被更新
- 扫描顺序变化
- 甚至行可能被锁住
这会导致执行结果 不确定、不稳定、不安全。
- 由于 UPDATE 会修改表内容,子查询读取的数据可能:
- MySQL 的执行方式是“一边读一边写”
解决方法:
- 用子查询包一层原因: 最里面的子查询 会形成一个临时表 tem
1
2
3
4
5
6
7
8
9UPDATE product_collect
SET salePlats = 'marketing_saleplat_swan'
WHERE uuid IN (
SELECT uuid FROM (
SELECT uuid
FROM product_collect
WHERE productType IN ('05','08','09')
) AS tmp
); - 使用临时表
1
2
3
4
5
6
7
8
9
10
11
12
13-- 1. 创建临时表,保存需要更新的 skuNo
CREATE TEMPORARY TABLE tmp AS
SELECT uuid, 'marketing_saleplat_swan' AS newSalePlats
FROM product_collect
WHERE productType IN ('05', '08', '09');
-- 2. 用 JOIN 更新
UPDATE product_collect pc
JOIN tmp t ON pc.uuid = t.uuid
SET pc.salePlats = t.newSalePlats;
-- 3. 更新完成后删除临时表
DROP TEMPORARY TABLE tmp;
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Little Monste'Blog!
评论




