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-20 10:00:28 +0800
Browse Files
Options
Browse Files
Download
Plain Diff
Commit
410c0752c3e9f11f06b4fa56c5503f54503c4030
410c0752
2 parents
05043309
070def22
Merge branch 'dev/nongxin-adjust' into 'master'
Dev/nongxin adjust See merge request
!8
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
495 additions
and
198 deletions
build.gradle
src/main/java/com/infoloop/tianting/logic/task/AutoCreateMealOrderTask.java
src/main/java/com/infoloop/tianting/model/vo/DinerMealSuspensionRecordVO.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
build.gradle
View file @
410c075
...
...
@@ -20,6 +20,12 @@ application {
'-Dlogging.path=logs'
]
}
java
{
toolchain
{
languageVersion
=
JavaLanguageVersion
.
of
(
17
)
}
}
ext
{
grpcVersion
=
'1.62.2'
protobufVersion
=
'3.25.3'
...
...
src/main/java/com/infoloop/tianting/logic/task/AutoCreateMealOrderTask.java
View file @
410c075
...
...
@@ -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
intervals
=
suspensionService
.
getSuspensionIntervalsForDiner
(
enterpriseId
,
diner
.
getClientId
(),
diner
.
getClassId
(),
diner
.
getId
()
);
// 构建下周的餐食安排
List
<
MenuSchedule
>
meals
=
buildNextWeekMeals
(
startOfNextWeek
,
suspensionRecord
,
holidayMap
);
// 构建下周的餐食安排
(基于多段停餐区间)
List
<
MenuSchedule
>
meals
=
buildNextWeekMeals
(
startOfNextWeek
,
intervals
,
dateStrToHolidayStatus
);
if
(
meals
.
isEmpty
())
{
return
null
;
...
...
@@ -261,8 +268,10 @@ public class AutoCreateMealOrderTask {
.
build
();
}
// 已由并集窗口版本替代
private
List
<
MenuSchedule
>
buildNextWeekMeals
(
ZonedDateTime
nextWeekStart
,
SingleDinerMealSuspensionRecordRpcResponse
suspensionRecord
,
java
.
util
.
List
<
SuspensionService
.
SuspensionInterval
>
intervals
,
Map
<
String
,
Integer
>
holidayMap
)
{
return
IntStream
.
range
(
0
,
7
)
.
mapToObj
(
i
->
{
...
...
@@ -272,30 +281,23 @@ public class AutoCreateMealOrderTask {
// 检查是否为节假日
final
var
status
=
holidayMap
.
get
(
dateStr
);
if
(
status
!=
null
&&
status
!=
0
&&
status
!=
2
)
{
// 如果不是工作日,返回null
return
null
;
}
// 如果有停餐信息,检查当前循环的日期是否在停餐时间范围内
if
(
suspensionRecord
!=
null
)
{
if
(
intervals
!=
null
&&
!
intervals
.
isEmpty
())
{
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
))
{
return
null
;
}
final
boolean
overlaps
=
intervals
.
stream
()
.
anyMatch
(
interval
->
!(
dateEnd
<
interval
.
startAt
||
dateStart
>
interval
.
endAt
));
if
(
overlaps
)
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/model/vo/DinerMealSuspensionRecordVO.java
View file @
410c075
...
...
@@ -6,6 +6,7 @@ import lombok.Builder;
import
lombok.Data
;
import
java.util.Date
;
import
java.util.List
;
@Data
@Builder
...
...
@@ -38,6 +39,13 @@ public class DinerMealSuspensionRecordVO {
@ApiModelProperty
(
"学号"
)
private
String
studentNo
;
@ApiModelProperty
(
"停餐时间区间列表"
)
private
List
<
Interval
>
intervals
;
@Data
@Builder
@ApiModel
(
"停餐时间区间"
)
public
static
class
Interval
{
@ApiModelProperty
(
"开始时间"
)
private
Date
startAt
;
...
...
@@ -46,4 +54,5 @@ public class DinerMealSuspensionRecordVO {
@ApiModelProperty
(
"备注"
)
private
String
comment
;
}
}
\ No newline at end of file
...
...
src/main/java/com/infoloop/tianting/service/SuspensionService.java
0 → 100644
View file @
410c075
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
;
}
}
/**
* 多段时间窗对象(标准闭区间 \[startAt, endAt\]),用于表示并集后可能出现的多段停餐区间
*/
class
SuspensionInterval
{
public
final
long
startAt
;
public
final
long
endAt
;
public
SuspensionInterval
(
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
);
/**
* 获取单个订餐人的停餐多段时间区间(对学校/班级/学生三个维度的最新记录做标准并集,保留间隙)
*/
List
<
SuspensionInterval
>
getSuspensionIntervalsForDiner
(
int
enterpriseId
,
int
clientId
,
int
classId
,
int
dinerId
);
/**
* 批量获取多个订餐人的停餐多段时间区间。返回 Map<dinerId, List<Interval>>。
*/
Map
<
Integer
,
List
<
SuspensionInterval
>>
getSuspensionIntervalsForDiners
(
int
enterpriseId
,
List
<
SingleDinerRpcResponse
>
diners
);
}
src/main/java/com/infoloop/tianting/service/impl/MealOrderServiceImpl.java
View file @
410c075
...
...
@@ -15,13 +15,13 @@ 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
;
import
org.springframework.stereotype.Service
;
import
java.time.DayOfWeek
;
import
java.time.Instant
;
import
java.time.LocalDate
;
import
java.time.LocalDateTime
;
import
java.time.LocalTime
;
...
...
@@ -30,7 +30,7 @@ import java.util.Collections;
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 +44,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,80 +107,23 @@ 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
(
// 5. 校验停餐记录(学校/班级/学生三个维度最新记录的多段并集区间)
final
var
intervals
=
suspensionService
.
getSuspensionIntervalsForDiner
(
loginInfo
.
getEnterpriseId
(),
List
.
of
(
dinerById
.
getClientId
()
),
null
,
null
dinerById
.
getClientId
(
),
dinerById
.
getClassId
()
,
dinerById
.
getId
()
);
// 直接过滤出学校级别的停餐记录
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
())
{
if
(
intervals
!=
null
&&
!
intervals
.
isEmpty
())
{
// 校验每个订餐日期是否在停餐时间段内
for
(
var
meal
:
createMealOrderDTO
.
getMeals
())
{
final
long
mealDateMs
=
DateUtil
.
parseDate
(
meal
.
getDate
()).
getTime
();
final
boolean
inSuspension
=
intervals
.
stream
()
.
anyMatch
(
i
->
mealDateMs
>=
i
.
startAt
&&
mealDateMs
<=
i
.
endAt
);
if
(
inSuspension
)
{
throw
ClientEndExceptions
.
IncorrectRequestValue
.
build
(
ErrorCodeEnum
.
DINER_MEAL_SUSPENDED
);
}
}
// 5.2 再查询班级级别的停餐信息
var
classSuspensionRecords
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
loginInfo
.
getEnterpriseId
(),
List
.
of
(
dinerById
.
getClientId
()),
List
.
of
(
dinerById
.
getClassId
()),
null
);
// 直接过滤出班级级别的停餐记录
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
())
{
throw
ClientEndExceptions
.
IncorrectRequestValue
.
build
(
ErrorCodeEnum
.
DINER_MEAL_SUSPENDED
);
}
}
// 6. 校验菜单信息
...
...
@@ -304,113 +249,48 @@ 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
();
// 4. 使用公共停餐服务:批量计算每个订餐人的多段停餐区间
Map
<
Integer
,
SingleDinerRpcResponse
>
dinerIdToDiner
=
diners
.
stream
()
.
collect
(
Collectors
.
toMap
(
SingleDinerRpcResponse:
:
getId
,
Function
.
identity
()));
// 6. 批量获取所有学生的停餐信息
var
studentSuspensionRecords
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
loginInfo
.
getEnterpriseId
(),
null
,
null
,
dinerIds
).
stream
().
filter
(
r
->
r
.
getEndAt
()
>
now
.
toEpochMilli
()).
toList
();
Map
<
Integer
,
List
<
SuspensionService
.
SuspensionInterval
>>
dinerIdToIntervals
=
suspensionService
.
getSuspensionIntervalsForDiners
(
loginInfo
.
getEnterpriseId
(),
diners
);
// 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
)
)
));
// 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
;
List
<
DinerMealSuspensionRecordVO
>
result
=
new
ArrayList
<>();
for
(
Map
.
Entry
<
Integer
,
List
<
SuspensionService
.
SuspensionInterval
>>
entry
:
dinerIdToIntervals
.
entrySet
())
{
Integer
dinerId
=
entry
.
getKey
();
SingleDinerRpcResponse
dinerInfo
=
dinerIdToDiner
.
get
(
dinerId
);
if
(
dinerInfo
==
null
)
{
continue
;
}
// 如果只有学校级别的停餐信息
if
(
schoolSuspension
!=
null
&&
studentSuspension
==
null
)
{
return
schoolSuspension
;
List
<
SuspensionService
.
SuspensionInterval
>
intervals
=
entry
.
getValue
();
if
(
intervals
==
null
||
intervals
.
isEmpty
())
{
continue
;
}
// 如果只有学生级别的停餐信息
if
(
schoolSuspension
==
null
&&
studentSuspension
!=
null
)
{
return
studentSuspension
;
}
// 如果两个都有停餐信息,合并时间范围
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
())))
List
<
DinerMealSuspensionRecordVO
.
Interval
>
voIntervals
=
intervals
.
stream
()
.
map
(
i
->
DinerMealSuspensionRecordVO
.
Interval
.
builder
()
.
startAt
(
new
Date
(
i
.
startAt
))
.
endAt
(
new
Date
(
i
.
endAt
))
.
comment
(
""
)
.
build
();
})
.
filter
(
Objects:
:
nonNull
)
.
distinct
()
.
build
())
.
toList
();
result
.
add
(
DinerMealSuspensionRecordVO
.
builder
()
.
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
())
.
intervals
(
voIntervals
)
.
build
());
}
return
result
;
}
/**
* 将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 @
410c075
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
);
final
long
now
=
System
.
currentTimeMillis
();
// 学校维度:classId == 0 && dinerId == 0,按 clientId 过滤
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
schoolLevelRecordsRaw
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
enterpriseId
,
List
.
of
(
clientId
),
null
,
List
.
of
(
0
)
);
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
schoolLevelRecords
=
schoolLevelRecordsRaw
==
null
?
List
.
of
()
:
schoolLevelRecordsRaw
.
stream
().
filter
(
r
->
r
.
getEndAt
()
>
now
).
toList
();
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
>
classLevelRecordsRaw
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
enterpriseId
,
List
.
of
(
clientId
),
List
.
of
(
classId
),
List
.
of
(
0
)
);
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
classLevelRecords
=
classLevelRecordsRaw
==
null
?
List
.
of
()
:
classLevelRecordsRaw
.
stream
().
filter
(
r
->
r
.
getEndAt
()
>
now
).
toList
();
SingleDinerMealSuspensionRecordRpcResponse
latestClassLevelRecord
=
pickLatest
(
classLevelRecords
,
record
->
record
.
getClientId
()
==
clientId
&&
record
.
getClassId
()
==
classId
&&
record
.
getDinerId
()
==
0
);
if
(
latestClassLevelRecord
!=
null
)
{
latestSuspensionRecords
.
add
(
latestClassLevelRecord
);
}
// 学生维度:三个 id 都不为 0
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
dinerLevelRecordsRaw
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
enterpriseId
,
List
.
of
(
clientId
),
List
.
of
(
classId
),
List
.
of
(
dinerId
)
);
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
dinerLevelRecords
=
dinerLevelRecordsRaw
==
null
?
List
.
of
()
:
dinerLevelRecordsRaw
.
stream
().
filter
(
r
->
r
.
getEndAt
()
>
now
).
toList
();
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
;
}
final
long
now
=
System
.
currentTimeMillis
();
// 预构建索引集合
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
>
schoolLevelRecordsRawBatch
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
enterpriseId
,
clientIds
,
null
,
List
.
of
(
0
));
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
classLevelRecordsRawBatch
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
enterpriseId
,
clientIds
,
classIds
,
List
.
of
(
0
));
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
dinerLevelRecordsRawBatch
=
mealOrderServiceRpcClient
.
queryDinerMealSuspensionRecordsByCondition
(
enterpriseId
,
clientIds
,
classIds
,
dinerIds
);
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
schoolLevelRecords
=
schoolLevelRecordsRawBatch
==
null
?
List
.
of
()
:
schoolLevelRecordsRawBatch
.
stream
().
filter
(
r
->
r
.
getEndAt
()
>
now
).
toList
();
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
classLevelRecords
=
classLevelRecordsRawBatch
==
null
?
List
.
of
()
:
classLevelRecordsRawBatch
.
stream
().
filter
(
r
->
r
.
getEndAt
()
>
now
).
toList
();
List
<
SingleDinerMealSuspensionRecordRpcResponse
>
dinerLevelRecords
=
dinerLevelRecordsRawBatch
==
null
?
List
.
of
()
:
dinerLevelRecordsRawBatch
.
stream
().
filter
(
r
->
r
.
getEndAt
()
>
now
).
toList
();
// 学校维度: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
;
}
@Override
public
List
<
SuspensionInterval
>
getSuspensionIntervalsForDiner
(
int
enterpriseId
,
int
clientId
,
int
classId
,
int
dinerId
)
{
Map
<
Integer
,
List
<
SuspensionInterval
>>
map
=
getSuspensionIntervalsForDiners
(
enterpriseId
,
List
.
of
(
SingleDinerRpcResponse
.
newBuilder
()
.
setEnterpriseId
(
enterpriseId
)
.
setClientId
(
clientId
)
.
setClassId
(
classId
)
.
setId
(
dinerId
)
.
build
()
));
return
map
.
getOrDefault
(
dinerId
,
List
.
of
());
}
@Override
public
Map
<
Integer
,
List
<
SuspensionInterval
>>
getSuspensionIntervalsForDiners
(
int
enterpriseId
,
List
<
SingleDinerRpcResponse
>
diners
)
{
Map
<
Integer
,
List
<
SuspensionInterval
>>
result
=
new
HashMap
<>();
if
(
diners
==
null
||
diners
.
isEmpty
())
{
return
result
;
}
final
long
now
=
System
.
currentTimeMillis
();
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
);
// 仅保留每个维度“最新一条”有效记录(createdAt 最大)
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
));
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
));
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
));
for
(
SingleDinerRpcResponse
diner
:
diners
)
{
List
<
SuspensionInterval
>
intervals
=
new
ArrayList
<>();
SingleDinerMealSuspensionRecordRpcResponse
latestSchool
=
clientIdToLatestSchool
.
get
(
diner
.
getClientId
());
SingleDinerMealSuspensionRecordRpcResponse
latestClass
=
clientClassToLatest
.
get
(
diner
.
getClientId
()
+
"#"
+
diner
.
getClassId
());
SingleDinerMealSuspensionRecordRpcResponse
latestDiner
=
dinerIdToLatest
.
get
(
diner
.
getId
());
if
(
latestSchool
!=
null
&&
latestSchool
.
getEndAt
()
>
now
)
{
intervals
.
add
(
new
SuspensionInterval
(
latestSchool
.
getStartAt
(),
latestSchool
.
getEndAt
()));
}
if
(
latestClass
!=
null
&&
latestClass
.
getEndAt
()
>
now
)
{
intervals
.
add
(
new
SuspensionInterval
(
latestClass
.
getStartAt
(),
latestClass
.
getEndAt
()));
}
if
(
latestDiner
!=
null
&&
latestDiner
.
getEndAt
()
>
now
)
{
intervals
.
add
(
new
SuspensionInterval
(
latestDiner
.
getStartAt
(),
latestDiner
.
getEndAt
()));
}
// 归并并集:合并重叠区间、保留间隙
intervals
.
sort
(
Comparator
.
comparingLong
(
i
->
i
.
startAt
));
List
<
SuspensionInterval
>
merged
=
new
ArrayList
<>();
for
(
SuspensionInterval
interval
:
intervals
)
{
if
(
merged
.
isEmpty
()
||
interval
.
startAt
>
merged
.
get
(
merged
.
size
()
-
1
).
endAt
)
{
merged
.
add
(
new
SuspensionInterval
(
interval
.
startAt
,
interval
.
endAt
));
}
else
{
SuspensionInterval
last
=
merged
.
get
(
merged
.
size
()
-
1
);
long
newEnd
=
Math
.
max
(
last
.
endAt
,
interval
.
endAt
);
merged
.
set
(
merged
.
size
()
-
1
,
new
SuspensionInterval
(
last
.
startAt
,
newEnd
));
}
}
if
(!
merged
.
isEmpty
())
{
result
.
put
(
diner
.
getId
(),
merged
);
}
}
return
result
;
}
@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