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
zhuyifan
2025-04-18 10:42:32 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
0c23df7e4c3b706f3a5df652498d5275d360edb4
0c23df7e
1 parent
c034a38f
feat(task): 实现自动创建餐单的周任务
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
106 additions
and
8 deletions
src/main/java/com/infoloop/tianting/config/AppConfig.java
src/main/java/com/infoloop/tianting/logic/task/AutoCreateMealOrderTask.java
src/main/java/com/infoloop/tianting/service/impl/MealOrderServiceImpl.java
src/main/java/com/infoloop/tianting/store/WeeklyTaskStore.java
src/main/java/com/infoloop/tianting/config/AppConfig.java
View file @
0c23df7
...
...
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import
com.infoloop.tianting.server.StorageService
;
import
com.infoloop.tianting.server.StorageServiceFactory
;
import
com.infoloop.tianting.store.LoginCodeStore
;
import
com.infoloop.tianting.store.WeeklyTaskStore
;
import
com.infoloop.tianting.utils.SmsUtil
;
import
io.lettuce.core.api.StatefulRedisConnection
;
import
org.springframework.beans.factory.annotation.Autowired
;
...
...
@@ -16,6 +17,8 @@ import org.springframework.context.annotation.Configuration;
import
org.springframework.context.annotation.Import
;
import
org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter
;
import
java.time.Duration
;
import
static
com
.
infoloop
.
tianting
.
constant
.
ConfigConstants
.
REDIS_CONNECTION
;
@Configuration
...
...
@@ -29,6 +32,13 @@ public class AppConfig {
return
new
LoginCodeStore
(
commands
,
300
);
}
@Bean
public
WeeklyTaskStore
weeklyTaskStore
(
@Autowired
@Qualifier
(
REDIS_CONNECTION
)
final
StatefulRedisConnection
<
String
,
String
>
connection
)
{
final
var
commands
=
connection
.
sync
();
final
var
expireSeconds
=
Duration
.
ofDays
(
7
).
getSeconds
();
return
new
WeeklyTaskStore
(
commands
,
expireSeconds
);
}
// init bean
@Bean
public
SmsUtil
smsUtil
(
@Autowired
SmsConfig
smsConfig
)
{
...
...
src/main/java/com/infoloop/tianting/logic/task/AutoCreateMealOrderTask.java
View file @
0c23df7
...
...
@@ -10,8 +10,11 @@ import com.infoloop.tianting.mealorderservice.SingleDinerRpcResponse;
import
com.infoloop.tianting.mealorderservice.SingleGradeClassRpcResponse
;
import
com.infoloop.tianting.mealorderservice.SingleMealMenuRecordRpcResponse
;
import
com.infoloop.tianting.mealorderservice.SingleMealOrderRpcResponse
;
import
com.infoloop.tianting.mealorderservice.SingleMpAccountDinerRefRpcResponse
;
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.store.WeeklyTaskStore
;
import
com.infoloop.tianting.utils.DateUtil
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
...
...
@@ -21,9 +24,12 @@ import org.springframework.stereotype.Component;
import
java.time.DayOfWeek
;
import
java.time.Instant
;
import
java.time.LocalDate
;
import
java.time.LocalDateTime
;
import
java.time.LocalTime
;
import
java.time.ZonedDateTime
;
import
java.time.temporal.TemporalAdjusters
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Objects
;
...
...
@@ -40,10 +46,40 @@ public class AutoCreateMealOrderTask {
private
final
ClientInventoryServiceRpcClient
clientInventoryServiceRpcClient
;
@Scheduled
(
cron
=
"59 59 23 ? * SUN"
)
private
final
DeliveryRuleServiceRpcClient
deliveryRuleServiceRpcClient
;
private
final
WeeklyTaskStore
weeklyTaskStore
;
@Scheduled
(
cron
=
"0 * * ? * *"
)
public
void
executeTask
()
{
final
int
enterpriseId
=
449
;
final
var
today
=
LocalDate
.
now
();
final
var
now
=
LocalDateTime
.
now
();
final
var
today
=
now
.
toLocalDate
();
// 判断是否本周已执行
final
var
keyPrefix
=
"create-meal-order-task"
;
if
(
weeklyTaskStore
.
isExecutedThisWeek
(
keyPrefix
,
enterpriseId
,
today
))
return
;
final
var
rules
=
deliveryRuleServiceRpcClient
.
getDefaultDeliveryTemplateAndRule
(
enterpriseId
).
getRule
();
if
(
rules
.
getConfigJson
().
getRulesList
().
isEmpty
())
{
return
;
}
final
var
rule
=
rules
.
getConfigJson
().
getRules
(
0
);
final
var
endTime
=
LocalTime
.
parse
(
rule
.
getEndTime
(),
DateUtil
.
HH_MM_SS
);
final
var
enabledDays
=
new
ArrayList
<
DayOfWeek
>();
if
(
Boolean
.
TRUE
.
equals
(
rule
.
getMonday
()))
enabledDays
.
add
(
DayOfWeek
.
MONDAY
);
if
(
Boolean
.
TRUE
.
equals
(
rule
.
getTuesday
()))
enabledDays
.
add
(
DayOfWeek
.
TUESDAY
);
if
(
Boolean
.
TRUE
.
equals
(
rule
.
getWednesday
()))
enabledDays
.
add
(
DayOfWeek
.
WEDNESDAY
);
if
(
Boolean
.
TRUE
.
equals
(
rule
.
getThursday
()))
enabledDays
.
add
(
DayOfWeek
.
THURSDAY
);
if
(
Boolean
.
TRUE
.
equals
(
rule
.
getFriday
()))
enabledDays
.
add
(
DayOfWeek
.
FRIDAY
);
if
(
Boolean
.
TRUE
.
equals
(
rule
.
getSaturday
()))
enabledDays
.
add
(
DayOfWeek
.
SATURDAY
);
if
(
Boolean
.
TRUE
.
equals
(
rule
.
getSunday
()))
enabledDays
.
add
(
DayOfWeek
.
SUNDAY
);
if
(
enabledDays
.
isEmpty
())
{
return
;
}
final
var
maxDay
=
Collections
.
max
(
enabledDays
);
final
var
endDateTime
=
getThisWeekDay
(
today
,
maxDay
).
atTime
(
endTime
);
if
(
now
.
isBefore
(
endDateTime
))
return
;
final
var
thisWeekStart
=
today
.
with
(
DayOfWeek
.
MONDAY
).
atStartOfDay
().
atZone
(
DateUtil
.
CHINA_ZONE
);
final
var
thisWeekEnd
=
today
.
with
(
DayOfWeek
.
SUNDAY
).
atTime
(
LocalTime
.
MAX
).
atZone
(
DateUtil
.
CHINA_ZONE
);
final
var
nextWeekStart
=
today
.
with
(
TemporalAdjusters
.
next
(
DayOfWeek
.
MONDAY
)).
atStartOfDay
().
atZone
(
DateUtil
.
CHINA_ZONE
);
...
...
@@ -59,6 +95,9 @@ public class AutoCreateMealOrderTask {
log
.
info
(
"无未订餐学生,跳过自动订餐任务"
);
return
;
}
final
var
mpAccountDinerRefMap
=
mealOrderServiceRpcClient
.
queryMpAccountDinerRefsByCondition
(
enterpriseId
,
Collections
.
emptyList
(),
notReservedDiners
.
stream
().
map
(
SingleDinerRpcResponse:
:
getId
).
toList
()).
stream
().
collect
(
Collectors
.
toMap
(
SingleMpAccountDinerRefRpcResponse:
:
getDinerId
,
Function
.
identity
(),
(
v1
,
v2
)
->
v1
));
// 所需关联数据查询
final
var
clientIds
=
notReservedDiners
.
stream
().
map
(
SingleDinerRpcResponse:
:
getClientId
).
distinct
().
toList
();
final
var
classIds
=
notReservedDiners
.
stream
().
map
(
SingleDinerRpcResponse:
:
getClassId
).
distinct
().
toList
();
...
...
@@ -66,7 +105,7 @@ public class AutoCreateMealOrderTask {
final
var
gradeClassMap
=
mealOrderServiceRpcClient
.
getGradeClassesByIds
(
enterpriseId
,
classIds
).
stream
().
collect
(
Collectors
.
toMap
(
SingleGradeClassRpcResponse:
:
getId
,
Function
.
identity
()));
final
var
latestMealMenuRecordMap
=
mealOrderServiceRpcClient
.
queryLatestMealMenuRecordsByClientIds
(
enterpriseId
,
clientIds
).
stream
().
collect
(
Collectors
.
toMap
(
SingleMealMenuRecordRpcResponse:
:
getClientId
,
Function
.
identity
()));
// 构建创建请求
final
var
creations
=
notReservedDiners
.
stream
().
map
(
diner
->
toMealOrderCreation
(
diner
,
clientMap
,
gradeClassMap
,
latestMealMenuRecordMap
,
nextWeekStart
)).
filter
(
Objects:
:
nonNull
).
toList
();
final
var
creations
=
notReservedDiners
.
stream
().
map
(
diner
->
toMealOrderCreation
(
diner
,
clientMap
,
gradeClassMap
,
latestMealMenuRecordMap
,
mpAccountDinerRefMap
,
nextWeekStart
)).
filter
(
Objects:
:
nonNull
).
toList
();
if
(
creations
.
isEmpty
())
{
log
.
info
(
"无可创建自动订餐项,跳过任务"
);
return
;
...
...
@@ -78,21 +117,25 @@ public class AutoCreateMealOrderTask {
.
build
();
mealOrderServiceRpcClient
.
batchCreateMealOrders
(
loginInfo
,
creations
);
log
.
info
(
"本周未订餐学生已自动补单 {} 条"
,
creations
.
size
());
// 标记本周已执行
weeklyTaskStore
.
markExecuted
(
keyPrefix
,
enterpriseId
,
today
);
}
private
MealOrderCreation
toMealOrderCreation
(
SingleDinerRpcResponse
diner
,
Map
<
Integer
,
SingleClientRpcResponse
>
clientMap
,
Map
<
Integer
,
SingleGradeClassRpcResponse
>
classMap
,
Map
<
Integer
,
SingleMealMenuRecordRpcResponse
>
menuMap
,
Map
<
Integer
,
SingleMpAccountDinerRefRpcResponse
>
mpAccountDinerRefMap
,
ZonedDateTime
startOfNextWeek
)
{
final
var
menu
=
menuMap
.
get
(
diner
.
getClientId
());
final
var
client
=
clientMap
.
get
(
diner
.
getClientId
());
final
var
gradeClass
=
classMap
.
get
(
diner
.
getClassId
());
if
(
menu
==
null
||
client
==
null
||
gradeClass
==
null
)
return
null
;
final
var
mpAccountDinerRef
=
mpAccountDinerRefMap
.
get
(
diner
.
getId
());
if
(
menu
==
null
||
client
==
null
||
gradeClass
==
null
||
mpAccountDinerRef
==
null
)
return
null
;
return
MealOrderCreation
.
newBuilder
()
.
setClientId
(
diner
.
getClientId
())
.
setMenuId
(
menu
.
getId
())
.
setAccountId
(
0
)
.
setAccountId
(
mpAccountDinerRef
.
getId
()
)
.
setOrderMethod
(
MealOrderOrderMethodEnum
.
MEAL_ORDER_METHOD_SYSTEM
)
.
setDinerId
(
diner
.
getId
())
.
setDinerName
(
diner
.
getName
())
...
...
@@ -105,6 +148,11 @@ public class AutoCreateMealOrderTask {
.
build
();
}
private
LocalDate
getThisWeekDay
(
LocalDate
now
,
DayOfWeek
target
)
{
LocalDate
monday
=
now
.
with
(
DayOfWeek
.
MONDAY
);
return
monday
.
plusDays
(
target
.
getValue
()
-
1L
);
}
private
List
<
MenuSchedule
>
buildNextWeekMeals
(
ZonedDateTime
nextWeekStart
)
{
return
IntStream
.
range
(
0
,
5
)
.
mapToObj
(
i
->
MenuSchedule
.
newBuilder
()
...
...
src/main/java/com/infoloop/tianting/service/impl/MealOrderServiceImpl.java
View file @
0c23df7
...
...
@@ -26,7 +26,6 @@ import java.time.DayOfWeek;
import
java.time.LocalDate
;
import
java.time.LocalDateTime
;
import
java.time.LocalTime
;
import
java.time.temporal.TemporalAdjusters
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
...
...
@@ -170,8 +169,13 @@ public class MealOrderServiceImpl implements MealOrderService {
final
var
minDay
=
Collections
.
min
(
enabledDays
);
final
var
maxDay
=
Collections
.
max
(
enabledDays
);
final
var
today
=
now
.
toLocalDate
();
final
var
startDateTime
=
today
.
with
(
TemporalAdjusters
.
previousOrSame
(
minDay
)
).
atTime
(
startTime
);
final
var
endDateTime
=
today
.
with
(
TemporalAdjusters
.
nextOrSame
(
maxDay
)
).
atTime
(
endTime
);
final
var
startDateTime
=
getThisWeekDate
(
today
,
minDay
).
atTime
(
startTime
);
final
var
endDateTime
=
getThisWeekDate
(
today
,
maxDay
).
atTime
(
endTime
);
return
!
now
.
isBefore
(
startDateTime
)
&&
!
now
.
isAfter
(
endDateTime
);
}
private
static
LocalDate
getThisWeekDate
(
LocalDate
now
,
DayOfWeek
day
)
{
LocalDate
monday
=
now
.
with
(
DayOfWeek
.
MONDAY
);
return
monday
.
plusDays
(
day
.
getValue
()
-
1L
);
// MONDAY 是 1,SUNDAY 是 7
}
}
...
...
src/main/java/com/infoloop/tianting/store/WeeklyTaskStore.java
0 → 100644
View file @
0c23df7
package
com
.
infoloop
.
tianting
.
store
;
import
io.lettuce.core.SetArgs
;
import
io.lettuce.core.api.sync.RedisCommands
;
import
java.time.DayOfWeek
;
import
java.time.LocalDate
;
import
java.time.temporal.WeekFields
;
public
class
WeeklyTaskStore
{
private
final
RedisCommands
<
String
,
String
>
commands
;
private
final
long
expire
;
// 过期时间(秒)
public
WeeklyTaskStore
(
final
RedisCommands
<
String
,
String
>
commands
,
final
long
expire
)
{
this
.
commands
=
commands
;
this
.
expire
=
expire
;
}
public
boolean
isExecutedThisWeek
(
String
keyPrefix
,
int
enterpriseId
,
LocalDate
date
)
{
String
key
=
getWeeklyKey
(
keyPrefix
,
enterpriseId
,
date
);
return
commands
.
exists
(
key
)
>
0
;
}
public
void
markExecuted
(
String
keyPrefix
,
int
enterpriseId
,
LocalDate
date
)
{
String
key
=
getWeeklyKey
(
keyPrefix
,
enterpriseId
,
date
);
commands
.
set
(
key
,
"1"
,
new
SetArgs
().
ex
(
expire
));
}
private
String
getWeeklyKey
(
String
keyPrefix
,
int
enterpriseId
,
LocalDate
date
)
{
WeekFields
weekFields
=
WeekFields
.
of
(
DayOfWeek
.
MONDAY
,
1
);
int
year
=
date
.
getYear
();
int
week
=
date
.
get
(
weekFields
.
weekOfWeekBasedYear
());
return
String
.
format
(
"%s:%d:%d-W%d"
,
keyPrefix
,
enterpriseId
,
year
,
week
);
}
}
Please
register
or
login
to post a comment