Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
custom-server
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
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
lizhonghong
custom-server
Commits
4d862d6f
Commit
4d862d6f
authored
Jun 22, 2026
by
Lizh
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
本地部署前端代码与后端代码进行自测,同时修复自测过程中的问题
parent
c701c5ab
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
460 additions
and
249 deletions
+460
-249
.gitignore
+0
-1
custom-server-app/src/main/java/com/jomalls/custom/app/dto/CustomProductInfoSaveSnakeDTO.java
+1
-20
custom-server-app/src/main/java/com/jomalls/custom/app/dto/FactoryPriceRelSnakeDTO.java
+3
-0
custom-server-app/src/main/java/com/jomalls/custom/app/exception/InvalidTokenException.java
+41
-5
custom-server-app/src/main/java/com/jomalls/custom/app/service/impl/CustomProductInfoServiceImpl.java
+52
-21
custom-server-core/src/main/java/com/jomalls/custom/enums/CodeEnum.java
+6
-1
custom-server-core/src/main/java/com/jomalls/custom/page/PageRequest.java
+16
-11
custom-server-core/src/main/java/com/jomalls/custom/utils/R.java
+3
-3
custom-server-starter/src/main/bin/startup.sh
+25
-0
custom-server-starter/src/main/bin/stop.sh
+33
-0
custom-server-starter/src/main/java/com/jomalls/custom/config/CommonExceptionHandlerAdvice.java
+5
-5
custom-server-starter/src/main/java/com/jomalls/custom/config/SecurityInterceptor.java
+4
-9
custom-server-starter/src/main/java/com/jomalls/custom/config/WebMvcConfiguration.java
+24
-13
custom-server-starter/src/main/resources/application-datasource.properties
+0
-23
custom-server-starter/src/main/resources/application-datasource.yml
+23
-0
custom-server-starter/src/main/resources/application-redis.properties
+0
-34
custom-server-starter/src/main/resources/application-redis.yml
+38
-0
custom-server-starter/src/main/resources/application.properties
+0
-71
custom-server-starter/src/main/resources/application.yml
+104
-8
custom-server-starter/src/main/resources/logback-spring.xml
+82
-24
No files found.
.gitignore
View file @
4d862d6f
...
...
@@ -9,7 +9,6 @@
.project
.settings
*.log
/logs/
.trae/
.vscode/
.claude/
...
...
custom-server-app/src/main/java/com/jomalls/custom/app/dto/CustomProductInfoSaveSnakeDTO.java
View file @
4d862d6f
package
com
.
jomalls
.
custom
.
app
.
dto
;
import
com.fasterxml.jackson.annotation.JsonSetter
;
import
com.jomalls.custom.page.PageRequest
;
import
io.swagger.v3.oas.annotations.media.Schema
;
import
jakarta.validation.constraints.Digits
;
import
jakarta.validation.constraints.Max
;
import
jakarta.validation.constraints.Min
;
import
jakarta.validation.constraints.NotBlank
;
import
jakarta.validation.constraints.NotEmpty
;
import
jakarta.validation.constraints.NotNull
;
import
jakarta.validation.constraints.Size
;
import
jakarta.validation.constraints.*
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
java.io.Serial
;
import
java.io.Serializable
;
...
...
@@ -56,7 +47,6 @@ public class CustomProductInfoSaveSnakeDTO implements Serializable {
* 材质
*/
@Schema
(
description
=
"材质"
,
example
=
""
)
@NotNull
(
message
=
"材质不能为空"
)
private
String
material
;
/**
...
...
@@ -78,35 +68,30 @@ public class CustomProductInfoSaveSnakeDTO implements Serializable {
* 产地编码
*/
@Schema
(
description
=
"产地编码"
)
@NotBlank
(
message
=
"产地不能为空"
)
private
String
origin_code
;
/**
* 产地中文名
*/
@Schema
(
description
=
"产地中文名"
)
@NotBlank
(
message
=
"产地不能为空"
)
private
String
origin_name_cn
;
/**
* 产地英文名
*/
@Schema
(
description
=
"产地英文名"
)
@NotBlank
(
message
=
"产地不能为空"
)
private
String
origin_name_en
;
/**
* 币种编码
*/
@Schema
(
description
=
"币种编码"
)
@NotBlank
(
message
=
"币种不能为空"
)
private
String
currency_code
;
/**
* 币种名称
*/
@Schema
(
description
=
"币种名称"
)
@NotBlank
(
message
=
"币种不能为空"
)
private
String
currency_name
;
/**
...
...
@@ -165,7 +150,6 @@ public class CustomProductInfoSaveSnakeDTO implements Serializable {
* 图片列表
*/
@Schema
(
description
=
"普通图片列表"
)
@NotEmpty
(
message
=
"商品图片不能为空"
)
private
List
<
CustomProductImageSnakeDTO
>
imageList
;
/**
...
...
@@ -203,7 +187,6 @@ public class CustomProductInfoSaveSnakeDTO implements Serializable {
* 商品明细
*/
@Schema
(
description
=
"商品明细"
,
implementation
=
CustomProductItemSnakeDTO
.
class
)
@NotEmpty
(
message
=
"商品明细不能为空"
)
private
List
<
CustomProductItemSnakeDTO
>
productList
;
/**
...
...
@@ -237,14 +220,12 @@ public class CustomProductInfoSaveSnakeDTO implements Serializable {
*
*/
@Schema
(
description
=
"商品属性ID 1"
,
example
=
""
)
@NotNull
(
message
=
"商品属性不能为空"
)
private
Integer
property1_cate_id
;
/**
*
*/
@Schema
(
description
=
"商品属性英文名称 1"
,
example
=
""
)
@NotNull
(
message
=
"商品属性名不能为空"
)
private
String
property1_enname
;
/**
...
...
custom-server-app/src/main/java/com/jomalls/custom/app/dto/FactoryPriceRelSnakeDTO.java
View file @
4d862d6f
...
...
@@ -28,6 +28,9 @@ public class FactoryPriceRelSnakeDTO implements Serializable {
@Serial
private
static
final
long
serialVersionUID
=
1L
;
@Schema
(
description
=
"主键ID)"
)
private
Integer
id
;
@Schema
(
description
=
"子项 ID(custom_product_item 的 id)"
)
private
Integer
item_id
;
...
...
custom-server-app/src/main/java/com/jomalls/custom/app/exception/InvalidTokenException.java
View file @
4d862d6f
package
com
.
jomalls
.
custom
.
app
.
exception
;
import
com.jomalls.custom.enums.CodeEnum
;
import
lombok.Getter
;
import
java.io.Serial
;
...
...
@@ -16,19 +17,53 @@ public class InvalidTokenException extends RuntimeException {
@Serial
private
static
final
long
serialVersionUID
=
1L
;
private
Integer
code
=
401
;
/**
* 错误码
*/
@Getter
private
Integer
code
;
/**
* 错误提示
*/
private
String
message
;
/**
* 错误明细,内部调试错误
* <p>
* 和 {@link CommonResult#getDetailMessage()} 一致的设计
*/
@Getter
private
String
detailMessage
;
/**
* 空构造方法,避免反序列化问题
*/
public
InvalidTokenException
()
{
super
(
"未登录或登录已过期"
);
}
public
InvalidTokenException
(
String
message
)
{
super
(
message
);
this
(
message
,
CodeEnum
.
UNAUTHORIZED
.
getCode
()
);
}
public
InvalidTokenException
(
Integer
code
,
String
messag
e
)
{
super
(
message
)
;
public
InvalidTokenException
(
String
message
,
Integer
cod
e
)
{
this
.
message
=
message
;
this
.
code
=
code
;
}
@Override
public
String
getMessage
()
{
return
message
;
}
public
InvalidTokenException
setMessage
(
String
message
)
{
this
.
message
=
message
;
return
this
;
}
public
InvalidTokenException
setDetailMessage
(
String
detailMessage
)
{
this
.
detailMessage
=
detailMessage
;
return
this
;
}
}
\ No newline at end of file
custom-server-app/src/main/java/com/jomalls/custom/app/service/impl/CustomProductInfoServiceImpl.java
View file @
4d862d6f
...
...
@@ -269,10 +269,26 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
}
}
}
/**
* 因为CustomProductInfoSaveSnakeDTO对象中一些字段是新增与修改通用的
* 但修改时不需要验证,新增时需要验证,所以无法使用类似@NotBlank这种方式校验
*/
private
void
validRequiredField
(
CustomProductInfoSaveSnakeDTO
dto
)
{
if
(
CollectionUtils
.
isEmpty
(
dto
.
getImageList
()))
{
throw
new
ServiceException
(
"商品图片不能为空"
);
}
if
(
CollectionUtils
.
isEmpty
(
dto
.
getProductList
()))
{
throw
new
ServiceException
(
"商品明细不能为空"
);
}
if
(
dto
.
getProperty1_cate_id
()
==
null
||
StringUtils
.
isBlank
(
dto
.
getProperty1_enname
()))
{
throw
new
ServiceException
(
"商品主图不能为空"
);
}
}
@Override
public
void
saveFull
(
CustomProductInfoSaveSnakeDTO
dto
)
{
CustomAsserts
.
nonNull
(
dto
,
"创建参数不能为空"
);
// 必填字段校验
validRequiredField
(
dto
);
// 1. 生成 SKU 调用SysBillRuleService
String
sku
=
sysBillRuleService
.
getProductSku
(
SkuGenerateEnums
.
TEMPLATE_PRODUCT
.
getCode
());
...
...
@@ -1631,6 +1647,9 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
private
void
rebuildProperties
(
List
<
CustomProductInfoPropertyDTO
>
skuProperties
,
List
<
CustomProductInfoPropertyDTO
>
normalProperties
,
Integer
productId
)
{
if
(
CollectionUtils
.
isEmpty
(
skuProperties
)
&&
CollectionUtils
.
isEmpty
(
normalProperties
))
{
return
;
}
// 删除旧属性
customProductInfoPropertyDomainService
.
remove
(
new
LambdaQueryWrapper
<
CustomProductInfoPropertyEntity
>()
...
...
@@ -1719,34 +1738,46 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
List
<
Integer
>
craftIds
,
List
<
Integer
>
diyUserIds
,
List
<
FactoryPriceIntervalRelSnakeDTO
>
intervals
,
Integer
productId
)
{
// 产品-工厂关联
productFactoryRelDomainService
.
remove
(
new
LambdaQueryWrapper
<
ProductFactoryRelEntity
>()
.
eq
(
ProductFactoryRelEntity:
:
getProductId
,
productId
));
saveFactoryRels
(
factoryIds
,
productId
);
if
(
CollectionUtils
.
isNotEmpty
(
factoryIds
))
{
productFactoryRelDomainService
.
remove
(
new
LambdaQueryWrapper
<
ProductFactoryRelEntity
>()
.
eq
(
ProductFactoryRelEntity:
:
getProductId
,
productId
));
saveFactoryRels
(
factoryIds
,
productId
);
}
// 仓库关联
customProductWarehouseRelDomainService
.
remove
(
new
LambdaQueryWrapper
<
CustomProductWarehouseRelEntity
>()
.
eq
(
CustomProductWarehouseRelEntity:
:
getProductId
,
productId
));
saveWarehouseRels
(
warehouseIds
,
productId
);
if
(
CollectionUtils
.
isNotEmpty
(
warehouseIds
))
{
customProductWarehouseRelDomainService
.
remove
(
new
LambdaQueryWrapper
<
CustomProductWarehouseRelEntity
>()
.
eq
(
CustomProductWarehouseRelEntity:
:
getProductId
,
productId
));
saveWarehouseRels
(
warehouseIds
,
productId
);
}
// 工艺关联
customProductCraftRelDomainService
.
remove
(
new
LambdaQueryWrapper
<
CustomProductCraftRelEntity
>()
.
eq
(
CustomProductCraftRelEntity:
:
getProductId
,
productId
));
saveCraftRels
(
craftIds
,
productId
);
if
(
CollectionUtils
.
isNotEmpty
(
craftIds
))
{
customProductCraftRelDomainService
.
remove
(
new
LambdaQueryWrapper
<
CustomProductCraftRelEntity
>()
.
eq
(
CustomProductCraftRelEntity:
:
getProductId
,
productId
));
saveCraftRels
(
craftIds
,
productId
);
}
// DIY 用户关联
customProductDiyUserRelDomainService
.
remove
(
new
LambdaQueryWrapper
<
CustomProductDiyUserRelEntity
>()
.
eq
(
CustomProductDiyUserRelEntity:
:
getProductId
,
productId
));
saveDiyUserRels
(
diyUserIds
,
productId
);
if
(
CollectionUtils
.
isNotEmpty
(
diyUserIds
))
{
customProductDiyUserRelDomainService
.
remove
(
new
LambdaQueryWrapper
<
CustomProductDiyUserRelEntity
>()
.
eq
(
CustomProductDiyUserRelEntity:
:
getProductId
,
productId
));
saveDiyUserRels
(
diyUserIds
,
productId
);
}
// 价格区间关联
customProductFactoryPriceIntervalRelDomainService
.
remove
(
new
LambdaQueryWrapper
<
CustomProductFactoryPriceIntervalRelEntity
>()
.
eq
(
CustomProductFactoryPriceIntervalRelEntity:
:
getProductId
,
productId
));
saveFactoryPriceIntervalRels
(
intervals
,
productId
);
if
(
CollectionUtils
.
isNotEmpty
(
intervals
))
{
customProductFactoryPriceIntervalRelDomainService
.
remove
(
new
LambdaQueryWrapper
<
CustomProductFactoryPriceIntervalRelEntity
>()
.
eq
(
CustomProductFactoryPriceIntervalRelEntity:
:
getProductId
,
productId
));
saveFactoryPriceIntervalRels
(
intervals
,
productId
);
}
}
/**
...
...
custom-server-core/src/main/java/com/jomalls/custom/enums/CodeEnum.java
View file @
4d862d6f
...
...
@@ -19,7 +19,12 @@ public enum CodeEnum {
/**
* 请求成功
*/
SUCCESS
(
200
,
"OK"
,
HttpStatus
.
OK
),
SUCCESS
(
200
,
"Success"
,
HttpStatus
.
OK
),
/*
* 适配原来的请求成功代码,custom前端判断成功是10000
*/
OK
(
10000
,
"OK"
,
HttpStatus
.
OK
),
/**
* 服务器内部错误
...
...
custom-server-core/src/main/java/com/jomalls/custom/page/PageRequest.java
View file @
4d862d6f
...
...
@@ -23,24 +23,24 @@ public class PageRequest implements Pageable, Serializable {
/**
* 默认当前页码
*/
private
static
final
long
DEFAULT_
CURRENT
=
1L
;
private
static
final
long
DEFAULT_
PAGE
=
1L
;
/**
* 默认分页大小
*/
private
static
final
long
DEFAULT_
SIZE
=
10L
;
private
static
final
long
DEFAULT_
ROWS
=
10L
;
/**
* 当前页码
* 当前页码
(前端字段名: page)
*/
@Schema
(
description
=
"当前页码"
,
example
=
"1"
,
defaultValue
=
"1"
)
private
long
current
;
private
long
page
;
/**
* 分页大小
* 分页大小
(前端字段名: rows)
*/
@Schema
(
description
=
"分页大小"
,
example
=
"10"
,
defaultValue
=
"10"
)
private
long
size
;
private
long
rows
;
/**
* 是否需要统计总条数
...
...
@@ -48,18 +48,23 @@ public class PageRequest implements Pageable, Serializable {
private
boolean
searchCount
;
public
PageRequest
()
{
this
(
DEFAULT_
CURRENT
,
DEFAULT_SIZE
);
this
(
DEFAULT_
PAGE
,
DEFAULT_ROWS
);
}
public
PageRequest
(
long
current
,
long
size
)
{
this
.
current
=
current
;
this
.
size
=
size
;
searchCount
=
true
;
this
.
page
=
current
>
0
?
current
:
DEFAULT_PAGE
;
this
.
rows
=
size
>
0
?
size
:
DEFAULT_ROWS
;
this
.
searchCount
=
true
;
}
@Override
public
long
getCurrent
()
{
return
current
;
return
page
>
0
?
page
:
DEFAULT_PAGE
;
}
@Override
public
long
getSize
()
{
return
rows
>
0
?
rows
:
DEFAULT_ROWS
;
}
@Override
...
...
custom-server-core/src/main/java/com/jomalls/custom/utils/R.java
View file @
4d862d6f
...
...
@@ -33,15 +33,15 @@ public class R<T> implements Serializable {
}
public
static
<
T
>
R
<
T
>
ok
()
{
return
new
R
<>(
CodeEnum
.
SUCCESS
.
getCode
(),
CodeEnum
.
SUCCESS
.
getMsg
(),
null
);
return
new
R
<>(
CodeEnum
.
OK
.
getCode
(),
CodeEnum
.
OK
.
getMsg
(),
null
);
}
public
static
<
T
>
R
<
T
>
ok
(
T
data
)
{
return
new
R
<>(
CodeEnum
.
SUCCESS
.
getCode
(),
CodeEnum
.
SUCCESS
.
getMsg
(),
data
);
return
new
R
<>(
CodeEnum
.
OK
.
getCode
(),
CodeEnum
.
OK
.
getMsg
(),
data
);
}
public
static
<
T
>
R
<
T
>
ok
(
String
msg
,
T
data
)
{
return
new
R
<>(
CodeEnum
.
SUCCESS
.
getCode
(),
msg
,
data
);
return
new
R
<>(
CodeEnum
.
OK
.
getCode
(),
msg
,
data
);
}
public
static
<
T
>
R
<
T
>
fail
(
int
code
,
String
msg
)
{
...
...
custom-server-starter/src/main/bin/startup.sh
0 → 100644
View file @
4d862d6f
#!/bin/bash
APP_NAME
=
"custom-server"
APP_JAR
=
"custom-server-starter-1.0-SNAPSHOT.jar"
APP_DIR
=
"/opt/custom-server"
LOG_FILE
=
"
$APP_DIR
/logs/custom-server.log"
# 检查进程是否存在
PID
=
$(
ps
-ef
|
grep
$APP_JAR
|
grep
-v
grep
| awk
'{print $2}'
)
if
[
-n
"
$PID
"
]
;
then
echo
"Stopping
$APP_NAME
(PID:
$PID
)..."
kill
-TERM
$PID
sleep 5
fi
echo
"Starting
$APP_NAME
..."
nohup java
-Xms2g
-Xmx4g
-XX
:+UseG1GC
\
-jar
$APP_DIR
/
$APP_JAR
\
--spring
.profiles.active
=
prod
\
--spring
.config.location
=
file:
$APP_DIR
/config/
\
>
$LOG_FILE
2>&1 &
echo
"
$APP_NAME
started successfully."
\ No newline at end of file
custom-server-starter/src/main/bin/stop.sh
0 → 100644
View file @
4d862d6f
#!/bin/bash
APP_NAME
=
"custom-server"
APP_JAR
=
"custom-server-starter-1.0-SNAPSHOT.jar"
# 检查进程是否存在
PID
=
$(
ps
-ef
|
grep
$APP_JAR
|
grep
-v
grep
| awk
'{print $2}'
)
if
[
-z
"
$PID
"
]
;
then
echo
"
$APP_NAME
is not running."
exit
0
fi
echo
"Stopping
$APP_NAME
(PID:
$PID
)..."
# 优雅停止(发送 SIGTERM 信号)
kill
-TERM
$PID
# 等待进程退出(最多等待 30 秒)
MAX_WAIT
=
30
WAIT_COUNT
=
0
while
kill
-0
$PID
2>/dev/null
;
do
if
[
$WAIT_COUNT
-ge
$MAX_WAIT
]
;
then
echo
"Force killing
$APP_NAME
(PID:
$PID
)..."
kill
-KILL
$PID
break
fi
sleep 1
WAIT_COUNT
=
$((
WAIT_COUNT
+
1
))
done
echo
"
$APP_NAME
stopped successfully."
\ No newline at end of file
custom-server-starter/src/main/java/com/jomalls/custom/config/CommonExceptionHandlerAdvice.java
View file @
4d862d6f
...
...
@@ -31,7 +31,7 @@ public class CommonExceptionHandlerAdvice {
@ExceptionHandler
(
HttpMessageNotReadableException
.
class
)
public
ResponseEntity
<
R
<
Object
>>
handleHttpMessageNotReadableException
(
HttpMessageNotReadableException
e
)
{
log
.
debug
(
"[ JSON反序列化失败 ] {}"
,
e
.
getMessage
(),
e
);
return
ResponseEntity
.
status
(
HttpStatus
.
BAD_REQUEST
)
return
ResponseEntity
.
status
(
HttpStatus
.
OK
)
.
body
(
R
.
fail
(
CodeEnum
.
FAIL
.
getCode
(),
"请求参数格式错误: "
+
e
.
getLocalizedMessage
()));
}
...
...
@@ -44,7 +44,7 @@ public class CommonExceptionHandlerAdvice {
.
map
(
fe
->
fe
.
getField
()
+
": "
+
fe
.
getDefaultMessage
())
.
collect
(
Collectors
.
joining
(
"; "
));
log
.
debug
(
"[ 参数校验失败 ] {}"
,
detail
);
return
ResponseEntity
.
status
(
HttpStatus
.
BAD_REQUEST
)
return
ResponseEntity
.
status
(
HttpStatus
.
OK
)
.
body
(
R
.
fail
(
CodeEnum
.
BAD_REQUEST
.
getCode
(),
"参数校验失败: "
+
detail
));
}
...
...
@@ -57,7 +57,7 @@ public class CommonExceptionHandlerAdvice {
.
map
(
ConstraintViolation:
:
getMessage
)
.
collect
(
Collectors
.
joining
(
"; "
));
log
.
debug
(
"[ 约束校验失败 ] {}"
,
detail
);
return
ResponseEntity
.
status
(
HttpStatus
.
BAD_REQUEST
)
return
ResponseEntity
.
status
(
HttpStatus
.
OK
)
.
body
(
R
.
fail
(
CodeEnum
.
BAD_REQUEST
.
getCode
(),
"参数校验失败: "
+
detail
));
}
...
...
@@ -67,7 +67,7 @@ public class CommonExceptionHandlerAdvice {
@ExceptionHandler
(
InvalidTokenException
.
class
)
public
ResponseEntity
<
R
<
Object
>>
handleInvalidTokenException
(
InvalidTokenException
e
)
{
log
.
debug
(
"[ Token验证失败 ] {}"
,
e
.
getMessage
());
return
ResponseEntity
.
status
(
HttpStatus
.
UNAUTHORIZED
)
return
ResponseEntity
.
status
(
HttpStatus
.
OK
)
.
body
(
R
.
fail
(
CodeEnum
.
UNAUTHORIZED
.
getCode
(),
e
.
getMessage
()));
}
...
...
@@ -77,7 +77,7 @@ public class CommonExceptionHandlerAdvice {
@ExceptionHandler
(
PermissionDeniedException
.
class
)
public
ResponseEntity
<
R
<
Object
>>
handlePermissionDeniedException
(
PermissionDeniedException
e
)
{
log
.
debug
(
"[ 权限拒绝 ] {}"
,
e
.
getMessage
());
return
ResponseEntity
.
status
(
HttpStatus
.
FORBIDDEN
)
return
ResponseEntity
.
status
(
HttpStatus
.
OK
)
.
body
(
R
.
fail
(
CodeEnum
.
FORBIDDEN
.
getCode
(),
e
.
getMessage
()));
}
...
...
custom-server-starter/src/main/java/com/jomalls/custom/config/SecurityInterceptor.java
View file @
4d862d6f
package
com
.
jomalls
.
custom
.
config
;
import
com.jomalls.custom.enums.CodeEnum
;
import
com.jomalls.custom.app.exception.InvalidTokenException
;
import
com.jomalls.custom.app.utils.RequestHolder
;
import
com.jomalls.custom.security.LoginUser
;
import
com.jomalls.custom.security.SecurityUtils
;
import
com.jomalls.custom.security.TokenHandle
;
import
jakarta.servlet.http.HttpServletRequest
;
import
jakarta.servlet.http.HttpServletResponse
;
import
lombok.extern.slf4j.Slf4j
;
import
org.jspecify.annotations.NonNull
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.stereotype.Component
;
import
org.springframework.web.servlet.HandlerInterceptor
;
import
jakarta.servlet.http.HttpServletRequest
;
import
jakarta.servlet.http.HttpServletResponse
;
/**
* 安全拦截器
* 负责用户登录权限检查,保存登录信息到线程本地变量
...
...
@@ -48,7 +43,7 @@ public class SecurityInterceptor implements HandlerInterceptor {
// 如果用户没有登录,返回提示
if
(
loginUser
==
null
)
{
throw
new
InvalidTokenException
(
CodeEnum
.
UNAUTHORIZED
.
getCode
(),
"未登录或登录已过期"
);
throw
new
InvalidTokenException
(
"未登录或登录已过期"
);
}
// 验证token是否有效
...
...
@@ -58,7 +53,7 @@ public class SecurityInterceptor implements HandlerInterceptor {
return
true
;
}
else
{
log
.
warn
(
"用户[{}]token已过期,请求URI: {}"
,
loginUser
.
getUsername
(),
request
.
getRequestURI
());
throw
new
InvalidTokenException
(
CodeEnum
.
UNAUTHORIZED
.
getCode
(),
"未登录或登录已过期"
);
throw
new
InvalidTokenException
(
"未登录或登录已过期"
);
}
}
...
...
custom-server-starter/src/main/java/com/jomalls/custom/config/WebMvcConfiguration.java
View file @
4d862d6f
...
...
@@ -2,10 +2,14 @@ package com.jomalls.custom.config;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.web.servlet.config.annotation.CorsRegistry
;
import
org.springframework.web.cors.CorsConfiguration
;
import
org.springframework.web.cors.UrlBasedCorsConfigurationSource
;
import
org.springframework.web.filter.CorsFilter
;
import
org.springframework.web.servlet.config.annotation.InterceptorRegistry
;
import
org.springframework.web.servlet.config.annotation.WebMvcConfigurer
;
import
java.util.List
;
@Configuration
public
class
WebMvcConfiguration
implements
WebMvcConfigurer
{
...
...
@@ -29,8 +33,7 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
"/webjars/**"
,
"/sys/Serf/Health/*"
,
"/error"
,
"/actuator/health"
,
"/health/check"
,
"/api/v3/actuator/*"
,
"/.well-known/**"
,
"/favicon.ico"
,
"/static/**"
);
...
...
@@ -38,16 +41,23 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
/**
* 配置服务器跨域请求被允许
*
* @param registry
* <p>
* 使用 {@link CorsFilter}(Servlet Filter 层)而非 {@code addCorsMappings}(MVC 层),
* 因为 MVC 层 CORS 依赖 Controller 匹配,无法处理浏览器 OPTIONS 预检请求。
* CorsFilter 在 DispatcherServlet 之前运行,覆盖所有请求。
*/
@Override
public
void
addCorsMappings
(
CorsRegistry
registry
)
{
registry
.
addMapping
(
"/**"
)
.
allowedOriginPatterns
(
"*"
)
.
allowedMethods
(
"GET"
,
"POST"
,
"PUT"
,
"OPTIONS"
,
"DELETE"
,
"PATCH"
)
.
allowCredentials
(
true
)
.
maxAge
(
3600
)
.
allowedHeaders
(
"*"
);
@Bean
public
CorsFilter
corsFilter
()
{
CorsConfiguration
config
=
new
CorsConfiguration
();
config
.
setAllowedOriginPatterns
(
List
.
of
(
"*"
));
config
.
setAllowedMethods
(
List
.
of
(
"GET"
,
"POST"
,
"PUT"
,
"OPTIONS"
,
"DELETE"
,
"PATCH"
));
config
.
setAllowedHeaders
(
List
.
of
(
"*"
));
config
.
setAllowCredentials
(
true
);
config
.
setMaxAge
(
3600L
);
config
.
addExposedHeader
(
"Content-Disposition"
);
UrlBasedCorsConfigurationSource
source
=
new
UrlBasedCorsConfigurationSource
();
source
.
registerCorsConfiguration
(
"/**"
,
config
);
return
new
CorsFilter
(
source
);
}
}
\ No newline at end of file
custom-server-starter/src/main/resources/application-datasource.properties
deleted
100644 → 0
View file @
c701c5ab
## 数据库连接配置
spring.datasource.url
=
jdbc:mysql://172.16.19.99:3306/foxpsd-lizh?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username
=
root
spring.datasource.password
=
joshine
spring.datasource.driver-class-name
=
com.mysql.cj.jdbc.Driver
## Hikari连接池配置
#最小空闲连接数
spring.datasource.hikari.minimum-idle
=
5
#最大连接池大小
spring.datasource.hikari.maximum-pool-size
=
50
#连接超时时间(60秒)
spring.datasource.hikari.connection-timeout
=
60000
#空闲连接超时时间(600秒)
spring.datasource.hikari.idle-timeout
=
600000
#最大连接生命周期(900秒)
spring.datasource.hikari.max-lifetime
=
900000
#验证超时时间(3000秒)
spring.datasource.hikari.validation-timeout
=
3000
## MyBatis-Plus SQL日志(可选开启)
#
mybatis-plus.configuration.log-impl
=
org.apache.ibatis.logging.stdout.StdOutImpl
\ No newline at end of file
custom-server-starter/src/main/resources/application-datasource.yml
0 → 100644
View file @
4d862d6f
# ============================================================
# Custom Server — 数据源配置(datasource profile)
# ============================================================
spring
:
datasource
:
url
:
jdbc:mysql://172.16.19.99:3306/foxpsd-lizh?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
username
:
root
password
:
joshine
driver-class-name
:
com.mysql.cj.jdbc.Driver
hikari
:
# 最小空闲连接数
minimum-idle
:
5
# 最大连接池大小
maximum-pool-size
:
50
# 连接超时时间(60 秒)
connection-timeout
:
60000
# 空闲连接超时时间(600 秒)
idle-timeout
:
600000
# 最大连接生命周期(900 秒)
max-lifetime
:
900000
# 验证超时时间(3 秒)
validation-timeout
:
3000
custom-server-starter/src/main/resources/application-redis.properties
deleted
100644 → 0
View file @
c701c5ab
## Redis连接配置
spring.data.redis.host
=
172.16.19.100
spring.data.redis.port
=
6379
spring.data.redis.password
=
joshine.dev
spring.data.redis.database
=
7
## 超时配置(毫秒)
# 读取/写入超时
spring.data.redis.timeout
=
5000
# 连接超时(缩短,避免长时间等待)
spring.data.redis.connect-timeout
=
3000
## 连接池配置(适配RedissonClient)
spring.data.redis.pool.max-active
=
8
spring.data.redis.pool.max-idle
=
4
spring.data.redis.pool.min-idle
=
2
## Lettuce连接池配置(Spring Data Redis使用)
spring.data.redis.lettuce.pool.enabled
=
true
spring.data.redis.lettuce.pool.max-active
=
8
spring.data.redis.lettuce.pool.max-idle
=
4
spring.data.redis.lettuce.pool.min-idle
=
2
spring.data.redis.lettuce.pool.max-wait
=
5000
## Redisson特有配置
# 重试次数
spring.data.redis.redisson.retry-attempts
=
3
# 重试间隔(毫秒)
spring.data.redis.redisson.retry-interval
=
1000
# 心跳间隔(毫秒)
spring.data.redis.redisson.ping-interval
=
60000
# 空闲连接超时(毫秒)
spring.data.redis.redisson.idle-timeout
=
10000
\ No newline at end of file
custom-server-starter/src/main/resources/application-redis.yml
0 → 100644
View file @
4d862d6f
# ============================================================
# Custom Server — Redis 配置(redis profile)
# ============================================================
spring
:
data
:
redis
:
host
:
172.16.19.100
port
:
6379
password
:
joshine.dev
database
:
7
# 读取/写入超时(毫秒)
timeout
:
5000
# 连接超时(毫秒)
connect-timeout
:
3000
# Jedis 连接池配置
pool
:
max-active
:
8
max-idle
:
4
min-idle
:
2
# Lettuce 连接池配置(Spring Data Redis 使用)
lettuce
:
pool
:
enabled
:
true
max-active
:
8
max-idle
:
4
min-idle
:
2
max-wait
:
5000
# Redisson 特有配置
redisson
:
# 重试次数
retry-attempts
:
3
# 重试间隔(毫秒)
retry-interval
:
1000
# 心跳间隔(毫秒)
ping-interval
:
60000
# 空闲连接超时(毫秒)
idle-timeout
:
10000
custom-server-starter/src/main/resources/application.properties
deleted
100644 → 0
View file @
c701c5ab
## 服务器配置
server.port
=
40071
server.servlet.context-path
=
/
## Tomcat配置
server.tomcat.uri-encoding
=
UTF-8
server.tomcat.accept-count
=
1000
server.tomcat.threads.max
=
200
server.tomcat.threads.min-spare
=
100
## Spring配置
spring.application.name
=
custom-server
spring.profiles.active
=
datasource,redis
spring.main.allow-circular-references
=
true
## Jackson配置
spring.jackson.date-format
=
yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone
=
GMT+8
## SpringDoc OpenAPI配置
springdoc.api-docs.path
=
/api-docs
springdoc.swagger-ui.path
=
/swagger-ui.html
## MyBatis-Plus配置
mybatis-plus.mapper-locations
=
classpath*:mapper/**/*.xml
mybatis-plus.type-aliases-package
=
com.jomalls.custom.domain.entity
mybatis-plus.configuration.call-setters-on-nulls
=
true
## 时区配置
TZ
=
Asia/Shanghai
# 令牌配置,是否需要认证(测试时可以不认证)
server.needAuthentication
=
true
# 令牌自定义标识
token.header
=
Authorization
# 令牌密钥(兼容旧版本)
token.secret
=
custom
# 令牌有效期(单位:分钟)
token.expireTime
=
720
# HTTP Client Configuration
# 连接超时(5000毫秒 = 5秒)
http.client.connect-timeout
=
5000
# 读取超时(30000毫秒 = 30秒)
http.client.read-timeout
=
30000
# 写入超时(30000毫秒 = 30秒)
http.client.write-timeout
=
30000
# 连接池最大连接数
http.client.pool-max-connections
=
50
# 连接池获取连接超时(毫秒)
http.client.pool-acquire-timeout
=
2000
# Resilience4j Retry Configuration
# 最大重试次数:最多重试3次(首次 + 2次重试)
resilience4j.retry.configs.default.max-attempts
=
3
# 初始等待时间:第一次重试前等待500毫秒
resilience4j.retry.configs.default.wait-duration
=
500ms
# 启用指数退避:每次重试间隔翻倍(500ms → 1000ms → 2000ms)
resilience4j.retry.configs.default.enable-exponential-backoff
=
true
# 指数退避倍数:每次等待时间乘以2
resilience4j.retry.configs.default.exponential-backoff-multiplier
=
2
# 以下异常触发重试(网络异常 + 服务端5xx错误)
resilience4j.retry.configs.default.retry-exceptions
=
java.util.concurrent.TimeoutException,java.io.IOException,java.net.ConnectException,org.springframework.web.reactive.function.client.WebClientResponseException
# 以下异常不触发重试(客户端4xx错误已包装为RemoteServiceException,重试无意义)
resilience4j.retry.configs.default.ignore-exceptions
=
com.jomalls.custom.integrate.exception.RemoteServiceException
# 远程 API 重试实例使用默认配置
resilience4j.retry.instances.remoteApi.base-config
=
default
server.admin.base-url
=
https://admin.jomalls.com
\ No newline at end of file
custom-server-starter/src/main/resources/application.yml
View file @
4d862d6f
## Spring MVC配置
# ============================================================
# Custom Server — 主配置文件
# ============================================================
# 服务器配置
server
:
port
:
40071
servlet
:
context-path
:
/
tomcat
:
uri-encoding
:
UTF-8
accept-count
:
1000
threads
:
max
:
200
min-spare
:
100
# 自定义:是否需要认证(测试时可以不认证)
needAuthentication
:
true
# 自定义:saasAdmin管理后台地址,获取商品类别信息
admin
:
base-url
:
https://admin.jomalls.com
# Spring 配置
spring
:
application
:
name
:
custom-server
profiles
:
active
:
datasource
mvc
:
pathmatch
:
matching-strategy
:
ant_path_matcher
active
:
datasource,redis
main
:
allow-circular-references
:
true
jackson
:
date-format
:
yyyy-MM-dd HH:mm:ss
time-zone
:
GMT+8
## MyBatis-Plus配置
# SpringDoc OpenAPI 配置
springdoc
:
api-docs
:
path
:
/api-docs
swagger-ui
:
path
:
/swagger-ui.html
# MyBatis-Plus 配置
mybatis-plus
:
mapper-locations
:
classpath*:mapper/**/*.xml
type-aliases-package
:
com.jomalls.custom.domain.dal.entity
configuration
:
call-setters-on-nulls
:
true
log-impl
:
org.apache.ibatis.logging.stdout.StdOutImpl
global-config
:
db-config
:
id-type
:
auto
mapper-locations
:
classpath*:mapper/**/*.xml
\ No newline at end of file
# ============================================================
# 令牌配置
# ============================================================
token
:
header
:
Authorization
# 令牌密钥(兼容旧版本)
secret
:
custom
# 令牌有效期(单位:分钟)
expireTime
:
720
# ============================================================
# HTTP Client 配置
# ============================================================
http
:
client
:
# 连接超时(毫秒)
connect-timeout
:
5000
# 读取超时(毫秒)
read-timeout
:
30000
# 连接池最大连接数
pool-max-connections
:
50
# 连接池获取连接超时(毫秒)
pool-acquire-timeout
:
2000
# ============================================================
# Resilience4j 重试配置
# ============================================================
resilience4j
:
retry
:
configs
:
default
:
# 最大重试次数
max-attempts
:
3
# 初始等待时间
wait-duration
:
500ms
# 启用指数退避
enable-exponential-backoff
:
true
# 指数退避倍数
exponential-backoff-multiplier
:
2
# 触发重试的异常
retry-exceptions
:
-
java.util.concurrent.TimeoutException
-
java.io.IOException
-
java.net.ConnectException
-
org.springframework.web.reactive.function.client.WebClientResponseException
# 不触发重试的异常
ignore-exceptions
:
-
com.jomalls.custom.integrate.exception.RemoteServiceException
instances
:
remoteApi
:
base-config
:
default
# ============================================================
# Actuator 管理端点
# ============================================================
management
:
endpoints
:
web
:
base-path
:
/api/v3/actuator
exposure
:
include
:
metrics
custom-server-starter/src/main/resources/logback-spring.xml
View file @
4d862d6f
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径(开发环境用相对路径,生产环境通过 logging.file.path 覆盖) -->
<!--<property name="log.path" value="${LOG_PATH:-logs}"/>-->
<property
name=
"log.path"
value=
"./custom-v2/logs"
/>
<!-- 日志输出格式 -->
<property
name=
"log.pattern"
value=
"%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [%method,%line] - %msg%n"
/>
<configuration
scan=
"true"
scanPeriod=
"300 seconds"
debug=
"false"
>
<property
name=
"max.history.days"
value=
"30"
/>
<property
name=
"max.file.size"
value=
"50MB"
/>
<property
name=
"max.total.size"
value=
"2GB"
/>
<!-- ==================== 日志路径 ==================== -->
<!--
生效优先级(由 Spring Boot LoggingApplicationListener 处理):
1. application.yml: logging.file.path: /var/log/app
2. JVM 启动参数: -DLOG_PATH=/var/log/app
3. 环境变量: export LOG_PATH=/var/log/app
4. 以上均未设置 → defaultValue: ./custom-v3/logs
LOG_PATH → logging.file.path 的映射由 Spring Boot 内置完成,
springProperty 从 Environment 中读取最终的 logging.file.path 值。
-->
<springProperty
scope=
"context"
name=
"log.path"
source=
"logging.file.path"
defaultValue=
"./custom-v3/logs"
/>
<!-- ==================== 日志格式 ==================== -->
<property
name=
"log.pattern"
value=
"%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{traceId}] - [%method,%line] - %msg%n"
/>
<!-- ==================== 控制台输出 ==================== -->
<appender
name=
"console"
class=
"ch.qos.logback.core.ConsoleAppender"
>
...
...
@@ -18,13 +31,22 @@
</appender>
<!-- ==================== 文件输出 ==================== -->
<!--
滚动策略:SizeAndTimeBasedRollingPolicy
- 按天 + 按大小双重滚动,单文件超过 maxFileSize 自动切分
- totalSizeCap 限制归档总量,超过自动清理最旧文件
- cleanHistoryOnStart 启动时清理超出限制的归档
-->
<!-- 系统日志:
INFO 及以上(含 WARN)
-->
<!-- 系统日志:
仅 INFO
-->
<appender
name=
"file_info"
class=
"ch.qos.logback.core.rolling.RollingFileAppender"
>
<file>
${log.path}/sys-info.log
</file>
<rollingPolicy
class=
"ch.qos.logback.core.rolling.TimeBasedRollingPolicy"
>
<fileNamePattern>
${log.path}/sys-info.%d{yyyy-MM-dd}.log
</fileNamePattern>
<maxHistory>
60
</maxHistory>
<rollingPolicy
class=
"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"
>
<fileNamePattern>
${log.path}/sys-info.%d{yyyy-MM-dd}.%i.log
</fileNamePattern>
<maxFileSize>
${max.file.size}
</maxFileSize>
<maxHistory>
${max.history.days}
</maxHistory>
<totalSizeCap>
${max.total.size}
</totalSizeCap>
<cleanHistoryOnStart>
true
</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<pattern>
${log.pattern}
</pattern>
...
...
@@ -37,11 +59,15 @@
</filter>
</appender>
<!-- 警告日志:仅 WARN -->
<appender
name=
"file_warn"
class=
"ch.qos.logback.core.rolling.RollingFileAppender"
>
<file>
${log.path}/sys-warn.log
</file>
<rollingPolicy
class=
"ch.qos.logback.core.rolling.TimeBasedRollingPolicy"
>
<fileNamePattern>
${log.path}/sys-warn.%d{yyyy-MM-dd}.log
</fileNamePattern>
<maxHistory>
60
</maxHistory>
<rollingPolicy
class=
"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"
>
<fileNamePattern>
${log.path}/sys-warn.%d{yyyy-MM-dd}.%i.log
</fileNamePattern>
<maxFileSize>
${max.file.size}
</maxFileSize>
<maxHistory>
${max.history.days}
</maxHistory>
<totalSizeCap>
${max.total.size}
</totalSizeCap>
<cleanHistoryOnStart>
true
</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<pattern>
${log.pattern}
</pattern>
...
...
@@ -57,9 +83,12 @@
<!-- 错误日志:仅 ERROR -->
<appender
name=
"file_error"
class=
"ch.qos.logback.core.rolling.RollingFileAppender"
>
<file>
${log.path}/sys-error.log
</file>
<rollingPolicy
class=
"ch.qos.logback.core.rolling.TimeBasedRollingPolicy"
>
<fileNamePattern>
${log.path}/sys-error.%d{yyyy-MM-dd}.log
</fileNamePattern>
<maxHistory>
60
</maxHistory>
<rollingPolicy
class=
"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"
>
<fileNamePattern>
${log.path}/sys-error.%d{yyyy-MM-dd}.%i.log
</fileNamePattern>
<maxFileSize>
${max.file.size}
</maxFileSize>
<maxHistory>
${max.history.days}
</maxHistory>
<totalSizeCap>
${max.total.size}
</totalSizeCap>
<cleanHistoryOnStart>
true
</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<pattern>
${log.pattern}
</pattern>
...
...
@@ -72,16 +101,44 @@
</filter>
</appender>
<!-- ==================== 异步包装 ==================== -->
<!-- 文件 I/O 不阻塞业务线程 -->
<appender
name=
"async_file_info"
class=
"ch.qos.logback.classic.AsyncAppender"
>
<neverBlock>
true
</neverBlock>
<queueSize>
512
</queueSize>
<discardingThreshold>
0
</discardingThreshold>
<appender-ref
ref=
"file_info"
/>
</appender>
<appender
name=
"async_file_warn"
class=
"ch.qos.logback.classic.AsyncAppender"
>
<neverBlock>
true
</neverBlock>
<queueSize>
256
</queueSize>
<discardingThreshold>
0
</discardingThreshold>
<appender-ref
ref=
"file_warn"
/>
</appender>
<appender
name=
"async_file_error"
class=
"ch.qos.logback.classic.AsyncAppender"
>
<neverBlock>
true
</neverBlock>
<queueSize>
256
</queueSize>
<discardingThreshold>
0
</discardingThreshold>
<appender-ref
ref=
"file_error"
/>
</appender>
<!-- ==================== 框架日志级别控制 ==================== -->
<!-- MyBatis / MyBatis-Plus -->
<!--<logger name="org.mybatis" level="DEBUG"/>-->
<!--<logger name="com.baomidou.mybatisplus" level="DEBUG"/>-->
<!-- MyBatis / MyBatis-Plus:默认关闭 SQL 日志,排查问题时临时打开
<logger name="org.mybatis" level="DEBUG"/>
<logger name="com.baomidou.mybatisplus" level="DEBUG"/>
-->
<!-- ==================== 环境差异配置 ==================== -->
<!-- 本地开发:项目包 DEBUG 级别,方便调试 -->
<springProfile
name=
"default"
>
<logger
name=
"com.jomalls.custom"
level=
"DEBUG"
/>
</springProfile>
<!-- ==================== 根配置 ==================== -->
<root
level=
"
DEBUG
"
>
<root
level=
"
INFO
"
>
<appender-ref
ref=
"console"
/>
<appender-ref
ref=
"file_info"
/>
<appender-ref
ref=
"file_warn"
/>
<appender-ref
ref=
"file_error"
/>
<appender-ref
ref=
"
async_
file_info"
/>
<appender-ref
ref=
"
async_
file_warn"
/>
<appender-ref
ref=
"
async_
file_error"
/>
</root>
</configuration>
</configuration>
\ No newline at end of file
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