文章目录
需求:
统计一个一天用户资产转移与订单关系
比较时间:昨日的快照(1点生成) ~ 今日的快照(1点生成)
一、准备
表结构:
术词解释:
- 快照
以天为单位,记录用户资产拥有情况。 - 资产
特指:用户购买后所得,用户拥有的份额 - 快照存在意义
用于计算用户每天的收益
二、 解决思路
再解析下需求;
- 用户资产变化要与订单对应上,即:两天快照资产差 === 订单总和(买入 - 卖出)。
- 筛选出异常资产 和 异常订单。
(1)SQL直接解决
即:业务逻辑写在SQL,甚至整个需求一个SQL来解决。
(凡凡不建议这样做,也不喜欢这样做。但若客观条件允许【机器,数据库优化】,或许这样运行效率高,现在的凡凡并不了解~)
- 用户资产差:
今日资产 - 昨日资产
- 订单差(用户为买家则 + , 用户为卖家则 - )
Group by(用户为买家) +
Group by(用户为卖家) —
最后:Group by(用户)
- 最后进行对比。
用户资产差 与 订单差 做对比。
(2)API解决
采用 Lambda 表达式,简洁表明。
1. 获得数据
SQL 查找两天的快照,查找两天的订单
2. 数据关联
即:根据(用户ID,项目ID)进行hash,关联
3. 数据对比
遍历订单列表:
若用户为买方,则减
若用户为卖方,则增
(Tips:若用户有买入,则资产为正;若用户有卖出,则资产为负)
4. 数据筛选(统计)
筛选出异常用户,即累加后资产 != 0的。
三、代码
这主要针对 API 解决思路。(因为现在的凡凡更倾向于这个解决方法,或写代码思想)
(1)获得数据
获取订单列表 和 用户快照
List<OrderPairDto> orderPairs = orderService.getIntervalOrdersByDate(
DateUtil.format(startDate, "YYYY-MM-dd"),
DateUtil.format(endDate, "YYYY-MM-dd"));
List<SnapshotDto> snapshots = assetsSnapshotDao.selectSnapshotsByDates(DateUtil.format(startDate, "YYYY-MM-dd"),
DateUtil.format(endDate, "YYYY-MM-dd"));
(2)数据关联
重写 SnapshotDto
与 OrderPairDto
的 hashCode
、equals
public class SnapshotDto {
private Integer userId;
private Integer projectId;
private int share;
private Date date;
public SnapshotDto() {
}
private SnapshotDto(Builder builder) {
setUserId(builder.userId);
setProjectId(builder.projectId);
setShare(builder.share);
setDate(builder.date);
}
public static Builder newBuilder() {
return new Builder();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SnapshotDto that = (SnapshotDto) o;
return Objects.equals(userId, that.userId) &&
Objects.equals(projectId, that.projectId);
}
@Override
public int hashCode() {
return Objects.hash(userId, projectId);
}
}
订单这块:
注意:Objects.hash(buyerId + sellerId, projectId);
这是为了 卖方 和 买方 的同一订单进行一起运算,
但貌似也不对哦:
比如: 买方 444 和 卖方 445,买方 443 和 卖方 446
这样不就混淆了吗?
实则不然,这里也重写了 equals
,但 Map
进行判重时候,会先进行 hash
判断再根据 equals
。
详细看源码吧。
@Data
public class OrderPairDto {
private Integer buyerId;
private Integer sellerId;
private Integer projectId;
private int actualShare;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
OrderPairDto that = (OrderPairDto) o;
return (Objects.equals(buyerId, that.buyerId) &&
Objects.equals(sellerId, that.sellerId) &&
Objects.equals(projectId, that.projectId))
||
(Objects.equals(buyerId, that.sellerId) &&
Objects.equals(sellerId, that.buyerId) &&
Objects.equals(projectId, that.projectId));
}
@Override
public int hashCode() {
return Objects.hash(buyerId + sellerId, projectId);
}
}
(3)数据对比
- 生成 用户
map
Map<SnapshotDto, SnapshotDto> snapshotMap = snapshots.stream().collect(
Collectors.toMap(snapshotDto -> snapshotDto,
snapshotDto -> snapshotDto,
(oldValue, newValue) -> {
if (oldValue.getDate().compareTo(newValue.getDate()) > 0) {
oldValue.setShare(oldValue.getShare() - newValue.getShare());
return oldValue;
}
oldValue.setShare(newValue.getShare() - oldValue.getShare());
return oldValue;
}));
- 用户 与 订单对比
// 一个订单有买方和卖方,
for (OrderPairDto item : orderPairs) {
// 买方 资产增 就要减去订单份额
SnapshotDto buyerSnapshot = SnapshotDto.newBuilder().userId(item.getBuyerId())
.share(item.getActualShare() * -1).projectId(item.getProjectId()).build();
// 卖方 资产减 就要增加订单份额
SnapshotDto sellerSnapshot = SnapshotDto.newBuilder().userId(item.getSellerId())
.share(item.getActualShare()).projectId(item.getProjectId()).build();
this.computeSnapshotMap(snapshotMap, buyerSnapshot);
this.computeSnapshotMap(snapshotMap, sellerSnapshot);
}
(4)数据筛选
最后 map
中 value
的资产不为0,则为异常,将列举出来
List<SnapshotDto> abnormalList = snapshotMap.values().stream()
.filter(snapshot -> snapshot.getShare() != 0).collect(Collectors.toList());
四、想法
一、写本文的目的
- 记录使用 Lambda 过程。
- 记录工作上,解决需求的思路。
- 启发自己,找到更好的思路。
二、思考:使用SQL方式
将需求交给 数据库 来处理,数据库的优化、执行效率可能往往比弱鸡(凡凡)写的好。
但数据库的处理和优化不也是人写的嘛?
那么什么时候使用SQL较好呢?
现在的凡凡认为:数据库用于简单查询,而业务逻辑应放在代码里。
三、思考:使用API方式
使用这个方法,要将数据库里的数据加载到内存,再进行处理。
这时要考虑数据量和执行效率,并针对进行优化。
对写代码的人要求要高,但这样往往能写出更优雅、更容易维护的代码。
四、最后
想起一位up的座右铭:
保持独立思考、不卑不亢不怂、长成自己想要的样子
新的一年
愿每个人有平凡美好的生活
也有不平凡的美好生活
像往常一样
可以为之付出很多
也无妨为之舍弃很多
不要为了刻意讨好世界而忽略内心中的孩子
无论命运带来什么
都只不过是人间再平常不过的事而已