Toggle navigation
Toggle navigation
This project
Loading...
Sign in
jiujian
/
client-customer-order-api
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
Jiaqi Xia
2025-08-12 18:31:08 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
f1d5bb92cb40fe0f8149f1a50945a25a4a9fefb5
f1d5bb92
1 parent
05043309
feat: add SuspensionService and SuspensionServiceImpl, refactor suspension logic to use union window
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
329 additions
and
199 deletions
src/main/java/com/infoloop/tianting/logic/task/AutoCreateMealOrderTask.java
src/main/java/com/infoloop/tianting/service/SuspensionService.java
src/main/java/com/infoloop/tianting/service/impl/MealOrderServiceImpl.java
src/main/java/com/infoloop/tianting/service/impl/SuspensionServiceImpl.java
src/main/java/com/infoloop/tianting/logic/task/AutoCreateMealOrderTask.java
View file @
f1d5bb9
...
...
@@ -7,6 +7,7 @@ import com.infoloop.tianting.mealorderservice.*;
import
com.infoloop.tianting.service.client.ClientInventoryServiceRpcClient
;
import
com.infoloop.tianting.service.client.DeliveryRuleServiceRpcClient
;
import
com.infoloop.tianting.service.client.MealOrderServiceRpcClient
;
import
com.infoloop.tianting.service.SuspensionService
;
import
com.infoloop.tianting.store.WeeklyTaskStore
;
import
com.infoloop.tianting.utils.DateUtil
;
import
lombok.RequiredArgsConstructor
;
...
...
@@ -46,6 +47,8 @@ public class AutoCreateMealOrderTask {
private
final
WeeklyTaskStore
weeklyTaskStore
;
private
final
SuspensionService
suspensionService
;
@Scheduled
(
cron
=
"0 0/5 * * * ?"
)
public
void
executeTask
()
{
final
int
enterpriseId
=
449
;
...
...
@@ -188,6 +191,7 @@ public class AutoCreateMealOrderTask {
// 构建创建请求
final
var
creations
=
notReservedDiners
.
stream
()
.
map
(
diner
->
toMealOrderCreation
(
enterpriseId
,
diner
,
clientMap
,
gradeClassMap
,
...
...
@@ -217,29 +221,32 @@ public class AutoCreateMealOrderTask {
}
}
private
MealOrderCreation
toMealOrderCreation
(
SingleDinerRpcResponse
diner
,
Map
<
Integer
,
SingleClientRpcResponse
>
clientMap
,
Map
<
Integer
,
SingleGradeClassRpcResponse
>
classMap
,
Map
<
Integer
,
SingleMealMenuRecordRpcResponse
>
menuMap
,
Map
<
Integer
,
SingleMpAccountDinerRefRpcResponse
>
mpAccountDinerRefMap
,
private
MealOrderCreation
toMealOrderCreation
(
int
enterpriseId
,
SingleDinerRpcResponse
diner
,
Map
<
Integer
,
SingleClientRpcResponse
>
clientIdToClient
,
Map
<
Integer
,
SingleGradeClassRpcResponse
>
classIdToClass
,
Map
<
Integer
,
SingleMealMenuRecordRpcResponse
>
clientIdToLatestMenu
,
Map
<
Integer
,
SingleMpAccountDinerRefRpcResponse
>
dinerIdToAccountRef
,
ZonedDateTime
startOfNextWeek
,
Map
<
Integer
,
SingleDinerMealSuspensionRecordRpcResponse
>
schoolSuspensionMap
,
Map
<
Integer
,
SingleDinerMealSuspensionRecordRpcResponse
>
studentSuspensionMap
,
Map
<
String
,
Integer
>
holidayMap
)
{
final
var
menu
=
menuMap
.
get
(
diner
.
getClientId
());
final
var
client
=
clientMap
.
get
(
diner
.
getClientId
());
final
var
gradeClass
=
classMap
.
get
(
diner
.
getClassId
());
final
var
mpAccountDinerRef
=
mpAccountDinerRefMap
.
get
(
diner
.
getId
());
Map
<
String
,
Integer
>
dateStrToHolidayStatus
)
{
final
SingleMealMenuRecordRpcResponse
menu
=
clientIdToLatestMenu
.
get
(
diner
.
getClientId
());
final
SingleClientRpcResponse
client
=
clientIdToClient
.
get
(
diner
.
getClientId
());
final
SingleGradeClassRpcResponse
gradeClass
=
classIdToClass
.
get
(
diner
.
getClassId
());
final
SingleMpAccountDinerRefRpcResponse
mpAccountDinerRef
=
dinerIdToAccountRef
.
get
(
diner
.
getId
());
if
(
menu
==
null
||
client
==
null
||
gradeClass
==
null
)
return
null
;
// 获取停餐信息,优先使用学校级别的
SingleDinerMealSuspensionRecordRpcResponse
suspensionRecord
=
schoolSuspensionMap
.
get
(
diner
.
getClientId
());
if
(
suspensionRecord
==
null
)
{
suspensionRecord
=
studentSuspensionMap
.
get
(
diner
.
getId
());
}
// 计算并集停餐窗口(学校/班级/学生维度的最新记录取并集)
final
var
unionWindow
=
suspensionService
.
getSuspensionUnionWindowForDiner
(
enterpriseId
,
diner
.
getClientId
(),
diner
.
getClassId
(),
diner
.
getId
()
);
// 构建下周的餐食安排
List
<
MenuSchedule
>
meals
=
buildNextWeekMeals
(
startOfNextWeek
,
suspensionRecord
,
holidayMap
);
// 构建下周的餐食安排
(基于并集时间窗)
List
<
MenuSchedule
>
meals
=
buildNextWeekMeals
(
startOfNextWeek
,
unionWindow
,
dateStrToHolidayStatus
);
if
(
meals
.
isEmpty
())
{
return
null
;
...
...
@@ -261,41 +268,36 @@ public class AutoCreateMealOrderTask {
.
build
();
}
private
List
<
MenuSchedule
>
buildNextWeekMeals
(
ZonedDateTime
nextWeekStart
,
SingleDinerMealSuspensionRecordRpcResponse
suspensionRecord
,
Map
<
String
,
Integer
>
holidayMap
)
{
// 已由并集窗口版本替代
private
List
<
MenuSchedule
>
buildNextWeekMeals
(
ZonedDateTime
nextWeekStart
,
SuspensionService
.
SuspensionWindow
unionWindow
,
Map
<
String
,
Integer
>
holidayMap
)
{
return
IntStream
.
range
(
0
,
7
)
.
mapToObj
(
i
->
{
final
var
date
=
nextWeekStart
.
plusDays
(
i
);
final
var
dateStr
=
date
.
format
(
DateUtil
.
YYYY_MM_DD
);
// 检查是否为节假日
final
var
status
=
holidayMap
.
get
(
dateStr
);
if
(
status
!=
null
&&
status
!=
0
&&
status
!=
2
)
{
// 如果不是工作日,返回null
return
null
;
}
// 如果有停餐信息,检查当前循环的日期是否在停餐时间范围内
if
(
suspensionRecord
!=
null
)
{
if
(
unionWindow
!=
null
)
{
final
var
dateStart
=
date
.
with
(
LocalTime
.
MIN
).
toInstant
().
toEpochMilli
();
final
var
dateEnd
=
date
.
with
(
LocalTime
.
MAX
).
toInstant
().
toEpochMilli
();
final
var
suspensionStart
=
suspensionRecord
.
getStartAt
();
final
var
suspensionEnd
=
suspensionRecord
.
getEndAt
();
// 如果停餐时间范围与当前日期有重叠,返回null
if
(!(
dateEnd
<
suspensionStart
||
dateStart
>
suspensionEnd
))
{
if
(!(
dateEnd
<
unionWindow
.
startAt
||
dateStart
>
unionWindow
.
endAt
))
{
return
null
;
}
}
// 如果是工作日且不在停餐时间内,返回正常菜单
return
MenuSchedule
.
newBuilder
()
.
setDate
(
date
.
format
(
DateUtil
.
YYYY_MM_DD_LEFT_SYMBOL
))
.
setMenu
(
"A"
)
.
build
();
})
.
filter
(
Objects:
:
nonNull
)
// 过滤掉null值
.
filter
(
Objects:
:
nonNull
)
.
toList
();
}
...
...
src/main/java/com/infoloop/tianting/service/SuspensionService.java
0 → 100644
View file @
f1d5bb9
package
com
.
infoloop
.
tianting
.
service
;
import
com.infoloop.tianting.mealorderservice.SingleDinerMealSuspensionRecordRpcResponse
;
import
com.infoloop.tianting.mealorderservice.SingleDinerRpcResponse
;
import
java.util.List
;
import
java.util.Map
;
/**
* 公共停餐服务:为单个就餐人提供学校/班级/学生三个维度的最新停餐记录集合
*/
public
interface
SuspensionService
{
/**
* 简单时间窗对象
*/
class
SuspensionWindow
{
public
final
long
startAt
;
public
final
long
endAt
;
public
SuspensionWindow
(
long
startAt
,
long
endAt
)
{
this
.
startAt
=
startAt
;
this
.
endAt
=
endAt
;
}
}
/**
* 获取就餐人的停餐记录(学校/班级/学生三个维度各取最新一条),用于做并集判断
*
* @param enterpriseId 企业ID
* @param clientId 学校ID
* @param classId 班级ID
* @param dinerId 就餐人ID
* @return 最多包含3条记录(学校级一条、班级级一条、学生级一条),按存在性返回非空集合
*/
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
getLatestSuspensionsForDiner
(
int
enterpriseId
,
int
clientId
,
int
classId
,
int
dinerId
);
/**
* 获取就餐人的停餐并集时间窗(取上述三维度最新记录的并集:min(startAt), max(endAt))。
* 若不存在任何停餐记录则返回 null。
*/
SuspensionWindow
getSuspensionUnionWindowForDiner
(
int
enterpriseId
,
int
clientId
,
int
classId
,
int
dinerId
);
/**
* 为多个订餐人批量计算停餐并集时间窗。避免逐个调用,内部一次性查询并用 Map 聚合。
* 返回的 Map key 为 dinerId,仅为存在停餐窗口的订餐人返回条目。
*/
Map
<
Integer
,
SuspensionWindow
>
getSuspensionUnionWindowsForDiners
(
int
enterpriseId
,
List
<
SingleDinerRpcResponse
>
diners
);
}
src/main/java/com/infoloop/tianting/service/impl/MealOrderServiceImpl.java
View file @
f1d5bb9
...
...
@@ -15,6 +15,7 @@ import com.infoloop.tianting.service.client.ClientInventoryServiceRpcClient;
import
com.infoloop.tianting.service.client.DeliveryRuleServiceRpcClient
;
import
com.infoloop.tianting.service.client.MealOrderServiceRpcClient
;
import
com.infoloop.tianting.utils.DateUtil
;
import
com.infoloop.tianting.service.SuspensionService
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
...
...
@@ -31,6 +32,7 @@ import java.util.Date;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Objects
;
import
java.util.function.Function
;
import
java.util.stream.Collectors
;
@Slf4j
...
...
@@ -44,6 +46,8 @@ public class MealOrderServiceImpl implements MealOrderService {
private
final
DeliveryRuleServiceRpcClient
deliveryRuleServiceRpcClient
;
private
final
SuspensionService
suspensionService
;
@Override
public
List
<
MealOrderVO
>
queryMealOrders
(
MealOrderDTO
.
QueryMealOrderDTO
queryMealOrderDTO
)
{
final
var
loginInfo
=
LoginContextHolder
.
getLoginInfo
();
...
...
@@ -105,78 +109,16 @@ public class MealOrderServiceImpl implements MealOrderService {
throw
ClientEndExceptions
.
IncorrectRequestValue
.
build
(
ErrorCodeEnum
.
GRADE_CLASS_NOT_FOUND
);
}
// 5. 校验停餐记录
var
currentTime
=
System
.
currentTimeMillis
();
// 5.1 先查询学校级别的停餐信息
var
schoolSuspensionRecords
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
loginInfo
.
getEnterpriseId
(),
List
.
of
(
dinerById
.
getClientId
()),
null
,
null
);
// 直接过滤出学校级别的停餐记录
if
(
schoolSuspensionRecords
!=
null
)
{
schoolSuspensionRecords
=
schoolSuspensionRecords
.
stream
()
.
filter
(
r
->
r
.
getClassId
()
==
0
&&
r
.
getDinerId
()
==
0
)
.
toList
();
}
if
(
schoolSuspensionRecords
!=
null
&&
!
schoolSuspensionRecords
.
isEmpty
())
{
var
latestSchoolSuspension
=
schoolSuspensionRecords
.
stream
()
.
max
((
a
,
b
)
->
Long
.
compare
(
a
.
getCreatedAt
(),
b
.
getCreatedAt
()))
.
orElse
(
null
);
if
(
latestSchoolSuspension
!=
null
&&
currentTime
>=
latestSchoolSuspension
.
getStartAt
()
&&
currentTime
<=
latestSchoolSuspension
.
getEndAt
())
{
throw
ClientEndExceptions
.
IncorrectRequestValue
.
build
(
ErrorCodeEnum
.
DINER_MEAL_SUSPENDED
);
}
}
// 5.2 再查询班级级别的停餐信息
var
classSuspensionRecords
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
// 5. 校验停餐记录(学校/班级/学生三个维度最新记录并集)
final
var
unionWindow
=
suspensionService
.
getSuspensionUnionWindowForDiner
(
loginInfo
.
getEnterpriseId
(),
List
.
of
(
dinerById
.
getClientId
()
),
List
.
of
(
dinerById
.
getClassId
()
),
null
dinerById
.
getClientId
(
),
dinerById
.
getClassId
(
),
dinerById
.
getId
()
);
// 直接过滤出班级级别的停餐记录
if
(
classSuspensionRecords
!=
null
)
{
classSuspensionRecords
=
classSuspensionRecords
.
stream
()
.
filter
(
r
->
r
.
getDinerId
()
==
0
)
.
toList
();
}
if
(
classSuspensionRecords
!=
null
&&
!
classSuspensionRecords
.
isEmpty
())
{
var
latestClassSuspension
=
classSuspensionRecords
.
stream
()
.
max
((
a
,
b
)
->
Long
.
compare
(
a
.
getCreatedAt
(),
b
.
getCreatedAt
()))
.
orElse
(
null
);
if
(
latestClassSuspension
!=
null
&&
currentTime
>=
latestClassSuspension
.
getStartAt
()
&&
currentTime
<=
latestClassSuspension
.
getEndAt
())
{
throw
ClientEndExceptions
.
IncorrectRequestValue
.
build
(
ErrorCodeEnum
.
DINER_MEAL_SUSPENDED
);
}
}
// 5.3 再查询学生级别的停餐信息
var
studentSuspensionRecords
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
loginInfo
.
getEnterpriseId
(),
List
.
of
(
dinerById
.
getClientId
()),
List
.
of
(
dinerById
.
getClassId
()),
List
.
of
(
dinerById
.
getId
())
);
if
(
studentSuspensionRecords
!=
null
&&
!
studentSuspensionRecords
.
isEmpty
())
{
var
latestStudentSuspension
=
studentSuspensionRecords
.
stream
()
.
max
((
a
,
b
)
->
Long
.
compare
(
a
.
getCreatedAt
(),
b
.
getCreatedAt
()))
.
orElse
(
null
);
if
(
latestStudentSuspension
!=
null
&&
currentTime
>=
latestStudentSuspension
.
getStartAt
()
&&
currentTime
<=
latestStudentSuspension
.
getEndAt
())
{
if
(
unionWindow
!=
null
)
{
final
long
nowMs
=
System
.
currentTimeMillis
();
if
(
nowMs
>=
unionWindow
.
startAt
&&
nowMs
<=
unionWindow
.
endAt
)
{
throw
ClientEndExceptions
.
IncorrectRequestValue
.
build
(
ErrorCodeEnum
.
DINER_MEAL_SUSPENDED
);
}
}
...
...
@@ -304,113 +246,37 @@ public class MealOrderServiceImpl implements MealOrderService {
// 3. 获取所有订餐人信息
var
diners
=
mealOrderServiceRpcClient
.
getDinersByIds
(
loginInfo
.
getEnterpriseId
(),
dinerIds
);
var
dinerById
=
diners
.
stream
().
collect
(
Collectors
.
toMap
(
SingleDinerRpcResponse:
:
getId
,
diner
->
diner
));
// 4. 获取所有不重复的clientId
var
uniqueClientIds
=
diners
.
stream
()
.
map
(
diner
->
Long
.
valueOf
(
diner
.
getClientId
()))
.
distinct
()
.
toList
();
// 5. 批量获取所有学校的停餐信息
final
var
now
=
Instant
.
now
();
var
schoolSuspensionRecords
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
loginInfo
.
getEnterpriseId
(),
null
,
null
,
null
).
stream
().
filter
(
r
->
r
.
getClassId
()
==
0
).
filter
(
r
->
r
.
getEndAt
()
>
now
.
toEpochMilli
()).
toList
();
// 6. 批量获取所有学生的停餐信息
var
studentSuspensionRecords
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
loginInfo
.
getEnterpriseId
(),
null
,
null
,
dinerIds
).
stream
().
filter
(
r
->
r
.
getEndAt
()
>
now
.
toEpochMilli
()).
toList
();
// 4. 使用公共停餐服务:批量计算三维度(学校/班级/学生)“最新记录并集”的停餐窗口,避免逐个调用
Map
<
Integer
,
SingleDinerRpcResponse
>
dinerIdToDiner
=
diners
.
stream
()
.
collect
(
Collectors
.
toMap
(
SingleDinerRpcResponse:
:
getId
,
Function
.
identity
()));
// 7. 构建学校停餐信息Map
var
schoolSuspensionMap
=
schoolSuspensionRecords
.
stream
()
.
collect
(
Collectors
.
groupingBy
(
SingleDinerMealSuspensionRecordRpcResponse:
:
getClientId
,
Collectors
.
collectingAndThen
(
Collectors
.
maxBy
((
a
,
b
)
->
Long
.
compare
(
a
.
getCreatedAt
(),
b
.
getCreatedAt
())),
opt
->
opt
.
map
(
o
->
convertToVO
(
o
,
dinerById
)).
orElse
(
null
)
)
));
Map
<
Integer
,
SuspensionService
.
SuspensionWindow
>
dinerIdToUnionWindow
=
suspensionService
.
getSuspensionUnionWindowsForDiners
(
loginInfo
.
getEnterpriseId
(),
diners
);
// 8. 构建学生停餐信息Map
var
studentSuspensionMap
=
studentSuspensionRecords
.
stream
()
.
collect
(
Collectors
.
groupingBy
(
SingleDinerMealSuspensionRecordRpcResponse:
:
getDinerId
,
Collectors
.
collectingAndThen
(
Collectors
.
maxBy
((
a
,
b
)
->
Long
.
compare
(
a
.
getCreatedAt
(),
b
.
getCreatedAt
())),
opt
->
opt
.
map
(
o
->
convertToVO
(
o
,
dinerById
)).
orElse
(
null
)
)
));
// 9. 获取每个订餐人的停餐信息
return
diners
.
stream
()
.
map
(
diner
->
{
// 获取学校级别的停餐信息
var
schoolSuspension
=
schoolSuspensionMap
.
get
(
Long
.
valueOf
(
diner
.
getClientId
()).
intValue
());
// 获取学生级别的停餐信息
var
studentSuspension
=
studentSuspensionMap
.
get
(
Long
.
valueOf
(
diner
.
getId
()).
intValue
());
// 如果两个都没有停餐信息,返回null
if
(
schoolSuspension
==
null
&&
studentSuspension
==
null
)
{
return
null
;
}
// 如果只有学校级别的停餐信息
if
(
schoolSuspension
!=
null
&&
studentSuspension
==
null
)
{
return
schoolSuspension
;
}
// 如果只有学生级别的停餐信息
if
(
schoolSuspension
==
null
&&
studentSuspension
!=
null
)
{
return
studentSuspension
;
}
// 如果两个都有停餐信息,合并时间范围
List
<
DinerMealSuspensionRecordVO
>
suspensionList
=
dinerIdToUnionWindow
.
entrySet
().
stream
()
.
map
(
entry
->
{
Integer
dinerId
=
entry
.
getKey
();
SuspensionService
.
SuspensionWindow
unionWindow
=
entry
.
getValue
();
SingleDinerRpcResponse
dinerInfo
=
dinerIdToDiner
.
get
(
dinerId
);
return
DinerMealSuspensionRecordVO
.
builder
()
.
id
(
schoolSuspension
.
getId
())
.
enterpriseId
(
schoolSuspension
.
getEnterpriseId
())
.
clientId
(
schoolSuspension
.
getClientId
())
.
classId
(
studentSuspension
.
getClassId
())
.
dinerId
(
studentSuspension
.
getDinerId
())
.
dinerName
(
studentSuspension
.
getDinerName
())
.
studentNo
(
studentSuspension
.
getStudentNo
())
.
startAt
(
new
Date
(
Math
.
min
(
schoolSuspension
.
getStartAt
().
getTime
(),
studentSuspension
.
getStartAt
().
getTime
())))
.
endAt
(
new
Date
(
Math
.
max
(
schoolSuspension
.
getEndAt
().
getTime
(),
studentSuspension
.
getEndAt
().
getTime
())))
.
enterpriseId
(
Long
.
valueOf
(
dinerInfo
.
getEnterpriseId
()))
.
clientId
(
Long
.
valueOf
(
dinerInfo
.
getClientId
()))
.
classId
(
Long
.
valueOf
(
dinerInfo
.
getClassId
()))
.
dinerId
(
Long
.
valueOf
(
dinerInfo
.
getId
()))
.
dinerName
(
dinerInfo
.
getName
())
.
studentNo
(
dinerInfo
.
getStudentNo
())
.
startAt
(
new
Date
(
unionWindow
.
startAt
))
.
endAt
(
new
Date
(
unionWindow
.
endAt
))
.
comment
(
""
)
.
build
();
})
.
filter
(
Objects:
:
nonNull
)
.
distinct
()
.
toList
();
return
suspensionList
;
}
/**
* 将RPC响应转换为VO对象
*/
private
DinerMealSuspensionRecordVO
convertToVO
(
SingleDinerMealSuspensionRecordRpcResponse
response
,
Map
<
Integer
,
SingleDinerRpcResponse
>
dinerById
)
{
if
(
response
==
null
)
{
return
null
;
}
return
DinerMealSuspensionRecordVO
.
builder
()
.
id
(
Long
.
valueOf
(
response
.
getId
()))
.
enterpriseId
(
Long
.
valueOf
(
response
.
getEnterpriseId
()))
.
clientId
(
Long
.
valueOf
(
response
.
getClientId
()))
.
classId
(
Long
.
valueOf
(
response
.
getClassId
()))
.
dinerId
(
Long
.
valueOf
(
response
.
getDinerId
()))
.
dinerName
(
dinerById
.
get
(
response
.
getDinerId
())
!=
null
?
dinerById
.
get
(
response
.
getDinerId
()).
getName
()
:
null
)
.
studentNo
(
dinerById
.
get
(
response
.
getDinerId
())
!=
null
?
dinerById
.
get
(
response
.
getDinerId
()).
getStudentNo
()
:
null
)
.
startAt
(
new
Date
(
response
.
getStartAt
()))
.
endAt
(
new
Date
(
response
.
getEndAt
()))
.
comment
(
response
.
getComment
())
.
build
();
}
// 已改为统一从 SuspensionService 获取并集窗口,不再需要单条转换
}
...
...
src/main/java/com/infoloop/tianting/service/impl/SuspensionServiceImpl.java
0 → 100644
View file @
f1d5bb9
package
com
.
infoloop
.
tianting
.
service
.
impl
;
import
com.infoloop.tianting.mealorderservice.SingleDinerMealSuspensionRecordRpcResponse
;
import
com.infoloop.tianting.mealorderservice.SingleDinerRpcResponse
;
import
com.infoloop.tianting.service.SuspensionService
;
import
com.infoloop.tianting.service.client.MealOrderServiceRpcClient
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.lang.Nullable
;
import
org.springframework.stereotype.Service
;
import
java.util.ArrayList
;
import
java.util.Comparator
;
import
java.util.List
;
import
java.util.Objects
;
import
java.util.Map
;
import
java.util.HashMap
;
import
java.util.function.Function
;
import
java.util.stream.Collectors
;
@Service
@RequiredArgsConstructor
public
class
SuspensionServiceImpl
implements
SuspensionService
{
private
final
MealOrderServiceRpcClient
mealOrderServiceRpcClient
;
@Override
public
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
getLatestSuspensionsForDiner
(
int
enterpriseId
,
int
clientId
,
int
classId
,
int
dinerId
)
{
// 汇总三种维度(学校/班级/学生)各自“最新一条”停餐记录
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
latestSuspensionRecords
=
new
ArrayList
<>(
3
);
// 学校维度:classId == 0 && dinerId == 0,按 clientId 过滤
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
schoolLevelRecords
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
enterpriseId
,
List
.
of
(
clientId
),
null
,
List
.
of
(
0
)
);
SingleDinerMealSuspensionRecordRpcResponse
latestSchoolLevelRecord
=
pickLatest
(
schoolLevelRecords
,
record
->
record
.
getClientId
()
==
clientId
&&
record
.
getClassId
()
==
0
&&
record
.
getDinerId
()
==
0
);
if
(
latestSchoolLevelRecord
!=
null
)
{
latestSuspensionRecords
.
add
(
latestSchoolLevelRecord
);
}
// 班级维度:dinerId == 0,按 clientId + classId 过滤
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
classLevelRecords
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
enterpriseId
,
List
.
of
(
clientId
),
List
.
of
(
classId
),
List
.
of
(
0
)
);
SingleDinerMealSuspensionRecordRpcResponse
latestClassLevelRecord
=
pickLatest
(
classLevelRecords
,
record
->
record
.
getClientId
()
==
clientId
&&
record
.
getClassId
()
==
classId
&&
record
.
getDinerId
()
==
0
);
if
(
latestClassLevelRecord
!=
null
)
{
latestSuspensionRecords
.
add
(
latestClassLevelRecord
);
}
// 学生维度:三个 id 都不为 0
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
dinerLevelRecords
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
enterpriseId
,
List
.
of
(
clientId
),
List
.
of
(
classId
),
List
.
of
(
dinerId
)
);
SingleDinerMealSuspensionRecordRpcResponse
latestDinerLevelRecord
=
pickLatest
(
dinerLevelRecords
,
record
->
record
.
getClientId
()
==
clientId
&&
record
.
getClassId
()
==
classId
&&
record
.
getDinerId
()
==
dinerId
);
if
(
latestDinerLevelRecord
!=
null
)
{
latestSuspensionRecords
.
add
(
latestDinerLevelRecord
);
}
return
latestSuspensionRecords
;
}
@Override
public
SuspensionWindow
getSuspensionUnionWindowForDiner
(
int
enterpriseId
,
int
clientId
,
int
classId
,
int
dinerId
)
{
// 取最新记录集合并生成并集窗口:\[min(startAt), max(endAt)\]
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
latestSuspensions
=
getLatestSuspensionsForDiner
(
enterpriseId
,
clientId
,
classId
,
dinerId
);
if
(
latestSuspensions
==
null
||
latestSuspensions
.
isEmpty
())
{
return
null
;
}
long
minimalStartEpochMillis
=
Long
.
MAX_VALUE
;
long
maximalEndEpochMillis
=
Long
.
MIN_VALUE
;
for
(
SingleDinerMealSuspensionRecordRpcResponse
record
:
latestSuspensions
)
{
if
(
record
.
getStartAt
()
<
minimalStartEpochMillis
)
{
minimalStartEpochMillis
=
record
.
getStartAt
();
}
if
(
record
.
getEndAt
()
>
maximalEndEpochMillis
)
{
maximalEndEpochMillis
=
record
.
getEndAt
();
}
}
if
(
minimalStartEpochMillis
==
Long
.
MAX_VALUE
||
maximalEndEpochMillis
==
Long
.
MIN_VALUE
)
{
return
null
;
}
return
new
SuspensionWindow
(
minimalStartEpochMillis
,
maximalEndEpochMillis
);
}
@Override
public
Map
<
Integer
,
SuspensionWindow
>
getSuspensionUnionWindowsForDiners
(
int
enterpriseId
,
List
<
SingleDinerRpcResponse
>
diners
)
{
// 基于输入 diners 一次性拉取三类停餐记录,再按 diner 维度聚合出最新记录并计算并集窗口
Map
<
Integer
,
SuspensionWindow
>
dinerIdToWindow
=
new
HashMap
<>();
if
(
diners
==
null
||
diners
.
isEmpty
())
{
return
dinerIdToWindow
;
}
// 预构建索引集合
List
<
Integer
>
clientIds
=
diners
.
stream
().
map
(
SingleDinerRpcResponse:
:
getClientId
).
distinct
().
toList
();
List
<
Integer
>
classIds
=
diners
.
stream
().
map
(
SingleDinerRpcResponse:
:
getClassId
).
distinct
().
toList
();
List
<
Integer
>
dinerIds
=
diners
.
stream
().
map
(
SingleDinerRpcResponse:
:
getId
).
toList
();
// 查询三类记录
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
schoolLevelRecords
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
enterpriseId
,
clientIds
,
null
,
List
.
of
(
0
));
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
classLevelRecords
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
enterpriseId
,
clientIds
,
classIds
,
List
.
of
(
0
));
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
dinerLevelRecords
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
enterpriseId
,
clientIds
,
classIds
,
dinerIds
);
// 学校维度:clientId -> latest
Map
<
Integer
,
SingleDinerMealSuspensionRecordRpcResponse
>
clientIdToLatestSchool
=
schoolLevelRecords
==
null
?
Map
.
of
()
:
schoolLevelRecords
.
stream
()
.
filter
(
Objects:
:
nonNull
)
.
filter
(
r
->
r
.
getClassId
()
==
0
&&
r
.
getDinerId
()
==
0
)
.
collect
(
Collectors
.
toMap
(
SingleDinerMealSuspensionRecordRpcResponse:
:
getClientId
,
Function
.
identity
(),
(
a
,
b
)
->
a
.
getCreatedAt
()
>=
b
.
getCreatedAt
()
?
a
:
b
));
// 班级维度:clientId+classId -> latest(使用合成 key)
Map
<
String
,
SingleDinerMealSuspensionRecordRpcResponse
>
clientClassToLatest
=
classLevelRecords
==
null
?
Map
.
of
()
:
classLevelRecords
.
stream
()
.
filter
(
Objects:
:
nonNull
)
.
filter
(
r
->
r
.
getDinerId
()
==
0
)
.
collect
(
Collectors
.
toMap
(
r
->
r
.
getClientId
()
+
"#"
+
r
.
getClassId
(),
Function
.
identity
(),
(
a
,
b
)
->
a
.
getCreatedAt
()
>=
b
.
getCreatedAt
()
?
a
:
b
));
// 学生维度:dinerId -> latest
Map
<
Integer
,
SingleDinerMealSuspensionRecordRpcResponse
>
dinerIdToLatest
=
dinerLevelRecords
==
null
?
Map
.
of
()
:
dinerLevelRecords
.
stream
()
.
filter
(
Objects:
:
nonNull
)
.
collect
(
Collectors
.
toMap
(
SingleDinerMealSuspensionRecordRpcResponse:
:
getDinerId
,
Function
.
identity
(),
(
a
,
b
)
->
a
.
getCreatedAt
()
>=
b
.
getCreatedAt
()
?
a
:
b
));
// 计算每个 diner 的并集窗口
for
(
SingleDinerRpcResponse
diner
:
diners
)
{
SingleDinerMealSuspensionRecordRpcResponse
latestSchool
=
clientIdToLatestSchool
.
get
(
diner
.
getClientId
());
SingleDinerMealSuspensionRecordRpcResponse
latestClass
=
clientClassToLatest
.
get
(
diner
.
getClientId
()
+
"#"
+
diner
.
getClassId
());
SingleDinerMealSuspensionRecordRpcResponse
latestDiner
=
dinerIdToLatest
.
get
(
diner
.
getId
());
long
minStart
=
Long
.
MAX_VALUE
;
long
maxEnd
=
Long
.
MIN_VALUE
;
if
(
latestSchool
!=
null
)
{
if
(
latestSchool
.
getStartAt
()
<
minStart
)
{
minStart
=
latestSchool
.
getStartAt
();
}
if
(
latestSchool
.
getEndAt
()
>
maxEnd
)
{
maxEnd
=
latestSchool
.
getEndAt
();
}
}
if
(
latestClass
!=
null
)
{
if
(
latestClass
.
getStartAt
()
<
minStart
)
{
minStart
=
latestClass
.
getStartAt
();
}
if
(
latestClass
.
getEndAt
()
>
maxEnd
)
{
maxEnd
=
latestClass
.
getEndAt
();
}
}
if
(
latestDiner
!=
null
)
{
if
(
latestDiner
.
getStartAt
()
<
minStart
)
{
minStart
=
latestDiner
.
getStartAt
();
}
if
(
latestDiner
.
getEndAt
()
>
maxEnd
)
{
maxEnd
=
latestDiner
.
getEndAt
();
}
}
if
(
minStart
!=
Long
.
MAX_VALUE
&&
maxEnd
!=
Long
.
MIN_VALUE
)
{
dinerIdToWindow
.
put
(
diner
.
getId
(),
new
SuspensionWindow
(
minStart
,
maxEnd
));
}
}
return
dinerIdToWindow
;
}
@Nullable
private
SingleDinerMealSuspensionRecordRpcResponse
pickLatest
(
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
records
,
java
.
util
.
function
.
Predicate
<
SingleDinerMealSuspensionRecordRpcResponse
>
predicate
)
{
// 在满足谓词条件的集合中,按 createdAt 取最新的一条
if
(
records
==
null
||
records
.
isEmpty
())
{
return
null
;
}
return
records
.
stream
()
.
filter
(
Objects:
:
nonNull
)
.
filter
(
predicate
)
.
max
(
Comparator
.
comparingLong
(
SingleDinerMealSuspensionRecordRpcResponse:
:
getCreatedAt
))
.
orElse
(
null
);
}
}
Please
register
or
login
to post a comment