文章目录

需求:
统计一个一天用户资产转移与订单关系
比较时间:昨日的快照(1点生成) ~ 今日的快照(1点生成)

一、准备

表结构:

术词解释:

  1. 快照
    以天为单位,记录用户资产拥有情况。
  2. 资产
    特指:用户购买后所得,用户拥有的份额
  3. 快照存在意义
    用于计算用户每天的收益



二、 解决思路

再解析下需求;

  1. 用户资产变化要与订单对应上,即:两天快照资产差 === 订单总和(买入 - 卖出)。
  2. 筛选出异常资产 和 异常订单。

(1)SQL直接解决

即:业务逻辑写在SQL,甚至整个需求一个SQL来解决。
(凡凡不建议这样做,也不喜欢这样做。但若客观条件允许【机器,数据库优化】,或许这样运行效率高,现在的凡凡并不了解~)

  1. 用户资产差:

今日资产 - 昨日资产

  1. 订单差(用户为买家则 + , 用户为卖家则 - )

Group by(用户为买家) +
Group by(用户为卖家) —
最后:Group by(用户)

  1. 最后进行对比。

用户资产差 与 订单差 做对比。

(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)数据对比
  1. 生成 用户​​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;
}));
  1. 用户 与 订单对比
// 一个订单有买方和卖方,
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());




四、想法

一、写本文的目的

  1. 记录使用 Lambda 过程。
  2. 记录工作上,解决需求的思路。
  3. 启发自己,找到更好的思路。

二、思考:使用SQL方式

将需求交给 数据库 来处理,数据库的优化、执行效率可能往往比弱鸡(凡凡)写的好。

但数据库的处理和优化不也是人写的嘛?

那么什么时候使用SQL较好呢?

现在的凡凡认为:数据库用于简单查询,而业务逻辑应放在代码里。

三、思考:使用API方式

使用这个方法,要将数据库里的数据加载到内存,再进行处理。

这时要考虑数据量和执行效率,并针对进行优化。

对写代码的人要求要高,但这样往往能写出更优雅、更容易维护的代码。

四、最后

想起一位up的座右铭:

​保持独立思考、不卑不亢不怂、长成自己想要的样子​

新的一年
愿每个人有平凡美好的生活
也有不平凡的美好生活
像往常一样
可以为之付出很多
也无妨为之舍弃很多
不要为了刻意讨好世界而忽略内心中的孩子
无论命运带来什么
都只不过是人间再平常不过的事而已