Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
H
hanni-external-api
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
杨向龙
hanni-external-api
Commits
f180b5d0
Commit
f180b5d0
authored
Mar 20, 2026
by
yangxianglong
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
自采联营门店系统开发
parent
25c521e9
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
617 additions
and
69 deletions
+617
-69
HanniExternalApiApplication.java
...ain/java/cn/nhsoft/hanni/HanniExternalApiApplication.java
+2
-2
ItemController.java
.../java/cn/nhsoft/hanni/item/controller/ItemController.java
+11
-0
LemonToHanniItemConverter.java
...hsoft/hanni/item/converter/LemonToHanniItemConverter.java
+63
-0
ItemSyncDetailPageDTO.java
.../java/cn/nhsoft/hanni/item/dto/ItemSyncDetailPageDTO.java
+17
-0
LemonItemRow.java
src/main/java/cn/nhsoft/hanni/item/dto/LemonItemRow.java
+21
-0
ItemSyncDetail.java
src/main/java/cn/nhsoft/hanni/item/model/ItemSyncDetail.java
+68
-0
ItemSyncSnapshot.java
...ain/java/cn/nhsoft/hanni/item/model/ItemSyncSnapshot.java
+72
-0
ItemSyncDetailRepository.java
...hsoft/hanni/item/repository/ItemSyncDetailRepository.java
+11
-0
ItemSyncSnapshotRepository.java
...oft/hanni/item/repository/ItemSyncSnapshotRepository.java
+11
-0
ItemSyncService.java
...in/java/cn/nhsoft/hanni/item/service/ItemSyncService.java
+6
-2
ItemSyncServiceImpl.java
...n/nhsoft/hanni/item/service/impl/ItemSyncServiceImpl.java
+174
-54
LemengAuthController.java
.../nhsoft/hanni/lemeng/controller/LemengAuthController.java
+3
-3
LemengTokenController.java
...nhsoft/hanni/lemeng/controller/LemengTokenController.java
+5
-5
LemengTokenRefreshJob.java
...ava/cn/nhsoft/hanni/lemeng/job/LemengTokenRefreshJob.java
+5
-1
LemengOAuthToken.java
...n/java/cn/nhsoft/hanni/lemeng/model/LemengOAuthToken.java
+51
-0
LemengOAuthTokenRepository.java
...t/hanni/lemeng/repository/LemengOAuthTokenRepository.java
+10
-0
LemengTokenPersistService.java
...hsoft/hanni/lemeng/service/LemengTokenPersistService.java
+84
-0
TokenHolder.java
src/main/java/cn/nhsoft/hanni/lemeng/util/TokenHolder.java
+3
-2
No files found.
src/main/java/cn/nhsoft/hanni/HanniExternalApiApplication.java
View file @
f180b5d0
...
...
@@ -14,8 +14,8 @@ import org.springframework.scheduling.annotation.EnableScheduling;
*/
@EnableScheduling
@SpringBootApplication
@EntityScan
({
"cn.nhsoft.hanni.item"
})
@EnableJpaRepositories
({
"cn.nhsoft.hanni.item"
})
@EntityScan
({
"cn.nhsoft.hanni.item"
,
"cn.nhsoft.hanni.lemeng"
})
@EnableJpaRepositories
({
"cn.nhsoft.hanni.item"
,
"cn.nhsoft.hanni.lemeng.repository"
})
public
class
HanniExternalApiApplication
{
public
static
void
main
(
String
[]
args
)
{
...
...
src/main/java/cn/nhsoft/hanni/item/controller/ItemController.java
View file @
f180b5d0
package
cn
.
nhsoft
.
hanni
.
item
.
controller
;
import
cn.nhsoft.hanni.common.Result
;
import
cn.nhsoft.hanni.item.dto.ItemSyncDetailPageDTO
;
import
cn.nhsoft.hanni.item.dto.ItemSyncLogPageDTO
;
import
cn.nhsoft.hanni.item.model.ItemSyncLog
;
import
cn.nhsoft.hanni.item.repository.ItemSyncLogRepository
;
...
...
@@ -13,6 +14,7 @@ import org.springframework.data.domain.Page;
import
org.springframework.data.domain.PageRequest
;
import
org.springframework.data.domain.Sort
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.PathVariable
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RequestParam
;
import
org.springframework.web.bind.annotation.RestController
;
...
...
@@ -92,4 +94,13 @@ public class ItemController {
ItemSyncLogPageDTO
dto
=
new
ItemSyncLogPageDTO
(
resultPage
.
getContent
(),
resultPage
.
getTotalElements
());
return
Result
.
success
(
dto
);
}
@Operation
(
summary
=
"商品同步明细"
,
description
=
"按同步日志 ID 分页查询本次任务每条商品的 ADD/UPDATE/SKIP 记录"
)
@GetMapping
(
"/sync-logs/{logId}/details"
)
public
Result
<
ItemSyncDetailPageDTO
>
getSyncLogDetails
(
@Parameter
(
description
=
"同步日志 id"
)
@PathVariable
(
"logId"
)
Long
logId
,
@Parameter
(
description
=
"页码"
)
@RequestParam
(
defaultValue
=
"0"
)
Integer
page
,
@Parameter
(
description
=
"每页条数"
)
@RequestParam
(
defaultValue
=
"50"
)
Integer
size
)
{
return
Result
.
success
(
itemSyncService
.
getSyncDetails
(
logId
,
page
,
size
));
}
}
src/main/java/cn/nhsoft/hanni/item/converter/LemonToHanniItemConverter.java
View file @
f180b5d0
package
cn
.
nhsoft
.
hanni
.
item
.
converter
;
import
cn.nhsoft.hanni.item.dto.HanniItemDTO
;
import
cn.nhsoft.hanni.item.dto.LemonItemRow
;
import
cn.nhsoft.hanni.item.util.LemonItemResponseParser
;
import
org.springframework.util.StringUtils
;
import
com.fasterxml.jackson.core.type.TypeReference
;
import
com.fasterxml.jackson.databind.JsonNode
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
...
...
@@ -56,6 +58,34 @@ public class LemonToHanniItemConverter {
}
}
/**
* 转换为带乐檬最后修改时间的行(同步、跳过逻辑用)
*/
public
List
<
LemonItemRow
>
convertToRows
(
String
lemonResponse
)
{
if
(
lemonResponse
==
null
||
lemonResponse
.
trim
().
isEmpty
())
{
return
new
ArrayList
<>();
}
try
{
JsonNode
root
=
objectMapper
.
readTree
(
lemonResponse
);
JsonNode
itemsNode
=
LemonItemResponseParser
.
findItemsArray
(
root
);
if
(
itemsNode
==
null
||
!
itemsNode
.
isArray
())
{
log
.
warn
(
"乐檬返回中未找到商品列表数组,请检查接口返回结构"
);
return
new
ArrayList
<>();
}
List
<
LemonItemRow
>
result
=
new
ArrayList
<>();
for
(
JsonNode
node
:
itemsNode
)
{
LemonItemRow
row
=
convertSingleToRow
(
node
);
if
(
row
!=
null
)
{
result
.
add
(
row
);
}
}
return
result
;
}
catch
(
Exception
e
)
{
log
.
error
(
"乐檬商品数据转换失败"
,
e
);
return
new
ArrayList
<>();
}
}
private
HanniItemDTO
convertSingle
(
JsonNode
node
)
{
try
{
Map
<
String
,
Object
>
map
=
objectMapper
.
convertValue
(
node
,
new
TypeReference
<
Map
<
String
,
Object
>>()
{});
...
...
@@ -69,6 +99,39 @@ public class LemonToHanniItemConverter {
}
}
private
LemonItemRow
convertSingleToRow
(
JsonNode
node
)
{
try
{
Map
<
String
,
Object
>
map
=
objectMapper
.
convertValue
(
node
,
new
TypeReference
<
Map
<
String
,
Object
>>()
{});
if
(!
shouldIncludeItem
(
map
))
{
return
null
;
}
HanniItemDTO
dto
=
convertFromMap
(
map
);
String
lemonMod
=
extractLemonLastModified
(
map
);
return
new
LemonItemRow
(
dto
,
lemonMod
);
}
catch
(
Exception
e
)
{
log
.
warn
(
"单条商品转换失败: {}"
,
node
,
e
);
return
null
;
}
}
/**
* 从乐檬商品对象解析「最后修改时间」字符串(多字段兼容)
*/
private
String
extractLemonLastModified
(
Map
<
String
,
Object
>
m
)
{
String
[]
keys
=
{
"item_modify_time"
,
"item_update_time"
,
"last_modify_time"
,
"last_update_time"
,
"modify_time"
,
"update_time"
,
"gmt_modified"
,
"item_last_modify_time"
,
"last_download_time"
,
"item_last_update_time"
};
for
(
String
k
:
keys
)
{
Object
v
=
m
.
get
(
k
);
if
(
v
!=
null
&&
StringUtils
.
hasText
(
v
.
toString
()))
{
return
v
.
toString
().
trim
();
}
}
return
null
;
}
/**
* 养馋记商品过滤:是否停购=否,且不为淘汰、删除的商品才传入汉尼
*/
...
...
src/main/java/cn/nhsoft/hanni/item/dto/ItemSyncDetailPageDTO.java
0 → 100644
View file @
f180b5d0
package
cn
.
nhsoft
.
hanni
.
item
.
dto
;
import
cn.nhsoft.hanni.item.model.ItemSyncDetail
;
import
lombok.AllArgsConstructor
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
java.util.List
;
@Data
@NoArgsConstructor
@AllArgsConstructor
public
class
ItemSyncDetailPageDTO
{
private
List
<
ItemSyncDetail
>
records
;
private
long
total
;
}
src/main/java/cn/nhsoft/hanni/item/dto/LemonItemRow.java
0 → 100644
View file @
f180b5d0
package
cn
.
nhsoft
.
hanni
.
item
.
dto
;
import
lombok.AllArgsConstructor
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
/**
* 乐檬单条商品转换结果 + 乐檬侧最后修改时间(用于跳过未变化商品)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public
class
LemonItemRow
{
private
HanniItemDTO
item
;
/**
* 乐檬商品最后修改时间原始字符串(与快照表按字符串相等比较,字段名兼容见转换器)
*/
private
String
lemonLastModified
;
}
src/main/java/cn/nhsoft/hanni/item/model/ItemSyncDetail.java
0 → 100644
View file @
f180b5d0
package
cn
.
nhsoft
.
hanni
.
item
.
model
;
import
lombok.Data
;
import
javax.persistence.*
;
import
java.util.Date
;
/**
* 单次同步任务中的商品级明细(可后台分页查询)
*/
@Data
@Entity
@Table
(
name
=
"item_sync_detail"
,
indexes
=
{
@Index
(
name
=
"idx_detail_sync_log_id"
,
columnList
=
"sync_log_id"
),
@Index
(
name
=
"idx_detail_log_barcode"
,
columnList
=
"sync_log_id,barcode"
)
})
public
class
ItemSyncDetail
{
public
static
final
String
ACTION_ADD
=
"ADD"
;
public
static
final
String
ACTION_UPDATE
=
"UPDATE"
;
public
static
final
String
ACTION_SKIP_TIME
=
"SKIP_TIME"
;
public
static
final
String
ACTION_SKIP_NO_BARCODE
=
"SKIP_NO_BARCODE"
;
@Id
@GeneratedValue
(
strategy
=
GenerationType
.
IDENTITY
)
private
Long
id
;
@Column
(
name
=
"sync_log_id"
,
nullable
=
false
)
private
Long
syncLogId
;
@Column
(
name
=
"barcode"
,
length
=
64
)
private
String
barcode
;
@Column
(
name
=
"gid"
)
private
Long
gid
;
@Column
(
name
=
"name"
,
length
=
512
)
private
String
name
;
/** ADD / UPDATE / SKIP_TIME / SKIP_NO_BARCODE */
@Column
(
name
=
"action"
,
length
=
32
)
private
String
action
;
/** 本次乐檬返回的修改时间 */
@Column
(
name
=
"lemon_last_modified"
,
length
=
64
)
private
String
lemonLastModified
;
/** 快照中上次记录的修改时间(SKIP_TIME 时有值) */
@Column
(
name
=
"stored_lemon_modified"
,
length
=
64
)
private
String
storedLemonModified
;
@Column
(
name
=
"success"
)
private
Boolean
success
;
@Column
(
name
=
"remark"
,
length
=
500
)
private
String
remark
;
@Column
(
name
=
"created_at"
)
@Temporal
(
TemporalType
.
TIMESTAMP
)
private
Date
createdAt
;
@PrePersist
public
void
prePersist
()
{
if
(
createdAt
==
null
)
{
createdAt
=
new
Date
();
}
}
}
src/main/java/cn/nhsoft/hanni/item/model/ItemSyncSnapshot.java
0 → 100644
View file @
f180b5d0
package
cn
.
nhsoft
.
hanni
.
item
.
model
;
import
lombok.Data
;
import
javax.persistence.*
;
import
java.math.BigDecimal
;
import
java.util.Date
;
/**
* 商品同步快照:仅存汉尼所需字段 + 乐檬最后修改时间;时间未变则可跳过推汉尼
*/
@Data
@Entity
@Table
(
name
=
"item_sync_snapshot"
,
indexes
=
{
@Index
(
name
=
"idx_snapshot_lemon_mod"
,
columnList
=
"lemon_last_modified"
)
})
public
class
ItemSyncSnapshot
{
@Id
@GeneratedValue
(
strategy
=
GenerationType
.
IDENTITY
)
private
Long
id
;
@Column
(
name
=
"barcode"
,
length
=
64
,
nullable
=
false
,
unique
=
true
)
private
String
barcode
;
@Column
(
name
=
"gid"
)
private
Long
gid
;
@Column
(
name
=
"name"
,
length
=
512
)
private
String
name
;
@Column
(
name
=
"spec"
,
length
=
255
)
private
String
spec
;
@Column
(
name
=
"saleunit"
,
length
=
64
)
private
String
saleunit
;
@Column
(
name
=
"price"
,
precision
=
18
,
scale
=
4
)
private
BigDecimal
price
;
@Column
(
name
=
"retailprice"
,
precision
=
18
,
scale
=
4
)
private
BigDecimal
retailprice
;
@Column
(
name
=
"parea"
,
length
=
255
)
private
String
parea
;
@Column
(
name
=
"isweight"
,
length
=
8
)
private
String
isweight
;
@Column
(
name
=
"packratio"
,
length
=
64
)
private
String
packratio
;
@Column
(
name
=
"grade"
,
length
=
64
)
private
String
grade
;
@Column
(
name
=
"model"
,
length
=
128
)
private
String
model
;
/** 乐檬侧最后修改时间(接口返回原始字符串,与本次拉取比较) */
@Column
(
name
=
"lemon_last_modified"
,
length
=
64
)
private
String
lemonLastModified
;
@Column
(
name
=
"updated_at"
)
@Temporal
(
TemporalType
.
TIMESTAMP
)
private
Date
updatedAt
;
@PrePersist
@PreUpdate
public
void
touch
()
{
this
.
updatedAt
=
new
Date
();
}
}
src/main/java/cn/nhsoft/hanni/item/repository/ItemSyncDetailRepository.java
0 → 100644
View file @
f180b5d0
package
cn
.
nhsoft
.
hanni
.
item
.
repository
;
import
cn.nhsoft.hanni.item.model.ItemSyncDetail
;
import
org.springframework.data.domain.Page
;
import
org.springframework.data.domain.Pageable
;
import
org.springframework.data.jpa.repository.JpaRepository
;
public
interface
ItemSyncDetailRepository
extends
JpaRepository
<
ItemSyncDetail
,
Long
>
{
Page
<
ItemSyncDetail
>
findBySyncLogIdOrderByIdAsc
(
Long
syncLogId
,
Pageable
pageable
);
}
src/main/java/cn/nhsoft/hanni/item/repository/ItemSyncSnapshotRepository.java
0 → 100644
View file @
f180b5d0
package
cn
.
nhsoft
.
hanni
.
item
.
repository
;
import
cn.nhsoft.hanni.item.model.ItemSyncSnapshot
;
import
org.springframework.data.jpa.repository.JpaRepository
;
import
java.util.Optional
;
public
interface
ItemSyncSnapshotRepository
extends
JpaRepository
<
ItemSyncSnapshot
,
Long
>
{
Optional
<
ItemSyncSnapshot
>
findByBarcode
(
String
barcode
);
}
src/main/java/cn/nhsoft/hanni/item/service/ItemSyncService.java
View file @
f180b5d0
package
cn
.
nhsoft
.
hanni
.
item
.
service
;
import
cn.nhsoft.hanni.item.dto.ItemSyncDetailPageDTO
;
import
cn.nhsoft.hanni.item.dto.LemonItemFindRequest
;
import
java.util.List
;
/**
* 商品档案同步服务接口
* <p>
...
...
@@ -37,4 +36,9 @@ public interface ItemSyncService {
* @param request 乐檬商品档案查询参数(与 nhsoft.amazon.basic.item.find 一致)
*/
void
syncItemsWithParams
(
LemonItemFindRequest
request
);
/**
* 分页查询某次同步任务的商品明细
*/
ItemSyncDetailPageDTO
getSyncDetails
(
Long
syncLogId
,
int
page
,
int
size
);
}
src/main/java/cn/nhsoft/hanni/item/service/impl/ItemSyncServiceImpl.java
View file @
f180b5d0
...
...
@@ -2,26 +2,38 @@ package cn.nhsoft.hanni.item.service.impl;
import
cn.nhsoft.hanni.client.HanniApiClient
;
import
cn.nhsoft.hanni.client.LemonApiClient
;
import
cn.nhsoft.hanni.common.util.Md5Util
;
import
cn.nhsoft.hanni.item.converter.LemonToHanniItemConverter
;
import
cn.nhsoft.hanni.item.dto.HanniItemDTO
;
import
cn.nhsoft.hanni.item.dto.ItemSyncDetailPageDTO
;
import
cn.nhsoft.hanni.item.dto.LemonItemFindRequest
;
import
cn.nhsoft.hanni.item.model.ItemSyncCache
;
import
cn.nhsoft.hanni.item.dto.LemonItemRow
;
import
cn.nhsoft.hanni.item.model.ItemSyncDetail
;
import
cn.nhsoft.hanni.item.model.ItemSyncLog
;
import
cn.nhsoft.hanni.item.repository.ItemSyncCacheRepository
;
import
cn.nhsoft.hanni.item.model.ItemSyncSnapshot
;
import
cn.nhsoft.hanni.item.repository.ItemSyncDetailRepository
;
import
cn.nhsoft.hanni.item.repository.ItemSyncLogRepository
;
import
cn.nhsoft.hanni.item.repository.ItemSyncSnapshotRepository
;
import
cn.nhsoft.hanni.item.service.ItemSyncService
;
import
cn.nhsoft.hanni.item.util.LemonItemResponseParser
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.BeanUtils
;
import
org.springframework.data.domain.Page
;
import
org.springframework.data.domain.PageRequest
;
import
org.springframework.data.domain.Sort
;
import
org.springframework.stereotype.Service
;
import
org.springframework.util.StringUtils
;
import
org.springframework.transaction.PlatformTransactionManager
;
import
org.springframework.transaction.support.TransactionTemplate
;
import
javax.annotation.PostConstruct
;
import
javax.annotation.Resource
;
import
java.text.SimpleDateFormat
;
import
java.util.ArrayList
;
import
java.util.Date
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Optional
;
/**
...
...
@@ -44,11 +56,24 @@ public class ItemSyncServiceImpl implements ItemSyncService {
private
ItemSyncLogRepository
itemSyncLogRepository
;
@Resource
private
ItemSyncCacheRepository
itemSyncCacheRepository
;
private
ItemSyncSnapshotRepository
itemSyncSnapshotRepository
;
@Resource
private
ItemSyncDetailRepository
itemSyncDetailRepository
;
@Resource
private
ObjectMapper
objectMapper
;
@Resource
private
PlatformTransactionManager
platformTransactionManager
;
private
TransactionTemplate
transactionTemplate
;
@PostConstruct
public
void
initTx
()
{
this
.
transactionTemplate
=
new
TransactionTemplate
(
platformTransactionManager
);
}
@FunctionalInterface
private
interface
SyncLogWork
{
void
execute
(
ItemSyncLog
syncLog
)
throws
Exception
;
...
...
@@ -82,16 +107,19 @@ public class ItemSyncServiceImpl implements ItemSyncService {
if
(
request
.
getPageSize
()
==
null
)
{
request
.
setPageSize
(
100
);
}
// 与定时任务一致:按条件多页拉取直至不足一页
doSyncIncremental
(
request
,
"MANUAL"
);
}
/**
* 增量/手动同步:分页拉取,复制完整查询条件
*/
@Override
public
ItemSyncDetailPageDTO
getSyncDetails
(
Long
syncLogId
,
int
page
,
int
size
)
{
PageRequest
pageable
=
PageRequest
.
of
(
page
,
size
,
Sort
.
by
(
Sort
.
Direction
.
ASC
,
"id"
));
Page
<
ItemSyncDetail
>
p
=
itemSyncDetailRepository
.
findBySyncLogIdOrderByIdAsc
(
syncLogId
,
pageable
);
return
new
ItemSyncDetailPageDTO
(
p
.
getContent
(),
p
.
getTotalElements
());
}
private
void
doSyncIncremental
(
LemonItemFindRequest
request
,
String
syncType
)
{
runWithSyncLog
(
syncType
,
syncLog
->
{
List
<
HanniItemDTO
>
allHanniItem
s
=
new
ArrayList
<>();
List
<
LemonItemRow
>
allRow
s
=
new
ArrayList
<>();
int
pageNo
=
request
.
getPageNo
()
!=
null
?
request
.
getPageNo
()
:
1
;
int
pageSize
=
request
.
getPageSize
()
!=
null
?
request
.
getPageSize
()
:
100
;
int
lemengTotal
=
0
;
...
...
@@ -100,14 +128,14 @@ public class ItemSyncServiceImpl implements ItemSyncService {
LemonItemFindRequest
pageReq
=
copyPageRequest
(
request
,
pageNo
,
pageSize
);
log
.
info
(
"从乐檬获取商品档案,pageNo={}, lastDownloadTime={}"
,
pageNo
,
request
.
getLastDownloadTime
());
String
lemonItems
=
lemonApiClient
.
findItems
(
pageReq
);
List
<
HanniItemDTO
>
pageItems
=
lemonToHanniItemConverter
.
convert
(
lemonItems
);
List
<
LemonItemRow
>
pageRows
=
lemonToHanniItemConverter
.
convertToRows
(
lemonItems
);
int
count
=
parseLemengItemCount
(
lemonItems
);
lemengTotal
+=
count
;
if
(
page
Items
==
null
||
pageItem
s
.
isEmpty
())
{
if
(
page
Rows
==
null
||
pageRow
s
.
isEmpty
())
{
break
;
}
all
HanniItems
.
addAll
(
pageItem
s
);
all
Rows
.
addAll
(
pageRow
s
);
if
(
count
<
pageSize
)
{
break
;
}
...
...
@@ -115,7 +143,10 @@ public class ItemSyncServiceImpl implements ItemSyncService {
}
syncLog
.
setLemengCount
(
lemengTotal
);
processAndSyncToHanniFromList
(
allHanniItems
,
syncLog
);
transactionTemplate
.
execute
(
status
->
{
processAndSyncToHanniFromList
(
allRows
,
syncLog
);
return
null
;
});
});
}
...
...
@@ -130,8 +161,10 @@ public class ItemSyncServiceImpl implements ItemSyncService {
private
void
runWithSyncLog
(
String
syncType
,
SyncLogWork
work
)
{
ItemSyncLog
syncLog
=
new
ItemSyncLog
();
syncLog
.
setSyncType
(
syncType
);
long
startMs
=
System
.
currentTimeMillis
();
syncLog
.
setStartTime
(
new
Date
());
syncLog
.
setStatus
(
"RUNNING"
);
syncLog
=
itemSyncLogRepository
.
saveAndFlush
(
syncLog
);
long
startMs
=
System
.
currentTimeMillis
();
try
{
work
.
execute
(
syncLog
);
}
catch
(
Exception
e
)
{
...
...
@@ -141,92 +174,179 @@ public class ItemSyncServiceImpl implements ItemSyncService {
}
finally
{
syncLog
.
setEndTime
(
new
Date
());
syncLog
.
setDurationMs
(
System
.
currentTimeMillis
()
-
startMs
);
if
(
"RUNNING"
.
equals
(
syncLog
.
getStatus
()))
{
syncLog
.
setStatus
(
"FAIL"
);
if
(
syncLog
.
getErrorMsg
()
==
null
||
syncLog
.
getErrorMsg
().
isEmpty
())
{
syncLog
.
setErrorMsg
(
"同步未完成"
);
}
}
itemSyncLogRepository
.
save
(
syncLog
);
}
}
private
void
processAndSyncToHanniFromList
(
List
<
HanniItemDTO
>
hanniItems
,
ItemSyncLog
syncLog
)
{
syncLog
.
setHanniCount
(
hanniItems
!=
null
?
hanniItems
.
size
()
:
0
);
if
(
hanniItems
==
null
||
hanniItems
.
isEmpty
())
{
log
.
warn
(
"转换后无有效商品数据,跳过同步"
);
private
void
processAndSyncToHanniFromList
(
List
<
LemonItemRow
>
rows
,
ItemSyncLog
syncLog
)
{
Long
logId
=
syncLog
.
getId
();
if
(
rows
==
null
||
rows
.
isEmpty
())
{
syncLog
.
setHanniCount
(
0
);
syncLog
.
setStatus
(
"SKIP"
);
syncLog
.
setErrorMsg
(
"转换后无有效商品数据"
);
return
;
}
log
.
info
(
"转换得到 {} 条汉尼格式商品(已过滤停购/淘汰/删除)"
,
hanniItems
.
size
());
syncLog
.
setHanniCount
(
rows
.
size
());
log
.
info
(
"待处理 {} 条商品(按乐檬最后修改时间跳过未变化)"
,
rows
.
size
());
List
<
ItemSyncDetail
>
details
=
new
ArrayList
<>();
List
<
HanniItemDTO
>
toAdd
=
new
ArrayList
<>();
List
<
HanniItemDTO
>
toUpdate
=
new
ArrayList
<>();
for
(
HanniItemDTO
item
:
hanniItems
)
{
List
<
LemonItemRow
>
addRows
=
new
ArrayList
<>();
List
<
LemonItemRow
>
updateRows
=
new
ArrayList
<>();
Map
<
String
,
ItemSyncDetail
>
addDetailByBc
=
new
HashMap
<>();
Map
<
String
,
ItemSyncDetail
>
updateDetailByBc
=
new
HashMap
<>();
for
(
LemonItemRow
row
:
rows
)
{
HanniItemDTO
item
=
row
.
getItem
();
if
(
item
==
null
)
{
continue
;
}
String
barcode
=
item
.
getBarcode
();
if
(
barcode
==
null
||
barcode
.
isEmpty
())
{
if
(!
StringUtils
.
hasText
(
barcode
))
{
ItemSyncDetail
d
=
new
ItemSyncDetail
();
d
.
setSyncLogId
(
logId
);
d
.
setGid
(
item
.
getGid
());
d
.
setName
(
item
.
getName
());
d
.
setAction
(
ItemSyncDetail
.
ACTION_SKIP_NO_BARCODE
);
d
.
setSuccess
(
true
);
d
.
setRemark
(
"缺少条码,不同步"
);
details
.
add
(
d
);
continue
;
}
String
hash
=
computeItemHash
(
item
);
Optional
<
ItemSyncCache
>
cached
=
itemSyncCacheRepository
.
findByBarcode
(
barcode
);
if
(!
cached
.
isPresent
())
{
toAdd
.
add
(
item
);
}
else
if
(!
hash
.
equals
(
cached
.
get
().
getContentHash
()))
{
Optional
<
ItemSyncSnapshot
>
snapOpt
=
itemSyncSnapshotRepository
.
findByBarcode
(
barcode
);
String
curMod
=
normalizeMod
(
row
.
getLemonLastModified
());
boolean
canSkipByTime
=
StringUtils
.
hasText
(
curMod
)
&&
snapOpt
.
isPresent
()
&&
curMod
.
equals
(
normalizeMod
(
snapOpt
.
get
().
getLemonLastModified
()));
if
(
canSkipByTime
)
{
ItemSyncDetail
d
=
new
ItemSyncDetail
();
d
.
setSyncLogId
(
logId
);
d
.
setBarcode
(
barcode
);
d
.
setGid
(
item
.
getGid
());
d
.
setName
(
item
.
getName
());
d
.
setAction
(
ItemSyncDetail
.
ACTION_SKIP_TIME
);
d
.
setLemonLastModified
(
row
.
getLemonLastModified
());
d
.
setStoredLemonModified
(
snapOpt
.
get
().
getLemonLastModified
());
d
.
setSuccess
(
true
);
d
.
setRemark
(
"乐檬最后修改时间未变化,跳过推送汉尼"
);
details
.
add
(
d
);
continue
;
}
ItemSyncDetail
d
=
new
ItemSyncDetail
();
d
.
setSyncLogId
(
logId
);
d
.
setBarcode
(
barcode
);
d
.
setGid
(
item
.
getGid
());
d
.
setName
(
item
.
getName
());
d
.
setLemonLastModified
(
row
.
getLemonLastModified
());
if
(
snapOpt
.
isPresent
())
{
d
.
setAction
(
ItemSyncDetail
.
ACTION_UPDATE
);
d
.
setStoredLemonModified
(
snapOpt
.
get
().
getLemonLastModified
());
toUpdate
.
add
(
item
);
updateRows
.
add
(
row
);
updateDetailByBc
.
put
(
barcode
,
d
);
}
else
{
d
.
setAction
(
ItemSyncDetail
.
ACTION_ADD
);
toAdd
.
add
(
item
);
addRows
.
add
(
row
);
addDetailByBc
.
put
(
barcode
,
d
);
}
details
.
add
(
d
);
}
StringBuilder
responseBuilder
=
new
StringBuilder
();
if
(!
toAdd
.
isEmpty
())
{
log
.
info
(
"新增 {} 条商品到汉尼"
,
toAdd
.
size
());
String
r
=
hanniApiClient
.
saveItems
(
toAdd
);
if
(
r
!=
null
)
{
responseBuilder
.
append
(
"add:"
).
append
(
r
);
}
String
shortR
=
truncateStr
(
r
,
500
);
for
(
LemonItemRow
rr
:
addRows
)
{
ItemSyncDetail
d
=
addDetailByBc
.
get
(
rr
.
getItem
().
getBarcode
());
if
(
d
!=
null
)
{
d
.
setSuccess
(
true
);
d
.
setRemark
(
shortR
);
}
upsertSnapshot
(
rr
);
}
}
if
(!
toUpdate
.
isEmpty
())
{
log
.
info
(
"更新 {} 条变更商品到汉尼"
,
toUpdate
.
size
());
String
r
=
hanniApiClient
.
updateItems
(
toUpdate
);
if
(
r
!=
null
)
{
responseBuilder
.
append
(
" update:"
).
append
(
r
);
}
String
shortR
=
truncateStr
(
r
,
500
);
for
(
LemonItemRow
rr
:
updateRows
)
{
ItemSyncDetail
d
=
updateDetailByBc
.
get
(
rr
.
getItem
().
getBarcode
());
if
(
d
!=
null
)
{
d
.
setSuccess
(
true
);
d
.
setRemark
(
shortR
);
}
for
(
HanniItemDTO
item
:
hanniItems
)
{
String
barcode
=
item
.
getBarcode
();
if
(
barcode
==
null
||
barcode
.
isEmpty
())
{
continue
;
upsertSnapshot
(
rr
);
}
String
hash
=
computeItemHash
(
item
);
ItemSyncCache
cache
=
itemSyncCacheRepository
.
findByBarcode
(
barcode
)
.
orElse
(
new
ItemSyncCache
());
cache
.
setBarcode
(
barcode
);
cache
.
setContentHash
(
hash
);
itemSyncCacheRepository
.
save
(
cache
);
}
saveDetailsInChunks
(
details
);
syncLog
.
setStatus
(
"SUCCESS"
);
syncLog
.
setHanniResponse
(
truncate
Response
(
responseBuilder
.
toString
()
));
syncLog
.
setHanniResponse
(
truncate
Str
(
responseBuilder
.
toString
(),
2000
));
}
private
int
parseLemengItemCount
(
String
lemonJson
)
{
return
LemonItemResponseParser
.
countItemsInResponse
(
lemonJson
,
objectMapper
);
private
static
String
normalizeMod
(
String
s
)
{
return
s
==
null
?
""
:
s
.
trim
(
);
}
private
String
truncateResponse
(
String
response
)
{
if
(
response
==
null
)
{
return
null
;
private
void
upsertSnapshot
(
LemonItemRow
row
)
{
HanniItemDTO
it
=
row
.
getItem
();
String
bc
=
it
.
getBarcode
();
ItemSyncSnapshot
s
=
itemSyncSnapshotRepository
.
findByBarcode
(
bc
).
orElse
(
new
ItemSyncSnapshot
());
s
.
setBarcode
(
bc
);
s
.
setGid
(
it
.
getGid
());
s
.
setName
(
it
.
getName
());
s
.
setSpec
(
it
.
getSpec
());
s
.
setSaleunit
(
it
.
getSaleunit
());
s
.
setPrice
(
it
.
getPrice
());
s
.
setRetailprice
(
it
.
getRetailprice
());
s
.
setParea
(
it
.
getParea
());
s
.
setIsweight
(
it
.
getIsweight
());
s
.
setPackratio
(
it
.
getPackratio
());
s
.
setGrade
(
it
.
getGrade
());
s
.
setModel
(
it
.
getModel
());
s
.
setLemonLastModified
(
row
.
getLemonLastModified
());
itemSyncSnapshotRepository
.
save
(
s
);
}
private
void
saveDetailsInChunks
(
List
<
ItemSyncDetail
>
details
)
{
int
batch
=
200
;
for
(
int
i
=
0
;
i
<
details
.
size
();
i
+=
batch
)
{
int
end
=
Math
.
min
(
i
+
batch
,
details
.
size
());
itemSyncDetailRepository
.
saveAll
(
details
.
subList
(
i
,
end
));
}
return
response
.
length
()
>
2000
?
response
.
substring
(
0
,
2000
)
+
"..."
:
response
;
}
private
String
computeItemHash
(
HanniItemDTO
item
)
{
String
str
=
String
.
format
(
"%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s"
,
nullToEmpty
(
item
.
getBarcode
()),
nullToEmpty
(
item
.
getName
()),
nullToEmpty
(
item
.
getSpec
()),
nullToEmpty
(
item
.
getSaleunit
()),
nullToEmpty
(
item
.
getPrice
()),
nullToEmpty
(
item
.
getRetailprice
()),
nullToEmpty
(
item
.
getParea
()),
nullToEmpty
(
item
.
getIsweight
()),
nullToEmpty
(
item
.
getPackratio
()),
nullToEmpty
(
item
.
getGrade
()),
nullToEmpty
(
item
.
getModel
()));
return
Md5Util
.
md5Hex
(
str
);
private
int
parseLemengItemCount
(
String
lemonJson
)
{
return
LemonItemResponseParser
.
countItemsInResponse
(
lemonJson
,
objectMapper
);
}
private
String
nullToEmpty
(
Object
o
)
{
return
o
==
null
?
""
:
o
.
toString
();
private
String
truncateStr
(
String
response
,
int
maxLen
)
{
if
(
response
==
null
)
{
return
null
;
}
return
response
.
length
()
>
maxLen
?
response
.
substring
(
0
,
maxLen
)
+
"..."
:
response
;
}
@Override
...
...
src/main/java/cn/nhsoft/hanni/lemeng/controller/LemengAuthController.java
View file @
f180b5d0
...
...
@@ -3,7 +3,7 @@ package cn.nhsoft.hanni.lemeng.controller;
import
cn.nhsoft.hanni.lemeng.client.LemengTokenRestClient
;
import
cn.nhsoft.hanni.lemeng.config.LemengProperties
;
import
cn.nhsoft.hanni.lemeng.dto.TokenRefreshResult
;
import
cn.nhsoft.hanni.lemeng.
util.TokenHolder
;
import
cn.nhsoft.hanni.lemeng.
service.LemengTokenPersistService
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.Parameter
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
...
...
@@ -32,7 +32,7 @@ public class LemengAuthController {
private
LemengProperties
lemengProperties
;
@Resource
private
TokenHolder
tokenHolder
;
private
LemengTokenPersistService
lemengTokenPersistService
;
@Operation
(
summary
=
"OAuth2 授权回调"
,
description
=
"使用 code 换取 access_token 并写入 TokenHolder;无 code 时跳转 Swagger"
)
@GetMapping
(
"/code"
)
...
...
@@ -61,7 +61,7 @@ public class LemengAuthController {
log
.
error
(
"无法解析账套号,请配置 lemeng.app.system-book-code"
);
return
"redirect:/swagger-ui.html?lemeng_auth=merchant"
;
}
tokenHolder
.
setToken
(
book
,
result
.
getAccessToken
(),
result
.
getRefreshToke
n
());
lemengTokenPersistService
.
saveAndCache
(
book
,
result
.
getAccessToken
(),
result
.
getRefreshToken
(),
result
.
getExpiresI
n
());
log
.
info
(
"OAuth 授权成功,账套: {}"
,
book
);
}
catch
(
Exception
e
)
{
log
.
error
(
"乐檬 OAuth 获取 Token 失败"
,
e
);
...
...
src/main/java/cn/nhsoft/hanni/lemeng/controller/LemengTokenController.java
View file @
f180b5d0
...
...
@@ -4,7 +4,7 @@ import cn.nhsoft.hanni.common.Result;
import
cn.nhsoft.hanni.lemeng.client.LemengTokenRestClient
;
import
cn.nhsoft.hanni.lemeng.config.LemengProperties
;
import
cn.nhsoft.hanni.lemeng.dto.TokenRefreshResult
;
import
cn.nhsoft.hanni.lemeng.
util.TokenHolder
;
import
cn.nhsoft.hanni.lemeng.
service.LemengTokenPersistService
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.Parameter
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
...
...
@@ -34,7 +34,7 @@ public class LemengTokenController {
private
LemengTokenRestClient
lemengTokenRestClient
;
@Resource
private
TokenHolder
tokenHolder
;
private
LemengTokenPersistService
lemengTokenPersistService
;
@Resource
private
LemengProperties
lemengProperties
;
...
...
@@ -59,7 +59,7 @@ public class LemengTokenController {
if
(!
StringUtils
.
hasText
(
book
))
{
return
Result
.
error
(
"无法解析账套号,请配置 lemeng.app.system-book-code 或检查 token"
);
}
tokenHolder
.
setToken
(
book
,
result
.
getAccessToken
(),
result
.
getRefreshToke
n
());
lemengTokenPersistService
.
saveAndCache
(
book
,
result
.
getAccessToken
(),
result
.
getRefreshToken
(),
result
.
getExpiresI
n
());
return
Result
.
success
(
result
);
}
catch
(
HttpClientErrorException
e
)
{
HttpStatus
status
=
e
.
getStatusCode
();
...
...
@@ -85,7 +85,7 @@ public class LemengTokenController {
}
}
@Operation
(
summary
=
"手动设置 Token"
,
description
=
"将 access_token、refresh_token 写入
内存 TokenHolder
,供乐檬商品查询使用"
)
@Operation
(
summary
=
"手动设置 Token"
,
description
=
"将 access_token、refresh_token 写入
数据库并同步到内存
,供乐檬商品查询使用"
)
@PostMapping
(
"/set"
)
public
Result
<
String
>
setToken
(
@Parameter
(
description
=
"账套号"
,
required
=
true
)
@RequestParam
String
systemBookCode
,
...
...
@@ -94,7 +94,7 @@ public class LemengTokenController {
if
(!
StringUtils
.
hasText
(
systemBookCode
)
||
!
StringUtils
.
hasText
(
accessToken
)
||
!
StringUtils
.
hasText
(
refreshToken
))
{
return
Result
.
error
(
"systemBookCode、accessToken、refreshToken 均不能为空"
);
}
tokenHolder
.
setToken
(
systemBookCode
,
accessToken
,
refreshToken
);
lemengTokenPersistService
.
saveAndCache
(
systemBookCode
,
accessToken
,
refreshToken
);
log
.
info
(
"手动设置 Token 成功,账套号: {}"
,
systemBookCode
);
return
Result
.
success
(
systemBookCode
);
}
...
...
src/main/java/cn/nhsoft/hanni/lemeng/job/LemengTokenRefreshJob.java
View file @
f180b5d0
...
...
@@ -3,6 +3,7 @@ package cn.nhsoft.hanni.lemeng.job;
import
cn.nhsoft.hanni.lemeng.client.LemengTokenRestClient
;
import
cn.nhsoft.hanni.lemeng.config.LemengProperties
;
import
cn.nhsoft.hanni.lemeng.dto.TokenRefreshResult
;
import
cn.nhsoft.hanni.lemeng.service.LemengTokenPersistService
;
import
cn.nhsoft.hanni.lemeng.util.TokenHolder
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
;
...
...
@@ -28,6 +29,9 @@ public class LemengTokenRefreshJob {
private
TokenHolder
tokenHolder
;
@Resource
private
LemengTokenPersistService
lemengTokenPersistService
;
@Resource
private
LemengProperties
lemengProperties
;
@Scheduled
(
fixedDelay
=
50
*
60
*
1000
,
initialDelay
=
60
*
1000
)
...
...
@@ -59,7 +63,7 @@ public class LemengTokenRefreshJob {
if
(!
StringUtils
.
hasText
(
book
))
{
book
=
systemBookCode
;
}
tokenHolder
.
setToken
(
book
,
result
.
getAccessToken
(),
result
.
getRefreshToke
n
());
lemengTokenPersistService
.
saveAndCache
(
book
,
result
.
getAccessToken
(),
result
.
getRefreshToken
(),
result
.
getExpiresI
n
());
log
.
info
(
"定时刷新 token 成功,账套: {}"
,
book
);
}
catch
(
Exception
ex
)
{
log
.
error
(
"定时刷新账套 {} token 失败"
,
systemBookCode
,
ex
);
...
...
src/main/java/cn/nhsoft/hanni/lemeng/model/LemengOAuthToken.java
0 → 100644
View file @
f180b5d0
package
cn
.
nhsoft
.
hanni
.
lemeng
.
model
;
import
lombok.Getter
;
import
lombok.Setter
;
import
javax.persistence.Column
;
import
javax.persistence.Entity
;
import
javax.persistence.Id
;
import
javax.persistence.PrePersist
;
import
javax.persistence.PreUpdate
;
import
javax.persistence.Table
;
import
javax.persistence.Temporal
;
import
javax.persistence.TemporalType
;
import
java.util.Date
;
/**
* 乐檬 OAuth Token 持久化(access / refresh),按账套唯一
*/
@Getter
@Setter
@Entity
@Table
(
name
=
"lemeng_oauth_token"
)
public
class
LemengOAuthToken
{
@Id
@Column
(
name
=
"system_book_code"
,
length
=
64
,
nullable
=
false
)
private
String
systemBookCode
;
@Column
(
name
=
"access_token"
,
columnDefinition
=
"TEXT"
,
nullable
=
false
)
private
String
accessToken
;
@Column
(
name
=
"refresh_token"
,
columnDefinition
=
"TEXT"
)
private
String
refreshToken
;
/**
* access_token 预计过期时间(根据 OAuth 返回的 expires_in 推算,可能为空)
*/
@Column
(
name
=
"expires_at"
)
@Temporal
(
TemporalType
.
TIMESTAMP
)
private
Date
expiresAt
;
@Column
(
name
=
"updated_at"
)
@Temporal
(
TemporalType
.
TIMESTAMP
)
private
Date
updatedAt
;
@PrePersist
@PreUpdate
public
void
touchUpdatedAt
()
{
this
.
updatedAt
=
new
Date
();
}
}
src/main/java/cn/nhsoft/hanni/lemeng/repository/LemengOAuthTokenRepository.java
0 → 100644
View file @
f180b5d0
package
cn
.
nhsoft
.
hanni
.
lemeng
.
repository
;
import
cn.nhsoft.hanni.lemeng.model.LemengOAuthToken
;
import
org.springframework.data.jpa.repository.JpaRepository
;
/**
* 乐檬 OAuth Token 表
*/
public
interface
LemengOAuthTokenRepository
extends
JpaRepository
<
LemengOAuthToken
,
String
>
{
}
src/main/java/cn/nhsoft/hanni/lemeng/service/LemengTokenPersistService.java
0 → 100644
View file @
f180b5d0
package
cn
.
nhsoft
.
hanni
.
lemeng
.
service
;
import
cn.nhsoft.hanni.lemeng.model.LemengOAuthToken
;
import
cn.nhsoft.hanni.lemeng.repository.LemengOAuthTokenRepository
;
import
cn.nhsoft.hanni.lemeng.util.TokenHolder
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.boot.ApplicationArguments
;
import
org.springframework.boot.ApplicationRunner
;
import
org.springframework.core.Ordered
;
import
org.springframework.core.annotation.Order
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
import
org.springframework.util.StringUtils
;
import
javax.annotation.Resource
;
import
java.util.Date
;
import
java.util.List
;
/**
* 乐檬 Token 数据库持久化,并与内存 {@link TokenHolder} 同步。
* <p>启动时从库加载到内存;OAuth / 手动设置 / 定时刷新写入库并更新缓存。</p>
*/
@Slf4j
@Order
(
Ordered
.
HIGHEST_PRECEDENCE
)
@Service
public
class
LemengTokenPersistService
implements
ApplicationRunner
{
@Resource
private
LemengOAuthTokenRepository
lemengOAuthTokenRepository
;
@Resource
private
TokenHolder
tokenHolder
;
@Override
@Transactional
(
readOnly
=
true
)
public
void
run
(
ApplicationArguments
args
)
{
reloadFromDatabaseIntoMemory
();
}
/**
* 从数据库加载全部账套 Token 到 {@link TokenHolder}
*/
@Transactional
(
readOnly
=
true
)
public
void
reloadFromDatabaseIntoMemory
()
{
List
<
LemengOAuthToken
>
all
=
lemengOAuthTokenRepository
.
findAll
();
int
loaded
=
0
;
for
(
LemengOAuthToken
row
:
all
)
{
if
(
row
!=
null
&&
StringUtils
.
hasText
(
row
.
getSystemBookCode
())
&&
StringUtils
.
hasText
(
row
.
getAccessToken
()))
{
tokenHolder
.
setToken
(
row
.
getSystemBookCode
(),
row
.
getAccessToken
(),
row
.
getRefreshToken
());
loaded
++;
}
}
log
.
info
(
"乐檬 OAuth Token 已从数据库加载到内存,记录数: {},有效账套: {}"
,
all
.
size
(),
loaded
);
}
/**
* 持久化并刷新内存缓存
*
* @param expiresIn OAuth 返回的过期秒数,可为 null
*/
@Transactional
public
void
saveAndCache
(
String
systemBookCode
,
String
accessToken
,
String
refreshToken
,
Integer
expiresIn
)
{
if
(!
StringUtils
.
hasText
(
systemBookCode
)
||
!
StringUtils
.
hasText
(
accessToken
))
{
return
;
}
LemengOAuthToken
entity
=
lemengOAuthTokenRepository
.
findById
(
systemBookCode
).
orElse
(
new
LemengOAuthToken
());
entity
.
setSystemBookCode
(
systemBookCode
);
entity
.
setAccessToken
(
accessToken
);
entity
.
setRefreshToken
(
StringUtils
.
hasText
(
refreshToken
)
?
refreshToken
:
null
);
if
(
expiresIn
!=
null
&&
expiresIn
>
0
)
{
entity
.
setExpiresAt
(
new
Date
(
System
.
currentTimeMillis
()
+
expiresIn
*
1000L
));
}
lemengOAuthTokenRepository
.
save
(
entity
);
tokenHolder
.
setToken
(
systemBookCode
,
accessToken
,
refreshToken
);
}
/**
* 无 expires_in 时持久化(如手动 set)
*/
@Transactional
public
void
saveAndCache
(
String
systemBookCode
,
String
accessToken
,
String
refreshToken
)
{
saveAndCache
(
systemBookCode
,
accessToken
,
refreshToken
,
null
);
}
}
src/main/java/cn/nhsoft/hanni/lemeng/util/TokenHolder.java
View file @
f180b5d0
...
...
@@ -8,8 +8,9 @@ import java.util.Map;
import
java.util.concurrent.ConcurrentHashMap
;
/**
* 内存 Token 存储,供 RestTemplate 方式商品查询及定时刷新使用
* 通过 /lemeng/token/set 设置后,LemonApiClient 从此读取
* 进程内内存 Token 缓存,供 RestTemplate 商品查询及定时刷新使用。
* <p>写入请通过 {@link cn.nhsoft.hanni.lemeng.service.LemengTokenPersistService},以保证与数据库同步;
* 服务启动时会从表 {@code lemeng_oauth_token} 加载到此。</p>
*/
@Component
public
class
TokenHolder
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment