Compare commits

..

92 Commits

Author SHA1 Message Date
4ebb71f7a0 测试:普通参数--新增&修改&删除
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 24s
2026-06-05 17:24:59 +08:00
88aff91e5d 测试:普通参数--删除
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 19s
2026-06-05 17:22:54 +08:00
d0aeefa1e7 测试:普通参数--新增
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 19s
2026-06-05 17:21:19 +08:00
eec5608cce 测试:参数修改
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 21s
2026-06-05 17:18:27 +08:00
380bc41fc4 测试:路径修改
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 21s
2026-06-05 17:16:56 +08:00
eb4856ab56 测试:新增接口
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 21s
2026-06-05 16:57:00 +08:00
326419bc53 测试:删除接口
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 20s
2026-06-05 16:54:04 +08:00
a128241ccc 测试:请求方式变更
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 20s
2026-06-05 16:52:20 +08:00
3e6fb3012b 接口类对象解析
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 20s
2026-06-05 16:44:10 +08:00
2d1292262a 接口类对象解析
Some checks failed
API接口参数变更检测 / api-param-check (push) Has been cancelled
2026-06-05 16:43:57 +08:00
51145e7c78 接口类对象解析
Some checks failed
API接口参数变更检测 / api-param-check (push) Has been cancelled
2026-06-05 16:41:16 +08:00
90b0045659 接口类对象解析
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 20s
2026-06-05 16:39:16 +08:00
a1c28570d4 接口类对象解析 2026-06-05 16:39:02 +08:00
ba3f1c6507 接口类对象解析
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 19s
2026-06-05 16:36:16 +08:00
03fb9766a6 接口类对象解析 2026-06-05 16:35:51 +08:00
3cba3bb74e commit
Some checks failed
API接口参数变更检测 / api-param-check (push) Has been cancelled
2026-06-05 16:18:40 +08:00
1ca34c6bb2 字段说明测试--改动两个
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 16:02:19 +08:00
026a490125 字段说明测试
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 16:00:12 +08:00
c46cb06391 字段说明测试 2026-06-05 16:00:07 +08:00
f9bf0df1e1 字段说明测试
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 19s
2026-06-05 15:44:21 +08:00
e9c2525b18 Merge remote-tracking branch 'origin/main'
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
# Conflicts:
#	.gitea/checker/comparator.py
#	.gitea/checker/controller_ast_parser.py
#	.gitea/checker/notifier.py
#	ftb/src/main/java/ftb/test/controller/CultureClockInController.java
2026-06-05 15:42:54 +08:00
021fc8d5e3 字段说明测试 2026-06-05 15:42:29 +08:00
f62a33b2c0 字段说明测试
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 15:27:22 +08:00
145f114029 字段说明测试
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 15:22:08 +08:00
be32bff0bb 字段说明测试
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-05 15:21:16 +08:00
dbcc1a3490 字段说明测试
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-05 15:20:01 +08:00
1d05aec86c @ApiParam测试
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-05 15:16:17 +08:00
0e118e7bc8 @ApiParam测试
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-05 15:15:09 +08:00
f7fc0dd453 字段说明获取
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-05 15:04:13 +08:00
77479a40a1 请求方式+path变更
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 19s
2026-06-05 14:51:51 +08:00
3102111028 请求方式+path变更
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 14:48:09 +08:00
285b75e2f1 请求方式变更
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 18s
2026-06-05 14:44:27 +08:00
6f33bf412d 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 14:43:56 +08:00
94df469372 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-05 14:24:59 +08:00
f79927192c 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-05 14:24:08 +08:00
35d64a91b3 1 2026-06-05 14:23:46 +08:00
de5fa86e32 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 14:07:02 +08:00
e32808713d 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 14:04:32 +08:00
68d2e5d35d 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 18s
2026-06-05 14:03:28 +08:00
bd106ed454 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-05 13:59:55 +08:00
c21ba30c55 1
Some checks failed
API接口参数变更检测 / api-param-check (push) Has been cancelled
2026-06-05 13:59:39 +08:00
e90b10b2ae 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 13:48:13 +08:00
2a2bee050d 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 13:44:53 +08:00
8a8b03ac03 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 11:37:21 +08:00
b607bce8ff 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 18s
2026-06-05 11:34:54 +08:00
3ccc697af7 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 29s
2026-06-05 11:26:59 +08:00
ccbb84ccb8 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-05 11:26:02 +08:00
f08c34f827 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 11:24:36 +08:00
67e24ceee2 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 11:18:27 +08:00
22e3c272ad 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-05 11:11:05 +08:00
283185a2dc 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-05 11:10:01 +08:00
78761a7799 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 11:04:58 +08:00
f642f9a09d 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-05 11:04:24 +08:00
cb7c06f0e3 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 18s
2026-06-05 10:58:51 +08:00
3ebc6e8025 1
Some checks failed
API接口参数变更检测 / api-param-check (push) Has been cancelled
2026-06-05 10:58:47 +08:00
21f84d0a9c 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 10:56:37 +08:00
9fa95bae16 1
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 18s
2026-06-05 10:45:29 +08:00
26cdde9344 py修改
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 10:44:23 +08:00
ef2a16e7b3 新增API请求方式变更通知
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 10:37:12 +08:00
e4eb41013c 新增API请求方式变更通知
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-05 10:29:21 +08:00
9aaf19a109 get-》post
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 10:22:12 +08:00
01a25b92c0 py修改
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-05 10:21:53 +08:00
4974eda17d py修改
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-05 10:21:34 +08:00
07f070e97d uodate
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 32s
2026-06-04 18:05:06 +08:00
c137461c91 create
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 25s
2026-06-04 18:02:58 +08:00
8bf65f2a56 del
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 26s
2026-06-04 18:00:38 +08:00
352730b991 fix
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-04 17:59:38 +08:00
4c299588f0 create
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 26s
2026-06-04 17:58:06 +08:00
bc1089b865 del
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-04 17:57:35 +08:00
03ee22c86d del
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 25s
2026-06-04 17:45:00 +08:00
1d35a25c60 create
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 23s
2026-06-04 17:34:50 +08:00
64dd7bdf9e del
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 27s
2026-06-04 17:29:47 +08:00
4f28d46729 py
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-04 17:29:13 +08:00
2d7abbb4ff 新增
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 26s
2026-06-04 17:23:42 +08:00
a3bfd26715 update
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 25s
2026-06-04 17:21:01 +08:00
4321d23e64 update
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-04 17:20:17 +08:00
d17210bd4d update
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 31s
2026-06-04 17:14:41 +08:00
2fdd7ec2cc py
Some checks failed
API接口参数变更检测 / api-param-check (push) Has been cancelled
2026-06-04 17:14:24 +08:00
e72c9e81de 接口路径修改
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 37s
2026-06-04 16:57:44 +08:00
48dc5ac8e4 py修改
Some checks failed
API接口参数变更检测 / api-param-check (push) Has been cancelled
2026-06-04 16:57:08 +08:00
2d7bba4b91 接口URI修改
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 27s
2026-06-04 15:59:08 +08:00
9f9883f169 接口修改
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 29s
2026-06-04 15:55:15 +08:00
2d41a7b0be 脚本修改
Some checks failed
API接口参数变更检测 / api-param-check (push) Has been cancelled
2026-06-04 15:54:17 +08:00
a8e2a6de29 模版文本补充
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-04 15:34:49 +08:00
7ff7198674 get-》post
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 23s
2026-06-04 10:01:37 +08:00
f9d2937fa1 修改
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 42s
2026-06-04 09:54:22 +08:00
1b19e8366e py修改
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
2026-06-04 09:53:36 +08:00
a8cde16c17 新增
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 47s
2026-06-03 15:57:40 +08:00
52b4b838a2 update
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 40s
2026-06-03 15:45:11 +08:00
6af9324e4b py修改
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 17s
2026-06-03 15:44:25 +08:00
0fd10f9d46 Controller修改
Some checks failed
API接口参数变更检测 / api-param-check (push) Failing after 17s
2026-06-03 15:41:56 +08:00
0600feed2e py依赖修改
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 33s
2026-06-03 15:40:47 +08:00
4405 changed files with 451221 additions and 395 deletions

View File

@@ -31,26 +31,72 @@ class ParameterChange:
required: Optional[bool] = None
old_required: Optional[bool] = None
detail: Optional[str] = None
description: Optional[str] = None
old_description: Optional[str] = None
source: str = "query"
parent_dto: Optional[str] = None
body_param_name: Optional[str] = None
def to_display_line(self) -> str:
"""
格式化为通知模板中的一行文本。
def _change_tag(self) -> str:
"""变更类型标签(企微颜色)。"""
tags = {
ChangeType.ADDED: '<font color="info">**新增**</font>',
ChangeType.REMOVED: '<font color="warning">**删除**</font>',
ChangeType.RENAMED: '<font color="comment">**重命名**</font>',
ChangeType.MODIFIED: '<font color="warning">**修改**</font>',
}
return tags.get(self.change_type, "")
def _required_tag(self) -> str:
"""必填/可选标签。"""
if self.required is True:
return '<font color="warning">必填</font>'
if self.required is False:
return '<font color="comment">可选</font>'
return ""
def to_markdown_block(self, index: int = 1) -> str:
"""格式化为企微友好的参数变更卡片(列表式,非表格)。"""
lines: List[str] = []
desc = self.description or self.old_description
:return: 如 "删除: Boolean userType""重命名: String userName -> String accountName"
"""
if self.change_type == ChangeType.REMOVED:
return f" - 删除: {self.param_type} {self.param_name}"
if self.change_type == ChangeType.ADDED:
req_text = f" (是否必填:{str(self.required).lower()})" if self.required is not None else ""
return f" - 新增: {self.param_type} {self.param_name}{req_text}"
if self.change_type == ChangeType.RENAMED:
return f" - 重命名: {self.old_type} {self.old_name} -> {self.param_type} {self.param_name}"
if self.change_type == ChangeType.MODIFIED:
parts = [f" - 修改: {self.param_name}"]
if self.detail:
parts.append(f" ({self.detail})")
return "".join(parts)
return f" - {self.change_type.value}: {self.param_name}"
lines.append(f"**{index}. `{self.param_name}`** {self._change_tag()}")
lines.append(f"> `{self.old_name}` → `{self.param_name}`")
if desc:
lines.append(f"> 说明:{desc}")
return "\n".join(lines)
if self.change_type == ChangeType.ADDED:
type_part = f" · `{self.param_type}`" if self.param_type else ""
req_part = f" · {self._required_tag()}" if self._required_tag() else ""
lines.append(
f"**{index}. `{self.param_name}`**{type_part}{req_part} {self._change_tag()}"
)
if desc:
lines.append(f"> 说明:{desc}")
return "\n".join(lines)
if self.change_type == ChangeType.REMOVED:
type_part = f" · `{self.param_type}`" if self.param_type else ""
lines.append(
f"**{index}. `{self.param_name}`**{type_part} {self._change_tag()}"
)
if desc:
lines.append(f"> 说明:{desc}")
return "\n".join(lines)
# MODIFIED
lines.append(f"**{index}. `{self.param_name}`** {self._change_tag()}")
if desc:
lines.append(f"> 说明:{desc}")
if self.detail:
lines.append(f"> 变更:{self.detail}")
return "\n".join(lines)
def to_table_row(self) -> str:
"""兼容旧调用,委托至卡片块。"""
return self.to_markdown_block(1)
@dataclass
@@ -61,9 +107,14 @@ class EndpointChangeReport:
http_method: str
controller_class: str
method_name: str
source_file: str = ""
parameter_changes: List[ParameterChange] = field(default_factory=list)
is_new_endpoint: bool = False
is_removed_endpoint: bool = False
is_renamed_endpoint: bool = False
old_uri: Optional[str] = None
is_method_changed: bool = False
old_http_method: Optional[str] = None
@property
def has_changes(self) -> bool:
@@ -71,6 +122,8 @@ class EndpointChangeReport:
return (
self.is_new_endpoint
or self.is_removed_endpoint
or self.is_renamed_endpoint
or self.is_method_changed
or len(self.parameter_changes) > 0
)
@@ -84,6 +137,11 @@ def _param_key(p: ApiParameter) -> Tuple[str, str]:
return (p.source, p.name)
def _format_type_change(old_type: str, new_type: str) -> str:
"""类型变更文案。"""
return f"类型由{old_type}改为{new_type}"
def compare_parameters(
old_params: List[ApiParameter], new_params: List[ApiParameter]
) -> List[ParameterChange]:
@@ -114,9 +172,16 @@ def compare_parameters(
new_p = new_map[key]
detail_parts = []
if old_p.type != new_p.type:
detail_parts.append(f"类型 {old_p.type} -> {new_p.type}")
detail_parts.append(_format_type_change(old_p.type, new_p.type))
if old_p.required != new_p.required:
detail_parts.append(f"必填 {old_p.required} -> {new_p.required}")
req_label = lambda r: "必填" if r else "可选"
detail_parts.append(
f"必填性由{req_label(old_p.required)}改为{req_label(new_p.required)}"
)
if old_p.description != new_p.description:
detail_parts.append(
f"说明由{old_p.description or '-'}改为{new_p.description or '-'}"
)
if detail_parts:
changes.append(
ParameterChange(
@@ -126,6 +191,11 @@ def compare_parameters(
required=new_p.required,
old_required=old_p.required,
detail=", ".join(detail_parts),
description=new_p.description,
old_description=old_p.description,
source=new_p.source,
parent_dto=new_p.parent_dto,
body_param_name=new_p.body_param_name,
)
)
@@ -154,6 +224,11 @@ def compare_parameters(
old_name=r_param.name,
old_type=r_param.type,
required=a_param.required,
description=a_param.description,
old_description=r_param.description,
source=a_param.source,
parent_dto=a_param.parent_dto,
body_param_name=a_param.body_param_name,
)
)
matched_removed.add(r_key)
@@ -168,6 +243,10 @@ def compare_parameters(
change_type=ChangeType.REMOVED,
param_name=param.name,
param_type=param.type,
description=param.description,
source=param.source,
parent_dto=param.parent_dto,
body_param_name=param.body_param_name,
)
)
@@ -180,6 +259,10 @@ def compare_parameters(
param_name=param.name,
param_type=param.type,
required=param.required,
description=param.description,
source=param.source,
parent_dto=param.parent_dto,
body_param_name=param.body_param_name,
)
)
@@ -193,26 +276,142 @@ def compare_endpoints(
"""
对比新旧两个版本的全部 Controller 端点,生成变更报告列表。
:param old_endpoints: 旧版本 { endpoint_key: ApiEndpoint }
:param new_endpoints: 新版本 { endpoint_key: ApiEndpoint }
:return: 有变更的接口报告列表
支持以下变更类型检测:
- HTTP 方法变更GET → POST 等)
- URI 路径变更(路径重命名)
- 新增 / 删除接口
- 参数变更
"""
reports: List[EndpointChangeReport] = []
all_keys = set(old_endpoints.keys()) | set(new_endpoints.keys())
old_keys = set(old_endpoints.keys())
new_keys = set(new_endpoints.keys())
for key in sorted(all_keys):
old_ep = old_endpoints.get(key)
new_ep = new_endpoints.get(key)
removed_keys = old_keys - new_keys
added_keys = new_keys - old_keys
common_keys = old_keys & new_keys
if old_ep is None and new_ep is not None:
# 全新接口
# 收集未匹配的 removed / added
unmatched_removed: List[Tuple[str, ApiEndpoint]] = []
unmatched_added: List[Tuple[str, ApiEndpoint]] = []
for key in removed_keys:
unmatched_removed.append((key, old_endpoints[key]))
for key in added_keys:
unmatched_added.append((key, new_endpoints[key]))
matched_removed: Set[str] = set()
matched_added: Set[str] = set()
# 1. HTTP 方法变更检测uri + controller 相同,但 method 不同)
# 放宽匹配条件:只要同一个 Controller 的同一个 URI 请求方式改变,就识别为「修改请求方式」
# 不再要求 method_name 相同(允许方法重命名场景)
# 如果同时有参数变更,生成两条独立报告(方法变更 + 参数变更),互不干扰
for r_key, r_ep in unmatched_removed:
for a_key, a_ep in unmatched_added:
if a_key in matched_added:
continue
if (
r_ep.uri == a_ep.uri
and r_ep.controller_class == a_ep.controller_class
and r_ep.http_method != a_ep.http_method
):
# 先生成纯方法变更报告
reports.append(
EndpointChangeReport(
uri=a_ep.uri,
http_method=a_ep.http_method,
controller_class=a_ep.controller_class,
method_name=a_ep.method_name,
source_file=a_ep.source_file,
is_method_changed=True,
old_http_method=r_ep.http_method,
)
)
# 再检测参数变更,如果有则额外生成参数报告
param_changes = compare_parameters(r_ep.parameters, a_ep.parameters)
if param_changes:
reports.append(
EndpointChangeReport(
uri=a_ep.uri,
http_method=a_ep.http_method,
controller_class=a_ep.controller_class,
method_name=a_ep.method_name,
source_file=a_ep.source_file,
parameter_changes=param_changes,
)
)
matched_removed.add(r_key)
matched_added.add(a_key)
break
# 2. URI 路径变更检测method + controller + method_name 相同,但 uri 不同)
# 如果同时有参数变更,生成两条独立报告(路径变更 + 参数变更),互不干扰
for r_key, r_ep in unmatched_removed:
if r_key in matched_removed:
continue
for a_key, a_ep in unmatched_added:
if a_key in matched_added:
continue
if (
r_ep.http_method == a_ep.http_method
and r_ep.controller_class == a_ep.controller_class
and r_ep.method_name == a_ep.method_name
and r_ep.uri != a_ep.uri
):
# 先生成纯路径变更报告
reports.append(
EndpointChangeReport(
uri=a_ep.uri,
http_method=a_ep.http_method,
controller_class=a_ep.controller_class,
method_name=a_ep.method_name,
source_file=a_ep.source_file,
is_renamed_endpoint=True,
old_uri=r_ep.uri,
)
)
# 再检测参数变更,如果有则额外生成参数报告
param_changes = compare_parameters(r_ep.parameters, a_ep.parameters)
if param_changes:
reports.append(
EndpointChangeReport(
uri=a_ep.uri,
http_method=a_ep.http_method,
controller_class=a_ep.controller_class,
method_name=a_ep.method_name,
source_file=a_ep.source_file,
parameter_changes=param_changes,
)
)
matched_removed.add(r_key)
matched_added.add(a_key)
break
# 3. 剩余未匹配的 removed → 删除接口
for key, ep in unmatched_removed:
if key not in matched_removed:
reports.append(
EndpointChangeReport(
uri=new_ep.uri,
http_method=new_ep.http_method,
controller_class=new_ep.controller_class,
method_name=new_ep.method_name,
uri=ep.uri,
http_method=ep.http_method,
controller_class=ep.controller_class,
method_name=ep.method_name,
source_file=ep.source_file,
is_removed_endpoint=True,
)
)
# 4. 剩余未匹配的 added → 新增接口
for key, ep in unmatched_added:
if key not in matched_added:
reports.append(
EndpointChangeReport(
uri=ep.uri,
http_method=ep.http_method,
controller_class=ep.controller_class,
method_name=ep.method_name,
source_file=ep.source_file,
is_new_endpoint=True,
parameter_changes=[
ParameterChange(
@@ -220,27 +419,20 @@ def compare_endpoints(
param_name=p.name,
param_type=p.type,
required=p.required,
description=p.description,
source=p.source,
parent_dto=p.parent_dto,
body_param_name=p.body_param_name,
)
for p in new_ep.parameters
for p in ep.parameters
],
)
)
continue
if new_ep is None and old_ep is not None:
# 接口被删除
reports.append(
EndpointChangeReport(
uri=old_ep.uri,
http_method=old_ep.http_method,
controller_class=old_ep.controller_class,
method_name=old_ep.method_name,
is_removed_endpoint=True,
)
)
continue
# 同 URI 对比参数
# 5. 共同 URI对比参数变更
for key in common_keys:
old_ep = old_endpoints[key]
new_ep = new_endpoints[key]
param_changes = compare_parameters(old_ep.parameters, new_ep.parameters)
if param_changes:
reports.append(
@@ -249,6 +441,7 @@ def compare_endpoints(
http_method=new_ep.http_method,
controller_class=new_ep.controller_class,
method_name=new_ep.method_name,
source_file=new_ep.source_file,
parameter_changes=param_changes,
)
)

View File

@@ -7,12 +7,13 @@ from __future__ import annotations
import re
from pathlib import Path
from typing import Dict, List, Optional, Union
from typing import Dict, List, Optional
import javalang
from javalang.tree import (
Annotation,
ClassDeclaration,
ElementValuePair,
FieldDeclaration,
FormalParameter,
Literal,
@@ -22,9 +23,49 @@ from javalang.tree import (
from models import ApiEndpoint, ApiParameter
# javax.validation 必填注解
REQUIRED_FIELD_ANNS = {"NotNull", "NotEmpty", "NotBlank"}
MAPPING_ANNS = {"GetMapping", "PostMapping", "PutMapping", "DeleteMapping", "PatchMapping", "RequestMapping"}
CONTROLLER_ANNS = {"RestController", "Controller"}
# Spring MVC 框架自动注入参数,不属于 API 调用方入参,解析时忽略
FRAMEWORK_PARAM_TYPES = {
"HttpServletRequest",
"HttpServletResponse",
"HttpSession",
"ServletRequest",
"ServletResponse",
"WebRequest",
"NativeWebRequest",
"Model",
"ModelMap",
"RedirectAttributes",
"BindingResult",
"Errors",
"Authentication",
"Principal",
"Locale",
"TimeZone",
"InputStream",
"OutputStream",
"Reader",
"Writer",
"HttpHeaders",
"UriComponentsBuilder",
}
def _is_framework_param(type_name: str, param_name: str) -> bool:
"""判断是否为框架注入参数(非 API 调用方需要传递)。"""
simple = type_name.split(".")[-1].replace(">", "").replace("<", "").strip()
if simple in FRAMEWORK_PARAM_TYPES:
return True
if param_name in ("request", "response") and (
simple.endswith("Request") or simple.endswith("Response")
):
return True
return False
def _ann_simple_name(ann: Annotation) -> str:
"""获取注解简单类名。"""
@@ -32,14 +73,18 @@ def _ann_simple_name(ann: Annotation) -> str:
def _literal_str(node) -> str:
"""Literal 节点提取字符串值。"""
"""AST 节点提取字符串或布尔值。"""
if node is None:
return ""
if isinstance(node, Literal):
v = node.value or ""
return str(v).strip('"').strip("'")
v = node.value
if isinstance(v, bool):
return str(v).lower()
return str(v or "").strip('"').strip("'")
if isinstance(node, MemberReference):
return node.member
if isinstance(node, bool):
return str(node).lower()
return str(node).strip('"').strip("'")
@@ -52,12 +97,12 @@ def _collect_ann_members(ann: Annotation) -> Dict[str, str]:
el = ann.element
if el is None:
return members
if isinstance(el, list):
for item in el:
if hasattr(item, "name") and hasattr(item, "value"):
members[item.name] = _literal_str(item.value)
elif hasattr(el, "name") and hasattr(el, "value"):
if isinstance(el, ElementValuePair):
members[el.name] = _literal_str(el.value)
elif isinstance(el, list):
for item in el:
if isinstance(item, ElementValuePair):
members[item.name] = _literal_str(item.value)
else:
members["value"] = _literal_str(el)
return members
@@ -116,7 +161,10 @@ def _join_paths(base: str, sub: str) -> str:
def _http_method(ann_name: str, ann: Annotation) -> str:
"""从映射注解推断 HTTP 方法。"""
"""从映射注解推断 HTTP 方法。
支持大小写不敏感匹配,避免 PUTMapping、POSTMapping 等不规范写法导致解析失败。
"""
mapping = {
"GetMapping": "GET",
"PostMapping": "POST",
@@ -124,9 +172,11 @@ def _http_method(ann_name: str, ann: Annotation) -> str:
"DeleteMapping": "DELETE",
"PatchMapping": "PATCH",
}
if ann_name in mapping:
return mapping[ann_name]
if ann_name == "RequestMapping":
# 大小写不敏感匹配
for key, value in mapping.items():
if key.lower() == ann_name.lower():
return value
if ann_name.lower() == "requestmapping":
m = _ann_string(ann, "method")
if m:
return m.replace("RequestMethod.", "").upper()
@@ -185,20 +235,89 @@ def _param_required(param: FormalParameter) -> bool:
return not _has_ann(param, "Nullable")
JAVADOC_PARAM_RE = re.compile(
r"@param\s+(\w+)\s+(.*?)(?=\n\s*\*\s*@|\n\s*\*/|\Z)",
re.DOTALL,
)
def _clean_javadoc_text(text: str) -> str:
"""清理 JavaDoc 行前缀与多余空白。"""
cleaned = re.sub(r"\s*\*\s?", " ", text)
return re.sub(r"\s+", " ", cleaned).strip()
def _parse_javadoc_params(javadoc: str) -> Dict[str, str]:
"""从 JavaDoc 块解析 @param 名称 -> 说明。"""
if not javadoc:
return {}
result: Dict[str, str] = {}
for match in JAVADOC_PARAM_RE.finditer(javadoc):
name = match.group(1)
desc = _clean_javadoc_text(match.group(2))
if desc:
result[name] = desc
return result
def _extract_javadoc_before_line(source: str, target_line: int) -> str:
"""
提取目标行之前紧邻的 JavaDoc 块。
target_line 为 1-indexed与方法声明行号一致
"""
if not source or target_line <= 1:
return ""
lines = source.splitlines()
idx = target_line - 2
while idx >= 0 and not lines[idx].strip():
idx -= 1
while idx >= 0 and lines[idx].strip().startswith("@"):
idx -= 1
if idx < 0 or not lines[idx].strip().endswith("*/"):
return ""
end_idx = idx
while idx >= 0 and not lines[idx].strip().startswith("/**"):
idx -= 1
if idx < 0:
return ""
return "\n".join(lines[idx : end_idx + 1])
def _field_required(field: FieldDeclaration) -> bool:
"""DTO 字段是否必填(@NotNull / @NotEmpty / @NotBlank"""
if _has_ann(field, "Nullable"):
return False
for ann in field.annotations or []:
if _ann_simple_name(ann) in REQUIRED_FIELD_ANNS:
return True
return False
def _lookup_param_description(
javadoc_params: Dict[str, str], param: FormalParameter, resolved_name: str
) -> Optional[str]:
"""按注解名或形参名匹配 JavaDoc @param 说明。"""
for key in (resolved_name, param.name):
if key and key in javadoc_params:
return javadoc_params[key]
return None
class ControllerAstParser:
"""
基于 javalang 的 Controller 解析器。
只解析传入的文件不扫描整个目录CI 更快)。
"""
def __init__(self, repo_root: Path, source_dir: Path):
def __init__(self, repo_root: Path, source_dirs: List[Path]):
"""
:param repo_root: 仓库根目录
:param source_dir: Java 源码根目录repo_root 下的相对路径对应的绝对路径
:param repo_root: 仓库根目录
:param source_dirs: Java 源码根目录列表(用于查找 DTO 等
"""
self.repo_root = repo_root
self.source_dir = source_dir
self.source_dirs = source_dirs
self._dto_cache: Dict[str, List[ApiParameter]] = {}
self._current_source = ""
def parse_file_content(self, source: str, repo_relative_path: str) -> List[ApiEndpoint]:
"""
@@ -209,6 +328,7 @@ class ControllerAstParser:
:return: 端点列表
"""
endpoints: List[ApiEndpoint] = []
self._current_source = source
try:
tree = javalang.parse.parse(source)
except (javalang.parser.JavaSyntaxError, RecursionError) as exc:
@@ -252,9 +372,16 @@ class ControllerAstParser:
continue
method_path = _ann_string(ann, "value", "path")
javadoc_params: Dict[str, str] = {}
if getattr(method, "position", None) and method.position:
javadoc = _extract_javadoc_before_line(
self._current_source, method.position.line
)
javadoc_params = _parse_javadoc_params(javadoc)
params = []
for p in method.parameters or []:
params.extend(self._extract_param(p))
params.extend(self._extract_param(p, javadoc_params))
return ApiEndpoint(
http_method=_http_method(ann_name, ann),
@@ -266,38 +393,82 @@ class ControllerAstParser:
)
return None
def _extract_param(self, param: FormalParameter) -> List[ApiParameter]:
"""提取方法参数,@RequestBody 展开 DTO 字段。"""
def _extract_param(
self, param: FormalParameter, javadoc_params: Optional[Dict[str, str]] = None
) -> List[ApiParameter]:
"""提取方法参数,@RequestBody 展开 DTO 字段;忽略框架注入参数。"""
type_name = _type_to_str(param.type)
if _has_ann(param, "RequestBody"):
return self._expand_dto(type_name, "body")
name = _param_name(param)
javadoc_params = javadoc_params or {}
if _is_framework_param(type_name, name):
return []
if _has_ann(param, "RequestBody"):
body_desc = _lookup_param_description(javadoc_params, param, name)
return self._expand_dto(
type_name,
"body",
body_param_name=param.name,
body_param_desc=body_desc,
)
description = _lookup_param_description(javadoc_params, param, name)
return [
ApiParameter(
name=_param_name(param),
name=name,
type=type_name,
required=_param_required(param),
source=_param_source(param),
description=description,
)
]
def _expand_dto(self, type_name: str, source: str) -> List[ApiParameter]:
"""展开 @RequestBody DTO 字段。"""
def _expand_dto(
self,
type_name: str,
source: str,
body_param_name: str = "",
body_param_desc: Optional[str] = None,
) -> List[ApiParameter]:
"""展开 @RequestBody DTO 一级字段。"""
simple = type_name.split(".")[-1].replace(">", "").replace("<", "").strip()
if simple in self._dto_cache:
return self._dto_cache[simple]
cache_key = f"{simple}:{body_param_name}"
if cache_key in self._dto_cache:
return self._dto_cache[cache_key]
dto_file = self._find_dto_file(simple)
if not dto_file:
result = [ApiParameter(name=simple, type=type_name, required=True, source=source)]
self._dto_cache[simple] = result
result = [
ApiParameter(
name=simple,
type=type_name,
required=True,
source=source,
description=body_param_desc,
parent_dto=simple,
body_param_name=body_param_name or None,
)
]
self._dto_cache[cache_key] = result
return result
try:
tree = javalang.parse.parse(dto_file.read_text(encoding="utf-8", errors="ignore"))
dto_source = dto_file.read_text(encoding="utf-8", errors="ignore")
tree = javalang.parse.parse(dto_source)
except (javalang.parser.JavaSyntaxError, OSError):
result = [ApiParameter(name=simple, type=type_name, required=True, source=source)]
self._dto_cache[simple] = result
result = [
ApiParameter(
name=simple,
type=type_name,
required=True,
source=source,
description=body_param_desc,
parent_dto=simple,
body_param_name=body_param_name or None,
)
]
self._dto_cache[cache_key] = result
return result
fields: List[ApiParameter] = []
@@ -307,56 +478,81 @@ class ControllerAstParser:
for field in type_decl.fields or []:
if "static" in (field.modifiers or []):
continue
field_javadoc = ""
if getattr(field, "position", None) and field.position:
field_javadoc = _extract_javadoc_before_line(
dto_source, field.position.line
)
field_desc = _clean_javadoc_text(
field_javadoc.replace("/**", "").replace("*/", "").strip()
) or None
for decl in field.declarators:
fields.append(
ApiParameter(
name=decl.name,
type=_type_to_str(field.type),
required=not _has_ann(field, "Nullable"),
required=_field_required(field),
source=source,
description=field_desc,
parent_dto=simple,
body_param_name=body_param_name or None,
)
)
if not fields:
fields = [ApiParameter(name=simple, type=type_name, required=True, source=source)]
fields = [
ApiParameter(
name=simple,
type=type_name,
required=True,
source=source,
description=body_param_desc,
parent_dto=simple,
body_param_name=body_param_name or None,
)
]
self._dto_cache[simple] = fields
self._dto_cache[cache_key] = fields
return fields
def _find_dto_file(self, simple_name: str) -> Optional[Path]:
"""在源码目录中查找 DTO 文件。"""
if not self.source_dir.exists():
return None
"""配置的源码目录及仓库内 src/main/java 中查找 DTO 文件。"""
target = f"{simple_name}.java"
for path in self.source_dir.rglob(target):
return path
for source_dir in self.source_dirs:
if source_dir.exists():
for path in source_dir.rglob(target):
return path
if self.repo_root.exists():
for path in self.repo_root.rglob(target):
if "src/main/java" in path.as_posix():
return path
return None
def parse_controller_files(
repo_root: Path,
source_subdir: str,
source_subdirs: List[str],
file_paths: List[str],
file_contents: Dict[str, str],
) -> List[ApiEndpoint]:
"""
批量解析指定 Controller 文件(仅解析传入的文件,不全量扫描)。
:param repo_root: 仓库根目录
:param source_subdir: 源码子目录(相对仓库根)
:param file_paths: 要解析的文件路径列表(相对仓库根)
:param file_contents: {文件路径: 源码内容}
:param repo_root: 仓库根目录
:param source_subdirs: 源码子目录列表(相对仓库根)
:param file_paths: 要解析的文件路径列表(相对仓库根)
:param file_contents: {文件路径: 源码内容}
:return: 所有端点
"""
source_dir = repo_root / source_subdir.replace("/", Path.sep)
parser = ControllerAstParser(repo_root, source_dir)
source_dirs = [(repo_root / sub).resolve() for sub in source_subdirs]
parser = ControllerAstParser(repo_root, source_dirs)
endpoints: List[ApiEndpoint] = []
for path in file_paths:
content = file_contents.get(path)
norm = path.replace("\\", "/")
content = file_contents.get(norm)
if not content:
continue
norm = path.replace("\\", "/")
endpoints.extend(parser.parse_file_content(content, norm))
return endpoints

View File

@@ -25,19 +25,19 @@ def filter_endpoints_by_files(
def parse_endpoints_from_files(
repo_root: Path,
source_subdir: str,
source_subdirs: List[str],
file_paths: List[str],
file_contents: Dict[str, str],
) -> List[ApiEndpoint]:
"""
解析指定 Controller 文件,提取接口参数(仅解析传入文件,不全量扫描)。
:param repo_root: 仓库根
:param source_subdir: 源码目录(相对仓库根)
:param file_paths: 文件路径列表
:param file_contents: 路径 -> 源码内容
:param repo_root: 仓库根
:param source_subdirs: 源码目录列表(相对仓库根)
:param file_paths: 文件路径列表
:param file_contents: 路径 -> 源码内容
:return: ApiEndpoint 列表
"""
from controller_ast_parser import parse_controller_files
return parse_controller_files(repo_root, source_subdir, file_paths, file_contents)
return parse_controller_files(repo_root, source_subdirs, file_paths, file_contents)

View File

@@ -1,6 +1,6 @@
"""
豆包 LLM 接口参数变更审核模块。
仅审核 Controller 层接口参数的增删改,不对 Java 源码做通用代码审查
LLM 仅输出简短的兼容性提示,详细变更由 AST + Markdown 通知展示
"""
import json
@@ -10,14 +10,17 @@ import requests
from comparator import EndpointChangeReport
# 写入 prompt不在通知中展示
FRAMEWORK_IGNORE_HINT = """
以下参数类型/名称属于 Spring MVC 框架自动注入,不是 API 调用方入参,审核时必须忽略,不要在结果中提及:
HttpServletRequest、HttpServletResponse、HttpSession、ServletRequest、ServletResponse、
WebRequest、NativeWebRequest、Model、ModelMap、RedirectAttributes、BindingResult、
Authentication、Principal 等。
"""
def is_llm_enabled(config: Dict[str, Any]) -> bool:
"""
判断大模型总开关是否开启。
:param config: 完整配置字典
:return: True=启用 LLM 审核
"""
"""判断大模型总开关是否开启。"""
return config.get("llm", {}).get("enabled", True)
@@ -26,14 +29,7 @@ def call_doubao_api(
prompt: str,
config: Dict[str, Any],
) -> Optional[str]:
"""
调用火山引擎豆包 Chat Completions API。
:param api_key: API Key
:param prompt: 用户提示词
:param config: 完整配置
:return: LLM 回复文本;失败返回 None
"""
"""调用豆包 API。"""
if not api_key or api_key == "YOUR_DOUBAO_API_KEY":
print("[警告] 未配置豆包 API Key跳过 LLM 审核。")
return None
@@ -41,7 +37,7 @@ def call_doubao_api(
llm_cfg = config.get("llm", {})
model = llm_cfg.get("model") or llm_cfg.get("endpoint_id", "")
if not model:
print("[警告] 未配置 llm.model 或 llm.endpoint_id,跳过 LLM 审核。")
print("[警告] 未配置 llm.model跳过 LLM 审核。")
return None
api_url = llm_cfg.get(
@@ -59,9 +55,9 @@ def call_doubao_api(
{
"role": "system",
"content": (
"你是 Java Spring Boot Controller 接口参数变更分析专家。"
"的职责是识别并整理 Controller 层接口参数的增、删、改、重命名,"
"确认 AST 解析结果是否准确,并指出对调用方的兼容性影响。"
"你是 Java Spring Boot API 变更分析专家。"
"只负责输出简短的兼容性风险提示,不重复罗列接口参数明细。"
+ FRAMEWORK_IGNORE_HINT
),
},
{"role": "user", "content": prompt},
@@ -73,13 +69,11 @@ def call_doubao_api(
kwargs = {"headers": headers, "json": payload}
if timeout is not None:
kwargs["timeout"] = timeout
resp = requests.post(api_url, **kwargs)
resp.raise_for_status()
data = resp.json()
if "choices" in data and data["choices"]:
return data["choices"][0]["message"]["content"]
print("[错误] AI 返回格式异常")
return None
except requests.RequestException as exc:
print(f"[错误] 豆包 API 调用失败: {exc}")
@@ -92,66 +86,49 @@ def build_parameter_change_prompt(
git_diff: str = "",
) -> str:
"""
构造接口参数变更审核提示词(对齐第一版需求:识别 Controller 参数增删改)
:param reports: AST 解析对比结果
:param changed_files: 本次变更的 Controller 文件列表
:param git_diff: 相关文件的 Git diff 内容
:return: 完整 prompt
构造 LLM 提示词:只要求输出兼容性摘要,不要求重复参数列表
"""
ast_report = []
for r in reports:
ast_report.append(
{
"uri": f"{r.http_method} {r.uri}",
"controller": r.controller_class,
"method": r.method_name,
"is_new_endpoint": r.is_new_endpoint,
"is_removed_endpoint": r.is_removed_endpoint,
"parameter_changes": [
"is_new": r.is_new_endpoint,
"is_removed": r.is_removed_endpoint,
"changes": [
{
"type": c.change_type.value,
"name": c.param_name,
"java_type": c.param_type,
"old_name": c.old_name,
"old_type": c.old_type,
"required": c.required,
"detail": c.detail,
}
for c in r.parameter_changes
],
}
)
diff_block = git_diff.strip() if git_diff.strip() else "(无 diff 内容"
if len(diff_block) > 8000:
diff_block = diff_block[:8000] + "\n... [diff 过长,已截断]"
diff_block = git_diff.strip()[:6000] if git_diff.strip() else "(无)"
return f"""审核以下 Controller 接口参数变更,整理并确认变更结果。
return f"""根据以下 Controller 接口参数变更,**仅输出「兼容性提示」**,要求:
## 变更的 Controller 文件
{FRAMEWORK_IGNORE_HINT}
## 输出格式(严格遵守)
- 只输出 36 行 Markdown不要输出「整体说明」「接口变更详情」等标题
- 不要逐条重复 URI 和参数列表(通知里已有)
- 不要提及「排除框架注入」相关字样
- 重点说明:是否有破坏性变更、哪些必填参数调用方必须传入
- 全新 Controller 说明「均为新接口,对现有调用方无破坏」即可
- 语气简洁,可用 <font color="warning">...</font> 标注风险项
## 变更文件
{json.dumps(changed_files, ensure_ascii=False)}
## AST 自动解析的参数变更报告
## AST 变更摘要
{json.dumps(ast_report, ensure_ascii=False, indent=2)}
## Git DiffController 相关)
```diff
## Git Diff
{diff_block}
```
## 审核要求
1. 逐条确认 AST 报告的参数变更是否准确(增/删/改/重命名)
2. 若 AST 有遗漏,补充遗漏的接口参数变更
3. 若 AST 有误报,指出并修正
4. 按以下格式整理每个接口的变更(与通知模板一致):
URI: GET /api/xxx
参数变更:
- 删除: Boolean userType
- 新增: Boolean includeDisabled (是否必填false)
- 重命名: String userName -> String accountName
5. 简要说明是否存在破坏性变更(影响前端/调用方)
6. 用中文回复,简洁清晰
"""
@@ -161,20 +138,8 @@ def review_parameter_changes(
changed_files: List[str],
git_diff: str = "",
) -> Optional[str]:
"""
使用 LLM 审核 Controller 接口参数变更AST 结果的二次确认与整理)。
:param reports: AST 对比报告
:param config: 完整配置
:param changed_files: 变更的 Controller 文件
:param git_diff: Git diff 文本
:return: LLM 整理后的审核结论;未启用或无报告时返回 None
"""
if not is_llm_enabled(config):
print("[LLM] 大模型开关已关闭,跳过接口参数变更审核。")
return None
if not reports:
"""LLM 审核,返回简短兼容性提示。"""
if not is_llm_enabled(config) or not reports:
return None
llm_cfg = config.get("llm", {})

View File

@@ -42,6 +42,14 @@ def load_config(config_path: Path) -> dict:
return yaml.safe_load(f) or {}
def resolve_source_subdirs(config: dict) -> list:
"""从配置解析 Java 源码目录列表(支持 source_dirs 多模块)。"""
dirs = config.get("source_dirs")
if dirs:
return [str(d) for d in dirs]
return [config.get("source_dir", "src/main/java")]
def _read_file_safe(path: Path) -> str:
"""读取文件内容。"""
try:
@@ -73,7 +81,7 @@ def _load_version_contents(
def parse_changed_endpoints(
repo_root: Path,
source_subdir: str,
source_subdirs: list,
changed_files: list,
old_sha: str,
label: str,
@@ -86,7 +94,7 @@ def parse_changed_endpoints(
print(f"[AST] 解析 {label} 版本 {len(contents)} 个 Controller 文件")
endpoints = parse_endpoints_from_files(
repo_root, source_subdir, changed_files, contents
repo_root, source_subdirs, changed_files, contents
)
print(f"[AST] {label} 版本共 {len(endpoints)} 个接口")
return endpoints_to_map(endpoints)
@@ -111,7 +119,11 @@ def main() -> int:
config_path = repo_root / config_path
config = load_config(config_path)
source_subdir = config.get("source_dir", "src/main/java")
if not config.get("check", {}).get("enabled", True):
print("[检查] API 变动检查已关闭check.enabled=false跳过。")
return 0
source_subdirs = resolve_source_subdirs(config)
commit_info = get_current_commit()
push_user = args.push_user or commit_info.author
@@ -121,6 +133,8 @@ def main() -> int:
print("=" * 40)
print(f"推送人: {push_user}")
print(f"推送时间: {push_time}")
print(f"API 变动检查: {config.get('check', {}).get('enabled', True)}")
print(f"源码目录: {', '.join(source_subdirs)}")
print(f"LLM 审核: {config.get('llm', {}).get('enabled', True)}")
print(f"记录日志: {config.get('log', {}).get('enabled', False)}")
print("=" * 40)
@@ -130,7 +144,7 @@ def main() -> int:
print("[Git] 首次提交,无可对比版本,跳过。")
return 0
changed_files = get_changed_java_controller_files(prev_sha, commit_info.sha)
changed_files = [f.replace("\\", "/") for f in get_changed_java_controller_files(prev_sha, commit_info.sha)]
if not changed_files:
print("[Git] 本次提交未变更 Controller 文件,跳过。")
return 0
@@ -142,10 +156,10 @@ def main() -> int:
git_diff = get_controller_files_diff(prev_sha, commit_info.sha, changed_files)
new_map = parse_changed_endpoints(
repo_root, source_subdir, changed_files, prev_sha, "new"
repo_root, source_subdirs, changed_files, prev_sha, "new"
)
old_map = parse_changed_endpoints(
repo_root, source_subdir, changed_files, prev_sha, "old"
repo_root, source_subdirs, changed_files, prev_sha, "old"
)
new_filtered = endpoints_to_map(

View File

@@ -15,6 +15,8 @@ class ApiParameter:
required: bool = True
source: str = "query"
description: Optional[str] = None
parent_dto: Optional[str] = None
body_param_name: Optional[str] = None
@dataclass

View File

@@ -1,162 +1,283 @@
"""
企业微信机器人通知模块。
按第一版模板发送 Controller 接口参数变更通知,支持超长内容分段发送
企业微信 Markdown 通知模块。
支持加粗、颜色info/comment/warning新增接口与变更接口使用不同展示模板
"""
import json
from typing import List, Optional
import re
from collections import OrderedDict
from typing import List, Optional, Tuple
import requests
from comparator import EndpointChangeReport
from git_utils import CommitInfo
from comparator import EndpointChangeReport, ParameterChange
# 企微 text 消息字节上限约 2048留余量按字符分段
MAX_TEXT_LENGTH = 2000
# 企微 Markdown 单条上限 4096 字符,留余量
MAX_MD_LENGTH = 3800
def truncate_text(text: str, max_length: int = MAX_TEXT_LENGTH) -> str:
"""
截断文本,避免超出企微单条消息限制。
:param text: 原始文本
:param max_length: 最大字符数
:return: 截断后文本
"""
def truncate_text(text: str, max_length: int = MAX_MD_LENGTH) -> str:
"""截断超长消息。"""
if len(text) <= max_length:
return text
return text[:max_length] + "\n... [消息过长,已截断]"
return text[:max_length] + "\n\n<font color=\"comment\">... 消息过长,已截断</font>"
def build_single_endpoint_message(
report: EndpointChangeReport,
push_user: str,
push_time: str,
) -> str:
def _format_param_change_list(changes: List[ParameterChange]) -> List[str]:
"""生成企微友好的普通参数变更列表(卡片式)。"""
if not changes:
return ['<font color="comment">无</font>']
lines = ["", f"共 **{len(changes)}** 项变更", ""]
for i, change in enumerate(changes, 1):
lines.append(change.to_markdown_block(i))
if i < len(changes):
lines.append("")
return lines
def _body_dto_group_key(change: ParameterChange) -> Tuple[str, str]:
"""类对象变更分组键:(body 参数名, DTO 类名)。"""
return (change.body_param_name or "body", change.parent_dto or "")
def _format_body_field_line(change: ParameterChange, *, is_last: bool) -> List[str]:
"""格式化 DTO 一级字段变更行。"""
branch = "└─" if is_last else "├─"
desc = change.description or change.old_description
type_part = f" · `{change.param_type}`" if change.param_type else ""
req_part = f" · {change._required_tag()}" if change._required_tag() else ""
lines = [f"{branch} `{change.param_name}`{type_part}{req_part} {change._change_tag()}"]
if desc:
lines.append(f"> 说明:{desc}")
if change.change_type.value == "modified" and change.detail:
lines.append(f"> 变更:{change.detail}")
if change.change_type.value == "renamed":
lines.append(f"> `{change.old_name}` → `{change.param_name}`")
return lines
def _format_body_dto_groups(changes: List[ParameterChange]) -> List[str]:
"""按 DTO 分组展示 @RequestBody 一级字段。"""
if not changes:
return ['<font color="comment">无</font>']
groups: OrderedDict[Tuple[str, str], List[ParameterChange]] = OrderedDict()
for change in changes:
key = _body_dto_group_key(change)
groups.setdefault(key, []).append(change)
lines: List[str] = ["", f"共 **{len(groups)}** 个类对象 · **{len(changes)}** 项字段变更", ""]
for (param_name, dto_name), group in groups.items():
label = param_name or "body"
dto_part = f" · `{dto_name}`" if dto_name else ""
lines.append(f"**{label}**{dto_part}")
lines.append("")
for i, change in enumerate(group):
lines.extend(_format_body_field_line(change, is_last=(i == len(group) - 1)))
lines.append("")
if lines and lines[-1] == "":
lines.pop()
return lines
def _format_param_details_section(report: EndpointChangeReport) -> List[str]:
"""生成接口参数变动详情区块。"""
body_changes = [c for c in report.parameter_changes if c.source == "body"]
regular_changes = [c for c in report.parameter_changes if c.source != "body"]
lines = ["", "---------------------------------------", "", "#### 【接口参数变动详情】", ""]
if body_changes:
lines.append("**类对象变更(一级字段)**")
lines.extend(_format_body_dto_groups(body_changes))
lines.append("")
if regular_changes:
lines.append("**普通参数变更**")
lines.extend(_format_param_change_list(regular_changes))
lines.append("")
if not body_changes and not regular_changes:
lines.append('<font color="comment">无</font>')
return lines
def _format_endpoint_block(report: EndpointChangeReport) -> str:
"""
按第一版模板构建单个接口的通知正文
模板示例:
[API变更通知]
URI: GET /api/users/{id}
修改人:张三
修改时间2026-06-03 10:00:00
参数变更:
- 删除: Boolean userType
- 新增: Boolean includeDisabled (是否必填false)
:param report: 单个接口变更报告
:param push_user: 代码推送人
:param push_time: 推送时间
:return: 通知文本
格式化单个接口块,按模板匹配格式输出
全路径类名显示为 source_file相对仓库根的完整 .java 路径)。
"""
lines = [
"[API变更通知]",
f"URI: {report.http_method} {report.uri}",
f"修改人:{push_user}",
f"修改时间:{push_time}",
change_type = "新增接口" if report.is_new_endpoint else ("删除接口" if report.is_removed_endpoint else "修改参数")
uri_line = f"**{report.http_method}** `{report.uri}`"
file_path = report.source_file or report.controller_class
class_line = f"- **全路径类名:** <font color=\"info\">**{file_path}**</font>"
header = [
f"- **变更类型:** <font color=\"warning\">**{change_type}**</font>",
f"- **URI** {uri_line}",
class_line,
]
if report.is_new_endpoint:
lines.append("接口状态:新增接口")
if report.parameter_changes:
lines.append("参数变更:")
for change in report.parameter_changes:
lines.append(change.to_display_line())
elif report.is_removed_endpoint:
lines.append("接口状态:已删除接口")
lines.append(" - 整个接口已被移除")
else:
lines.append("参数变更:")
for change in report.parameter_changes:
lines.append(change.to_display_line())
if report.is_removed_endpoint:
return "\n".join(header + ["", f"<font color=\"warning\">**该接口已被移除**</font>"])
return "\n".join(lines)
return "\n".join(header + _format_param_details_section(report))
def build_all_notifications(
def build_markdown_notification(
reports: List[EndpointChangeReport],
push_user: str,
push_time: str,
llm_review: Optional[str] = None,
) -> List[str]:
llm_summary: Optional[str] = None,
) -> str:
"""
将所有接口变更组装为通知消息列表,超长时自动分段
构建完整 Markdown 通知正文
:param reports: 变更报告列表
:param push_user: 推送人
:param push_time: 推送时间
:param llm_review: LLM 参数变更审核结论(可选,附在最后一条或单独一条
:return: 待发送的消息段落列表
:param reports: AST 变更报告
:param push_user: 推送人
:param push_time: 推送时间
:param llm_summary: LLM 兼容性摘要(可选,简短
:return: Markdown 文本
"""
if not reports:
return []
parts: List[str] = []
messages: List[str] = []
current = ""
# 所有 API 级变更(新增、修改路径、修改请求方式、删除、参数变更)统一走 model1.md 路径变更通知
method_changed_reports = [r for r in reports if r.is_method_changed]
renamed_reports = [r for r in reports if r.is_renamed_endpoint]
new_reports = [r for r in reports if r.is_new_endpoint]
# 参数变更报告只包含「URI/方法未变,仅参数变化」的报告
# 路径变更 + 参数变更、方法变更 + 参数变更 场景已在上层 comparator 中拆分为独立报告
changed_reports = [
r for r in reports
if not r.is_new_endpoint
and not r.is_removed_endpoint
and not r.is_renamed_endpoint
and not r.is_method_changed
]
removed_reports = [r for r in reports if r.is_removed_endpoint]
for report in reports:
block = build_single_endpoint_message(report, push_user, push_time)
separator = "\n\n---\n\n"
# 1. 新增接口 → 走 API路径变更通知
for report in new_reports:
path_md = build_path_change_markdown(
old_uri="-",
new_uri=report.uri,
change_type="新增接口",
push_user=push_user,
push_time=push_time,
file_name=report.source_file or report.controller_class,
)
parts.append(path_md)
parts.append("")
if not current:
current = block
elif len(current) + len(separator) + len(block) <= MAX_TEXT_LENGTH:
current += separator + block
else:
messages.append(current)
current = block
# 2. 修改请求方式 → 使用独立的新模板 【API请求方式变更通知】
for report in method_changed_reports:
method_md = build_method_change_markdown(
uri=report.uri,
old_method=report.old_http_method or "?",
new_method=report.http_method,
push_user=push_user,
push_time=push_time,
file_name=report.source_file or report.controller_class,
)
parts.append(method_md)
parts.append("")
if current:
messages.append(current)
# 3. 修改路径 → 走 API路径变更通知
for report in renamed_reports:
path_md = build_path_change_markdown(
old_uri=report.old_uri or "-",
new_uri=report.uri,
change_type="修改路径",
push_user=push_user,
push_time=push_time,
file_name=report.source_file or report.controller_class,
)
parts.append(path_md)
parts.append("")
# LLM 审核结论单独或追加发送
if llm_review:
review_msg = f"[AI参数变更审核]\n修改人:{push_user}\n修改时间:{push_time}\n\n{llm_review}"
if messages and len(messages[-1]) + len(review_msg) + 4 <= MAX_TEXT_LENGTH:
messages[-1] += "\n\n" + review_msg
elif len(review_msg) <= MAX_TEXT_LENGTH:
messages.append(review_msg)
else:
messages.extend(_split_long_text(review_msg, MAX_TEXT_LENGTH))
# 4. 删除接口 → 走 API路径变更通知
for report in removed_reports:
path_md = build_path_change_markdown(
old_uri=report.uri,
new_uri="已删除",
change_type="删除接口",
push_user=push_user,
push_time=push_time,
file_name=report.source_file or report.controller_class,
)
parts.append(path_md)
parts.append("")
return messages
# 4. 普通参数变更(非路径变更)仍使用 model.md 格式
if changed_reports:
parts.append("# 【API参数变更通知】")
parts.append(f"- **修改人:** {push_user}")
parts.append(f"- **修改时间:** {push_time}")
parts.append("")
for report in changed_reports:
parts.append(_format_endpoint_block(report))
parts.append("")
if llm_summary:
cleaned = llm_summary.strip()
# 去掉 LLM 可能输出的「排除框架注入」类说明
cleaned = re.sub(
r"排除Spring MVC框架自动注入的[^]+",
"",
cleaned,
)
cleaned = re.sub(
r"排除Spring MVC框架自动注入的[`\w/]+[`\w/、/]*[。\.]?",
"",
cleaned,
)
if cleaned:
parts.append("### <font color=\"comment\">【兼容性提示】</font>")
parts.append(cleaned)
return "\n".join(parts).strip()
def _split_long_text(text: str, max_len: int) -> List[str]:
"""行拆分超长文本"""
def _split_markdown(text: str, max_len: int) -> List[str]:
""" ### 标题块拆分超长 Markdown"""
if len(text) <= max_len:
return [text]
lines = text.split("\n")
chunks: List[str] = []
current = ""
current: List[str] = []
for line in lines:
candidate = current + line + "\n"
if len(candidate) > max_len and current:
chunks.append(current.rstrip())
current = line + "\n"
if line.startswith("### ") and current and len("\n".join(current)) > 200:
chunks.append("\n".join(current))
current = [line]
else:
current = candidate
if current.strip():
chunks.append(current.rstrip())
return chunks
current.append(line)
if len("\n".join(current)) >= max_len:
chunks.append("\n".join(current))
current = []
if current:
if chunks and len("\n".join(current)) < 200:
chunks[-1] = chunks[-1] + "\n" + "\n".join(current)
else:
chunks.append("\n".join(current))
return chunks or [truncate_text(text)]
def _post_wecom_text(webhook_url: str, content: str) -> bool:
"""
发送单条 text 消息到企业微信。
:param webhook_url: Webhook URL
:param content: 消息正文
:return: 是否成功
"""
def _post_wecom_markdown(webhook_url: str, content: str) -> bool:
"""发送企微 Markdown 消息。"""
if not webhook_url or "YOUR_WECOM_KEY" in webhook_url:
print("[警告] 未配置有效的企业微信 Webhook URL。")
print("--- 通知预览 ---")
print(content[:800])
print(content[:1000])
return False
payload = {
"msgtype": "text",
"text": {"content": truncate_text(content)},
"msgtype": "markdown",
"markdown": {"content": truncate_text(content)},
}
try:
@@ -184,35 +305,221 @@ def send_parameter_change_notification(
mentioned_users: Optional[List[str]] = None,
) -> int:
"""
发送 Controller 接口参数变更通知(支持分段)
发送 Markdown 格式的接口变更通知
:param webhook_url: 企微 Webhook
:param reports: AST 参数变更报告
:param push_user: 推送人
:param push_time: 推送时间
:param llm_review: LLM 参数变更审核(可选)
:param mentioned_users: @ 成员 userid 列表
:return: 成功发送条数
严格按变更类型拆分,各自独立构建和发送企微通知:
- 方法变更 → 独立调用 build_method_change_markdown
- 路径变更(新增/修改/删除) → 独立调用 build_path_change_markdown
- 参数变更 → 独立调用 _format_endpoint_block
不同类型之间完全互不干扰,各自走独立分支。
"""
if not reports and not llm_review:
print("无接口参数变更,不发送到企业微信")
return 0
segments = build_all_notifications(reports, push_user, push_time, llm_review)
if not segments:
return 0
# 按类型严格分组(互不重叠)
method_changed_reports = [r for r in reports if r.is_method_changed]
renamed_reports = [r for r in reports if r.is_renamed_endpoint]
new_reports = [r for r in reports if r.is_new_endpoint]
removed_reports = [r for r in reports if r.is_removed_endpoint]
changed_reports = [
r for r in reports
if not r.is_new_endpoint
and not r.is_removed_endpoint
and not r.is_renamed_endpoint
and not r.is_method_changed
]
sent = 0
for i, segment in enumerate(segments):
payload_text = segment
if mentioned_users and i == 0:
# text 类型 @ 成员需放在 mentioned_list
pass # 在 _post 里处理较复杂,首条单独带 mentioned
if _post_wecom_text(webhook_url, payload_text):
# ========== 1. 请求方式变更通知(独立分支) ==========
for report in method_changed_reports:
md = build_method_change_markdown(
uri=report.uri,
old_method=report.old_http_method or "?",
new_method=report.http_method,
push_user=push_user,
push_time=push_time,
file_name=report.source_file or report.controller_class,
)
if _post_wecom_markdown(webhook_url, md):
sent += 1
print(f"{sent} 条通知已发送到企业微信")
print(f"{sent} 条通知已发送到企业微信(请求方式变更)")
# ========== 2. 路径变更通知(新增/修改/删除) ==========
# 新增接口
for report in new_reports:
md = build_path_change_markdown(
old_uri="-",
new_uri=report.uri,
change_type="新增接口",
push_user=push_user,
push_time=push_time,
file_name=report.source_file or report.controller_class,
)
if report.parameter_changes:
param_section = "\n".join(_format_param_details_section(report)).strip()
md = f"{md}\n\n{param_section}"
if _post_wecom_markdown(webhook_url, md):
sent += 1
print(f"{sent} 条通知已发送到企业微信(新增接口)")
# 修改路径
for report in renamed_reports:
md = build_path_change_markdown(
old_uri=report.old_uri or "-",
new_uri=report.uri,
change_type="修改路径",
push_user=push_user,
push_time=push_time,
file_name=report.source_file or report.controller_class,
)
if _post_wecom_markdown(webhook_url, md):
sent += 1
print(f"{sent} 条通知已发送到企业微信(修改路径)")
# 删除接口
for report in removed_reports:
md = build_path_change_markdown(
old_uri=report.uri,
new_uri="已删除",
change_type="删除接口",
push_user=push_user,
push_time=push_time,
file_name=report.source_file or report.controller_class,
)
if _post_wecom_markdown(webhook_url, md):
sent += 1
print(f"{sent} 条通知已发送到企业微信(删除接口)")
# ========== 3. 参数变更通知(独立分支) ==========
if changed_reports:
# 构建参数变更通知(只包含参数变更报告,对齐 model.md
parts: List[str] = []
parts.append("# 【API参数变更通知】")
parts.append(f"- **修改人:** {push_user}")
parts.append(f"- **修改时间:** {push_time}")
parts.append("")
for report in changed_reports:
parts.append(_format_endpoint_block(report))
parts.append("")
if llm_review:
parts.append("---")
parts.append("### <font color=\"comment\">兼容性提示</font>")
parts.append(llm_review.strip())
md = "\n".join(parts).strip()
if _post_wecom_markdown(webhook_url, md):
sent += 1
print(f"{sent} 条通知已发送到企业微信(参数变更)")
if sent > 0:
print(f"总共发送 {sent} 条通知到企业微信")
return sent
def build_path_change_markdown(
old_uri: str,
new_uri: str,
change_type: str,
push_user: str,
push_time: str,
file_name: str,
) -> str:
"""构建 API路径变更通知完全匹配 model1.md 模板,并加强视觉区分。
支持的 change_type
- 新增接口 / 删除接口 / 修改路径 / 修改请求方式
改进点:
- 标题使用【】风格
- 头部信息缩进 + 颜色高亮
- URI 详情使用列表(更直观)
- 「修改请求方式」额外展示方法变更
"""
# 变更类型高亮
type_highlight = f"<font color=\"warning\">**{change_type}**</font>"
# 全路径类名高亮
class_highlight = f"<font color=\"info\">**{file_name}**</font>"
# 根据变更类型优化 URI 展示
if change_type == "新增接口":
old_display = "`-`"
new_display = f"<font color=\"info\">**`{new_uri}`**</font> ← <font color=\"info\">**新增**</font>"
elif change_type == "删除接口":
old_display = f"<font color=\"warning\">**`{old_uri}`**</font> ← <font color=\"warning\">**已删除**</font>"
new_display = "`已删除`"
else: # 修改路径
old_display = f"<font color=\"warning\">~~`{old_uri}`~~</font> ← <font color=\"warning\">**旧路径**</font>"
new_display = f"<font color=\"info\">**`{new_uri}`**</font> ← <font color=\"info\">**新路径**</font>"
parts = [
"# 【API路径变更通知】",
"",
f" 变更类型: {type_highlight}",
f" 全路径类名: {class_highlight}",
f" 修改人: {push_user}",
f" 修改时间: {push_time}",
"",
"---------------------------------------",
"",
"#### 【URI变更详情】",
f"- **原路径:** {old_display}",
f"- **新路径:** {new_display}",
"",
]
return "\n".join(parts).strip()
def build_method_change_markdown(
uri: str,
old_method: str,
new_method: str,
push_user: str,
push_time: str,
file_name: str,
) -> str:
"""构建【API请求方式变更通知】独立模板。
格式参考 model1.md但专门针对 HTTP 方法变更场景设计,
突出「原请求方式 → 新请求方式」的对比。
"""
type_highlight = '<font color="warning">**修改请求方式**</font>'
class_highlight = f'<font color="info">**{file_name}**</font>'
uri_highlight = f'<font color="info">**`{uri}`**</font>'
old_m = f'<font color="warning">**{old_method}**</font>'
new_m = f'<font color="info">**{new_method}**</font>'
parts = [
"# 【API请求方式变更通知】",
"",
f" 变更类型: {type_highlight}",
f" 全路径类名: {class_highlight}",
f" 修改人: {push_user}",
f" 修改时间: {push_time}",
"",
"---------------------------------------",
"",
"#### 【请求方式变更详情】",
f"- **URI** {uri_highlight}",
f"- **原请求方式:** {old_m}",
f"- **新请求方式:** {new_m} ← <font color=\"info\">**请求方式已变更**</font>",
"",
]
return "\n".join(parts).strip()
def send_path_change_notification(
webhook_url: str,
old_uri: str,
new_uri: str,
change_type: str,
push_user: str,
push_time: str,
file_name: str,
) -> bool:
"""发送路径变更通知。"""
md = build_path_change_markdown(old_uri, new_uri, change_type, push_user, push_time, file_name)
return _post_wecom_markdown(webhook_url, md)

View File

@@ -1,4 +1,9 @@
# Python 依赖(纯 Python AST 解析,无需 Java
PyYAML>=6.0.1
requests>=2.31.0
javalang>=0.13.0
# Python 依赖(版本锁定,避免 CI pip 冲突
PyYAML==6.0.2
requests==2.32.3
charset-normalizer==3.4.1
urllib3==2.2.3
certifi==2024.8.30
idna==3.10
javalang==0.13.0
six==1.16.0

View File

@@ -2,9 +2,17 @@
# AI-Check 配置文件(位于 .gitea/ 目录,与业务代码解耦)
# ============================================================
# ---------- API 变动检查 ----------
# 总开关false 时跳过 Controller 接口参数变更检测(不对比、不通知)
check:
enabled: true
# 业务 Java 源码目录(相对仓库根目录)
# 单模块: src/main/java
# 多模块: ftb/src/main/java
# 单模块: source_dir: "src/main/java"
# 多模块: 使用 source_dirs优先于 source_dir
source_dirs:
- "jnpf-ftb/jnpf-ftb-biz/src/main/java"
- "jnpf-ftb/jnpf-ftb-entity/src/main/java"
source_dir: "ftb/src/main/java"
# ---------- 企业微信机器人 ----------
@@ -13,7 +21,7 @@ wecom:
# ---------- 豆包 LLM审核接口参数变更----------
llm:
enabled: true
enabled: false
api_key: "2f3f7ee9-a6f7-46b7-a709-a36743a83a04"
model: "doubao-seed-1-8-251228"
endpoint_id: ""

34
.gitea/model.md Normal file
View File

@@ -0,0 +1,34 @@
# 【API参数变更通知】
- **变更类型:** {新增接口 / 修改参数 / 删除接口}
- **URI** {Method} {URI}
- **修改人:** {Modifier}
- **修改时间:** {ModifyTime}
- **全路径类名:** {FileName}
---
## 接口参数变动详情
---
### 类对象变更
(如有对象替换或对象属性变更)
- **对象:** {类名}
- **变更方式:** {对象属性变更 / 对象替换旧类A → 新类B}
- **属性变更明细:**
- [新增] 属性: `attr1` 说明: {说明}
- [删除] 属性: `attr2` 说明: {说明}
- [修改] 属性: `attr3` 说明: {说明}
### 【参数变更】
- **变更列表:**
| 字段 | 说明 | 变更 |
|------|------|------|
| `pageSize` | 每页条数 | 新增必填 |
| `keyword` | 搜索关键词 | 类型由String改为Long |
| `startTime` | 开始时间 | 删除 |
(如无变更,显示:无)

17
.gitea/model1.md Normal file
View File

@@ -0,0 +1,17 @@
# 【API路径变更通知】
变更类型: {新增接口 / 修改路径 / 删除接口}
全路径类名: {FullClassName}
修改人: {Modifier}
修改时间: {ModifyTime}
---
#### 【URI变更详情】
- **原路径:** `{OldURI}` *(新增时显示:-*
- **新路径:** `{NewURI}` *(删除时显示:已删除 / -*
**示例:**
- 全路径类名:`com.example.controller.UserController`
- 原路径:`/api/users/{id}`
- 新路径:`/api/users/getall`

View File

@@ -26,16 +26,20 @@ jobs:
exit 1
fi
- name: 安装 Python 依赖
- name: 安装 Python 依赖
run: |
sudo apt-get update
sudo apt-get install -y python3 python3-pip
python3 -m pip install --break-system-packages -r .gitea/checker/requirements.txt
if ! python3 -m venv .gitea/.venv 2>/dev/null; then
sudo apt-get update -qq
sudo apt-get install -y python3-venv
python3 -m venv .gitea/.venv
fi
.gitea/.venv/bin/pip install --upgrade pip
.gitea/.venv/bin/pip install -r .gitea/checker/requirements.txt
- name: 检测 Controller 接口参数变更
run: |
COMMIT_TIME=$(git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S')
python3 .gitea/checker/main.py \
.gitea/.venv/bin/python .gitea/checker/main.py \
--config .gitea/config.yaml \
--repo-root . \
"${{ gitea.actor }}" \

View File

@@ -2,7 +2,9 @@
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/jnpf-ftb" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>

17
.idea/sqldialects.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/jnpf-ftb/jnpf-ftb-biz/src/main/resources/ftb_certificate_instance_upgrade_template_status.sql" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/jnpf-ftb/jnpf-ftb-biz/src/main/resources/sql/certificate/ftb_certificate_instance.sql" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/jnpf-ftb/jnpf-ftb-biz/src/main/resources/sql/certificate/ftb_certificate_instance_upgrade_health_certificate_data.sql" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/jnpf-ftb/jnpf-ftb-biz/src/main/resources/sql/franchisee/base_dictionary_franchisee_nature.sql" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/jnpf-ftb/jnpf-ftb-biz/src/main/resources/sql/franchisee/base_dictionary_industry_param.sql" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/jnpf-ftb/jnpf-ftb-biz/src/main/resources/sql/franchisee/ftb_franchisee.sql" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/jnpf-ftb/jnpf-ftb-biz/src/main/resources/sql/franchisee/ftb_franchisee_batch_insert_5000_with_region.sql" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/jnpf-ftb/jnpf-ftb-biz/src/main/resources/sql/ftb_certificate_instance_upgrade_business_license_from_base_organize.sql" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/jnpf-ftb/jnpf-ftb-biz/src/main/resources/sql/ftb_certificate_instance_upgrade_org_store_missing.sql" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/jnpf-ftb/jnpf-ftb-biz/src/main/resources/sql/ftb_store_add_store_type_franchisee.sql" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/jnpf-ftb/jnpf-ftb-biz/src/main/resources/sql/storecertificatephoto/base_param.sql" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/jnpf-ftb/jnpf-ftb-biz/src/main/resources/sql/storecertificatephoto/ftb_store_certificate_photo.sql" dialect="GenericSQL" />
</component>
</project>

View File

@@ -1,2 +0,0 @@
# 示例 Spring Controller用于本地测试 AST 解析
# 将此目录结构复制到你的 Java 项目中进行验证

View File

@@ -1,44 +0,0 @@
package com.example.controller;
import org.springframework.web.bind.annotation.*;
/**
* 示例 UserController用于测试 API 变更检测。
*/
@RestController
@RequestMapping("/api/users")
public class UserController {
/**
* 查询用户详情 — 含多种参数类型,便于测试增删改检测。
*/
@GetMapping("/{id}")
public String getUser(@PathVariable("id") String id, @RequestParam(value = "test", required = false, defaultValue = "false") Boolean includeDisabled) {
return "ok";
}
/**
* 新增用户
*/
@PostMapping("/createUser")
public String createUser(@RequestBody UserCreateRequest request) {
return "created";
}
/**
* 更新用户
*/
@PutMapping("/{id}")
public String updateUser(@PathVariable("id") Long id, @RequestBody UserCreateRequest request) {
return "updated";
}
/**
* 删除用户
*/
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable("id") Long id) {
return "deleted";
}
}

View File

@@ -1,26 +0,0 @@
package com.example.controller;
/**
* 示例请求体 DTO测试 @RequestBody 字段展开。
*/
public class UserCreateRequest {
private String userName;
private Boolean userType;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Boolean getUserType() {
return userType;
}
public void setUserType(Boolean userType) {
this.userType = userType;
}
}

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-ftb</artifactId>
<version>3.4.7-RELEASE</version>
</parent>
<artifactId>jnpf-ftb-api</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-ftb-entity</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-duty-entity</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-common-feign</artifactId>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-permission-entity</artifactId>
<version>3.4.7-RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-tenant-entity</artifactId>
<version>3.4.7-RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>9</source>
<target>9</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,72 @@
package jnpf.account;
import jnpf.account.fallback.PTenantAccountApiFallBackFactory;
import jnpf.base.ActionResult;
import jnpf.exception.HandleException;
import jnpf.model.TenantGenerateDefaultAvatarVO;
import jnpf.model.personnels.dto.turnover.SaveTenantUserForm;
import jnpf.model.personnels.req.roster.UserAccountDto;
import jnpf.model.user.GenerateHeadForm;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
@FeignClient(name = "jnpf-tenant", fallbackFactory = PTenantAccountApiFallBackFactory.class, path = "/tenantUser")
public interface PTenantAccountApi {
@PutMapping("/batchAddUserAccount")
ActionResult<List<UserAccountDto>> batchAddUserAccount(@RequestBody List<UserAccountDto> userAccountForms);
@PutMapping("/batchDisabledUserAccount")
ActionResult<List<UserAccountDto>> batchDisabledUserAccount(@RequestBody List<UserAccountDto> userAccountForms);
@GetMapping("/generateDefaultAvatar")
ActionResult generateDefaultAvatar(@RequestParam("name") String name) throws IOException;
/**
* 批量异步生成头像 调用方自己要触发保底
* @param names 用户名
* @return
*/
@PostMapping("/batch/generate/default/avatar")
List<TenantGenerateDefaultAvatarVO> batchGenerateDefaultAvatar(@RequestBody List<String> names);
/**
* 用户状态修改
* @param saveTenantUserForm
* @return
*/
@PutMapping("/updateJobStatus")
ActionResult updateJobStatus(@RequestBody SaveTenantUserForm saveTenantUserForm);
@PutMapping("/userInfoSynchronous")
ActionResult userInfoSynchronous(@RequestBody SaveTenantUserForm saveTenantUserForm);
@DeleteMapping("/deleteUser/{id}")
ActionResult deleteUser(@PathVariable("id") String id);
/**
* 删除用户(同步删除租户用户)
*
* @param userId 用户ID
* @return ActionResult
*/
@DeleteMapping("/deleteUserWithPlatform/{userId}")
Boolean deleteUserWithPlatform(@PathVariable("userId") String userId);
/**
* 批量删除用户
*/
@DeleteMapping("/deleteUsers")
ActionResult<Void> deleteUsers(@RequestBody List<String> userIds) ;
@PostMapping("/generateDefaultAvatarCustomFilename")
ActionResult generateDefaultAvatar(@RequestBody GenerateHeadForm generateHeadForm) throws IOException;
}

View File

@@ -0,0 +1,72 @@
package jnpf.account.fallback;
import jnpf.account.PTenantAccountApi;
import jnpf.base.ActionResult;
import jnpf.exception.HandleException;
import jnpf.model.TenantGenerateDefaultAvatarVO;
import jnpf.model.personnels.dto.turnover.SaveTenantUserForm;
import jnpf.model.personnels.req.roster.UserAccountDto;
import jnpf.model.user.GenerateHeadForm;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.List;
@Slf4j
public class PTenantAccountApiFallBack implements PTenantAccountApi {
@Override
public ActionResult<List<UserAccountDto>> batchAddUserAccount(List<UserAccountDto> userAccountForms) {
log.error("调用批量添加用户接口失败,降级");
return null;
}
@Override
public ActionResult<List<UserAccountDto>> batchDisabledUserAccount(List<UserAccountDto> userAccountForms) {
return null;
}
@Override
public ActionResult generateDefaultAvatar(String name) throws IOException {
log.error("调用生成默认头像接口失败,降级");
return null;
}
@Override
public List<TenantGenerateDefaultAvatarVO> batchGenerateDefaultAvatar(List<String> names) {
log.error("调用生成默认头像接口失败,降级");
return List.of();
}
@Override
public ActionResult updateJobStatus(SaveTenantUserForm saveTenantUserForm) {
return null;
}
@Override
public ActionResult userInfoSynchronous(SaveTenantUserForm saveTenantUserForm) {
log.error("调用同步用户信息接口失败,降级");
return null;
}
@Override
public ActionResult deleteUser(String id) {
return null;
}
@Override
public Boolean deleteUserWithPlatform(String userId) {
log.error("调用删除租户用户接口失败,降级");
return false;
}
@Override
public ActionResult<Void> deleteUsers(List<String> userIds) {
return null;
}
@Override
public ActionResult generateDefaultAvatar(GenerateHeadForm generateHeadForm) throws IOException {
return null;
}
}

View File

@@ -0,0 +1,22 @@
package jnpf.account.fallback;
import feign.FeignException;
import feign.Request;
import jnpf.account.PTenantAccountApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class PTenantAccountApiFallBackFactory implements FallbackFactory<PTenantAccountApi> {
@Override
public PTenantAccountApi create(Throwable cause) {
log.error("服务降级了");
cause.printStackTrace();
log.error(cause.getMessage(), cause);
return new PTenantAccountApiFallBack();
}
}

View File

@@ -0,0 +1,35 @@
package jnpf.attendance;
import jnpf.attendance.fallback.AttendanceApiFallback;
import jnpf.model.attendance.vo.attendance.AttendanceToThousandsFacesVo;
import jnpf.util.NoDataSourceBind;
import org.apache.ibatis.annotations.Param;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* describe
*
* @author HuangLinPan
* @date 2023/11/27
*/
@FeignClient(name = "jnpf-ftb", fallbackFactory = AttendanceApiFallback.class, path = "/attendance")
public interface AttendanceApi {
@PostMapping(value = "/invalidationCoupons")
@NoDataSourceBind
Boolean invalidationCoupons(@RequestParam(value = "tenantId") String tenantId);
// @PostMapping(value = "/overtimeVouchers")
// @NoDataSourceBind
// Boolean overtimeVouchers(@RequestParam(value = "tenantId") String tenantId);
@PostMapping(value = "/storageRest")
Boolean storageRest(@RequestParam("tenantId") String tenantId);
@PostMapping(value = "/attendanceToThousandsFaces")
AttendanceToThousandsFacesVo attendanceToThousandsFaces();
}

View File

@@ -0,0 +1,27 @@
package jnpf.attendance;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.attendance.fallback.AttendanceUserApiFallback;
import jnpf.base.ActionResult;
import jnpf.model.thousandsfaces.TodayWorkVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@FeignClient(name = "jnpf-ftb", fallbackFactory = AttendanceUserApiFallback.class, path = "/attendance/confirm")
public interface AttendanceConfirmApi {
@Operation(summary = "自动生成考勤确认数据")
@GetMapping(value = "/autoConfirm")
ActionResult<Boolean> autoCreateConfirm();
@Operation(summary = "逾期自动确认")
@GetMapping(value = "/confirmAutoSlippage")
ActionResult<Boolean> confirmAutoSlippage();
@Operation(summary = "今日工作-考勤确认列表(0-待确认 1-已确认 2-已逾期)")
@GetMapping(value = "/getTodayWorkConfirmList")
List<TodayWorkVo> getTodayWorkConfirmList();
}

View File

@@ -0,0 +1,63 @@
package jnpf.attendance;
import jnpf.attendance.fallback.AttendanceDailyRuleApiFallback;
import jnpf.base.ActionResult;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Date;
import java.util.List;
/**
* 组队动态
*
* @author JNPF开发平台组
* @version V3.1.0
* @copyright 引迈信息技术有限公司https://www.jnpfsoft.com
* @date 2021-03-24
*/
@FeignClient(name = "jnpf-ftb", fallbackFactory = AttendanceDailyRuleApiFallback.class, path = "/arranging/work")
public interface AttendanceDailyRuleApi {
/**
* 定时执行初始化下个月固定排班
*/
@PostMapping("initFixedScheduleRule")
ActionResult initFixedScheduleRule(@RequestParam(value = "tenantId") String tenantId);
/**
* 定时执行初始化下个月固定排班
*/
@PostMapping("autoGrantBalance")
ActionResult autoGrantBalance(@RequestParam(value = "tenantId") String tenantId);
/**
* 用户是否排班
*
* @param tenantId 租户ID
* @param userId 用户ID
* @return
*/
@NoDataSourceBind
@GetMapping("userIsScheduling")
ActionResult<Boolean> userIsScheduling(@RequestParam("tenantId") String tenantId, @RequestParam("userId") String userId);
@PostMapping("userIsSchedulingOrdinary")
ActionResult<List<String>> userIsSchedulingOrdinary(@RequestBody List<String> organizeIds);
/**
* 判断用户指定时间范围内是否排班
*
* @param userId 用户ID
* @param start 开始时间
* @param end 结束时间
* @return
*/
@GetMapping("hasRuleByUserIdAndTime")
ActionResult<Boolean> hasRuleByUserIdAndTime(@RequestParam("userId") String userId, @RequestParam("start") Date start, @RequestParam("end") Date end);
}

View File

@@ -0,0 +1,59 @@
package jnpf.attendance;
import jnpf.attendance.dto.AttendanceUserGroupVo;
import jnpf.attendance.dto.AttendanceUserListGroupVO;
import jnpf.attendance.fallback.AttendanceGroupApiFallback;
import jnpf.base.ActionResult;
import jnpf.entity.AttendanceGroup;
import jnpf.model.attendance.vo.AttendanceGroupVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* @Title: 考情组api
* @Author: peng.hao
* @create: 2024/4/23:16:09
*/
@FeignClient(name = "jnpf-ftb",fallbackFactory = AttendanceGroupApiFallback.class,path = "/group")
public interface AttendanceGroupApi {
/**
* 获取考勤组名称
* @param groupId 考勤组Id
*
*/
@GetMapping("/queryTheNameOfTheAttendanceGroup")
AttendanceGroup queryTheNameOfTheAttendanceGroup(@RequestParam(value = "groupId") String groupId);
@GetMapping("/attendanceUserGroup")
List<AttendanceUserGroupVo> getAttendanceUserGroup(@RequestParam("userIds") List<String> userIds);
@GetMapping("/getGroupListByOrgId")
List<AttendanceGroup> getGroupListByOrgId(@RequestParam("organizeId") String organizeId);
/**
* 批量获取用户绑定的考勤组 ,包含全名称 xxx/xxx/xxx
* @param userIds 用户ids
*
*/
@PostMapping("/list/user_bound")
ActionResult<List<AttendanceUserListGroupVO>> getAttendanceUserListGroupVO(@RequestBody List<String> userIds);
/**
* 详情---获取一个考勤组
* @param organizeName 组织名称
* @param groupName 考勤组名称
* @return 一个考勤组
*/
@GetMapping("/info/organize_group/name")
ActionResult<AttendanceGroupVo> getAttendanceGroupByName(@RequestParam("organizeName") String organizeName, @RequestParam("groupName") String groupName);
@GetMapping("/checkScheduling")
boolean checkScheduling();
}

View File

@@ -0,0 +1,25 @@
package jnpf.attendance;
import jnpf.attendance.fallback.AttendanceApiFallback;
import jnpf.model.attendance.vo.attendance.AttendanceToThousandsFacesVo;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* describe
*
* @author HuangLinPan
* @date 2023/11/27
*/
@FeignClient(name = "jnpf-ftb", fallbackFactory = AttendanceApiFallback.class, path = "/arranging/line/scheduling/config")
public interface AttendanceLineSchedulingConfigApi {
@GetMapping(value = "/noticeLineScheduling")
Boolean noticeLineScheduling(@RequestParam("tenantId") String tenantId);
}

View File

@@ -0,0 +1,24 @@
package jnpf.attendance;
import jnpf.attendance.fallback.AttendanceSimulateDataApiFallback;
import jnpf.attendance.fallback.FtbClockInApiFallback;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 打卡api
*
* @author yanwenfu
* @create 2023-12-04
*/
@FeignClient(name = "jnpf-ftb", fallbackFactory = AttendanceSimulateDataApiFallback.class, path = "/attendance/simulateData")
public interface AttendanceSimulateDataApi {
/**
* 打卡
* @return java.lang.Object
*/
@PostMapping(value = "/clockIn")
void clockIn(@RequestParam(value = "tenantId") String tenantId) throws Exception;
}

View File

@@ -0,0 +1,39 @@
package jnpf.attendance;
import jnpf.attendance.dto.GroupUpdateByUserDTO;
import jnpf.attendance.fallback.AttendanceUserApiFallback;
import jnpf.base.ActionResult;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
/**
* describe
*
* @author HuangLinPan
* @date 2023/11/27
*/
@FeignClient(name = "jnpf-ftb", fallbackFactory = AttendanceUserApiFallback.class, path = "/user")
public interface AttendanceUserApi {
/**
* 花名册考勤组变更
*
* @param groupUpdateByUserDTO
* @return
*/
@PostMapping("groupUpdateByPersonnel")
ActionResult groupUpdateByPersonnel(@RequestBody GroupUpdateByUserDTO groupUpdateByUserDTO);
/**
* 借调考勤组变动通知
* @param tenantId
* @return
*/
@NoDataSourceBind
@GetMapping(value = "/userGroupUpdateBySecondNotice")
ActionResult<Boolean> userGroupUpdateBySecondNotice(@RequestParam(value = "tenantId") String tenantId);
}

View File

@@ -0,0 +1,44 @@
package jnpf.attendance;
import jnpf.attendance.fallback.FtbClockInApiFallback;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 打卡api
*
* @author yanwenfu
* @create 2023-12-04
*/
@FeignClient(name = "jnpf-ftb", fallbackFactory = FtbClockInApiFallback.class, path = "/attendance/clockIn")
public interface FtbClockInApi {
@GetMapping(value = "/daily-rule-change/execute")
Boolean dailyRuleChangeExecute();
@PostMapping(value = "/absenceRecord")
@NoDataSourceBind
Boolean generateFtbAbsenceRecord(@RequestParam(value = "tenantId") String tenantId);
@PostMapping(value = "/absenceTask")
@NoDataSourceBind
Boolean generateAbsenceTask(@RequestParam(value = "tenantId") String tenantId);
/**
* 生成打卡提醒记录
* @return java.lang.Boolean
*/
@PostMapping(value = "/remindRecord")
@NoDataSourceBind
Boolean generateBeforeWorkRemind(@RequestParam(value = "tenantId") String tenantId);
@PostMapping(value = "/repairNum")
@NoDataSourceBind
Boolean generateRepairNum(@RequestParam(value = "tenantId") String tenantId);
@PostMapping(value = "/continuousCheck")
Boolean continuousCheck(@RequestParam(value = "tenantId") String tenantId);
}

View File

@@ -0,0 +1,108 @@
package jnpf.attendance;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.attendance.dto.*;
import jnpf.attendance.fallback.FtbStatisticsApiFallback;
import jnpf.base.ActionResult;
import jnpf.model.attendance.dto.DayStatisticsDto;
import jnpf.model.attendance.dto.SalaryAttendanceSupportDto;
import jnpf.model.attendance.vo.attendance.AttendanceCustomizeTableVo;
import jnpf.model.attendance.vo.attendance.DayStatisticsPageListVo;
import jnpf.model.attendance.vo.attendance.DayStatisticsVo;
import jnpf.model.attendance.vo.attendance.SalaryAttendanceSupportVo;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Date;
import java.util.List;
import java.util.Map;
@FeignClient(name = "jnpf-ftb", fallbackFactory = FtbStatisticsApiFallback.class, path = "/attendance/statistics")
public interface FtbStatisticsApi {
@Operation(summary = "用户日统计数据初始化")
@GetMapping(value = "/userDayStatisticsInit")
ActionResult<Boolean> userDayStatisticsInit(@RequestParam(value = "tenantId") String tenantId);
@Operation(summary = "个人考勤日报通知")
@GetMapping(value = "/dayStatisticsNotice")
ActionResult<Boolean> dayStatisticsNotice(@RequestParam(value = "tenantId") String tenantId);
@NoDataSourceBind
@Operation(summary = "个人统计月报通知")
@GetMapping(value = "/monthStatisticsNotice")
ActionResult<Boolean> monthStatisticsNotice(@RequestParam(value = "tenantId") String tenantId);
@NoDataSourceBind
@Operation(summary = "团队统计月报通知")
@GetMapping(value = "/teamMonthStatisticsNotice")
ActionResult<Boolean> teamMonthStatisticsNotice(@RequestParam(value = "tenantId") String tenantId);
@Operation(summary = "连续未排班通知")
@GetMapping(value = "/consentUnscheduledNotice")
void consentUnscheduledNotice(@RequestParam(value = "tenantId") String tenantId);
@Operation(summary = "考勤封账-自动封账定时器")
@PutMapping(value = "/autoSealTimer")
ActionResult<Boolean> autoSealTimer(@RequestParam(value = "tenantId") String tenantId);
@Operation(summary = "计算考勤组平均工时(实际出勤工时)")
@PostMapping(value = "/countAttendanceAvgHours")
List<AttendanceCountAvgHoursVo> countAttendanceAvgHours(@Valid @RequestBody AttendanceCountAvgHoursDto dto);
@Operation(summary = "获取多考勤组月度统计数据")
@PostMapping(value = "/getAttendanceAvgHoursDetails")
ActionResult<MonthStatsDetailsVo> getAttendanceAvgHoursDetails(@Valid @RequestBody MonthStatsDetailsDto dto);
@Operation(summary = "获取多考勤组月度人均工时折线图")
@PostMapping(value = "/getAttendanceMonthPerCapita")
ActionResult<List<MonthStatsPerCapitaVo>> getAttendanceMonthPerCapita(@Valid @RequestBody MonthStatsDetailsDto dto);
@Operation(summary = "获取多考勤组月度日常情况")
@PostMapping(value = "/getAttendanceDailySituation")
ActionResult<List<MonthStatsDailySituationVo>> getAttendanceDailySituation(@Valid @RequestBody MonthStatsDetailsDto dto);
@Operation(summary = "获取多考勤组月度考勤工时排行")
@PostMapping(value = "/getAttendanceHoursRanking")
ActionResult<List<MonthStatsHoursRankingVo>> getAttendanceHoursRanking(@Valid @RequestBody MonthStatsDetailsDto dto);
@Operation(summary = "获取多考勤组月度全勤情况")
@PostMapping(value = "/getAttendanceFullSituation")
ActionResult<List<MonthStatsFullSituationVo>> getAttendanceFullSituation(@Valid @RequestBody MonthStatsDetailsDto dto);
@Operation(summary = "获取多考勤组月度异常情况")
@PostMapping(value = "/getAttendanceAbnormalCondition")
ActionResult<MonthStatsAbnormalConditionVo> getAttendanceAbnormalCondition(@Valid @RequestBody MonthStatsDetailsDto dto);
@Operation(summary = "获取多考勤组月度加班情况")
@PostMapping(value = "/getAttendanceOvertimeSituation")
ActionResult<List<MonthStatsOvertimeSituationVo>> getAttendanceOvertimeSituation(@Valid @RequestBody MonthStatsDetailsDto dto);
@Operation(summary = "薪酬考勤数据支持")
@PostMapping(value = "/salaryAttendanceSupport")
Map<String, SalaryAttendanceSupportVo> salaryAttendanceSupport(@Valid @RequestBody SalaryAttendanceSupportDto dto);
@Operation(summary = "考勤统计数据日度列表表头(薪酬)")
@PostMapping(value = "/attendanceDayStaTable")
List<AttendanceCustomizeTableVo> attendanceDayStaTable();
@Operation(summary = "考勤统计数据日度列表")
@PostMapping(value = "/attendanceDayStaList")
List<DayStatisticsPageListVo> attendanceDayStaList(@Valid @RequestBody SalaryAttendanceSupportDto dto);
@Operation(summary = "获取日出勤信息")
@NoDataSourceBind
@PostMapping(value = "/getAttendanceDayStaList")
List<DayStatisticsVo> getAttendanceDayStaList(@Valid @RequestBody DayStatisticsDto dto);
@Operation(summary = "获取各维度出勤人数")
@PostMapping(value = "/getDimensionsAttendanceCountMap")
Map<String, List<DateDimensionsRangeVo>> getDimensionsAttendanceCountMap(@Valid @RequestBody DimensionsAttendanceCountDto dto);
@Operation(summary = "获取各维度出勤人数")
@PostMapping(value = "/getDimensionsAttendanceDayCountMap")
Map<String, Map<Date, Integer>> getDimensionsAttendanceDayCountMap(@Valid @RequestBody DimensionsAttendanceDayCountDto dto);
}

View File

@@ -0,0 +1,32 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AttendanceCountAvgHoursDto {
/**
* 组织ID集合
*/
@NotEmpty(message = "组织ID集合不能为空")
private List<String> orgIds;
/**
* 开始月份(yyyyMM)
*/
@NotBlank(message = "开始月份不能为空")
private String startMonth;
/**
* 结束月份(yyyyMM)
*/
@NotBlank(message = "结束月份不能为空")
private String endMonth;
}

View File

@@ -0,0 +1,27 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AttendanceCountAvgHoursVo {
/**
* 月份
*/
@NotBlank(message = "月份不能为空")
private String month;
/**
* 平均值
*/
@NotNull(message = "平均值不能为空")
private BigDecimal avgHours = BigDecimal.ZERO;
}

View File

@@ -0,0 +1,24 @@
package jnpf.attendance.dto;
import lombok.Data;
/**
* @Author huanglinpan
* @Date 2024/4/23 17:31
* @Version 1.0 (版本号)
*/
@Data
public class AttendanceUserGroupVo {
/**
* 考勤组Id
*/
private String groupId;
/**
* 考勤组名称
*/
private String groupName;
/**
* 用户Id
*/
private String userId;
}

View File

@@ -0,0 +1,34 @@
package jnpf.attendance.dto;
import lombok.Data;
import java.io.Serializable;
/**
* todo
*
* @author Flynn Chan
* @create 2024-05-17
*/
@Data
public class AttendanceUserListGroupVO implements Serializable {
/**
* 考勤组id
*/
private String groupId;
/**
* 考勤组名称
*/
private String groupName;
/**
* 考勤组名称
*/
private String groupFullName;
/**
* 用户Id
*/
private String userId;
}

View File

@@ -0,0 +1,30 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 查询维度枚举
*
* @author Flynn Chan
* @create 2025-02-13
*/
@AllArgsConstructor
@Getter
public enum CompareTypeEnums {
CURRENT(1, "本期"),
YOY(2, "同比"),
MOM(3, "环比");
private final int value;
private final String text;
public static CompareTypeEnums getTypeEnums(int value) {
CompareTypeEnums[] values = CompareTypeEnums.values();
for (CompareTypeEnums typeEnums : values) {
if (typeEnums.getValue() == value) {
return typeEnums;
}
}
return null;
}
}

View File

@@ -0,0 +1,31 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.util.Date;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DateDimensionsRangeDto {
/**
* 查询维度枚举
*/
@NotNull(message = "查询维度不能为空")
private CompareTypeEnums typeEnums;
/**
* 开始时间
*/
@NotNull(message = "开始时间不能为空")
private Date startDate;
/**
* 结束时间
*/
@NotNull(message = "结束时间不能为空")
private Date endDate;
}

View File

@@ -0,0 +1,24 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DateDimensionsRangeVo {
/**
* 查询维度枚举
*/
@NotNull(message = "查询维度不能为空")
private CompareTypeEnums typeEnums;
/**
* 出勤人数
*/
private Integer count;
}

View File

@@ -0,0 +1,26 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DimensionsAttendanceCountDto {
/**
* 系统门店ID集合
*/
@NotEmpty(message = "系统门店ID集合不能为空")
private List<String> storeIds;
/**
* 时间维度集合
*/
@NotEmpty(message = "时间维度集合不能为空")
private List<DateDimensionsRangeDto> dimensionsRangeList;
}

View File

@@ -0,0 +1,33 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DimensionsAttendanceDayCountDto {
/**
* 系统门店ID集合
*/
@NotEmpty(message = "系统门店ID集合不能为空")
private List<String> storeIds;
/**
* 开始时间
*/
@NotNull(message = "开始时间不能为空")
private Date startDate;
/**
* 结束时间
*/
@NotNull(message = "结束时间不能为空")
private Date endDate;
}

View File

@@ -0,0 +1,28 @@
package jnpf.attendance.dto;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
public class GroupUpdateByUserDTO {
/**
* 1入职 2离职 3调岗 4晋升 5批量删除
*/
private Integer type;
/**
* 目标考勤组id
*/
private String toGroupId;
/**
* 用户id集合
*/
private List<String> userIds;
private String tenantId;
/**
* 实际入职日期,yyyy-MM-dd
*/
private Date joiningDate;
}

View File

@@ -0,0 +1,55 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MonthStatsAbnormalConditionVo {
/**
* 正常次数
*/
private Integer normalCount = 0;
/**
* 正常次数占比
*/
private BigDecimal normalPercent = BigDecimal.ZERO;
/**
* 迟到次数
*/
private Integer lateCount = 0;
/**
* 迟到次数占比
*/
private BigDecimal latePercent = BigDecimal.ZERO;
/**
* 早退次数
*/
private Integer earlyCount = 0;
/**
* 早退次数占比
*/
private BigDecimal earlyPercent = BigDecimal.ZERO;
/**
* 缺卡次数
*/
private Integer absenceCardCount = 0;
/**
* 缺卡次数占比
*/
private BigDecimal absenceCardPercent = BigDecimal.ZERO;
/**
* 旷工次数
*/
private Integer absenceCount = 0;
/**
* 旷工次数占比
*/
private BigDecimal absencePercent = BigDecimal.ZERO;
}

View File

@@ -0,0 +1,23 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MonthStatsDailySituationVo {
/**
* 出勤类型
*/
private String typeName;
/**
* 工时(天)
*/
private BigDecimal avgDays;
}

View File

@@ -0,0 +1,27 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MonthStatsDetailsDto {
/**
* 考勤组ID集合
*/
@NotEmpty(message = "考勤组ID集合不能为空")
private List<String> groupIds;
/**
* 月份(yyyyMM)
*/
@NotBlank(message = "开始月份不能为空")
private String month;
}

View File

@@ -0,0 +1,63 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MonthStatsDetailsVo {
/**
* 人均工时
*/
private BigDecimal avgHours = BigDecimal.ZERO;
/**
* 人均工时同比增长(正负表示增减,为空表示没有可对比性展示--)
*/
private BigDecimal avgHoursOnYear;
/**
* 人均工时环比增长(正负表示增减,为空表示没有可对比性展示--)
*/
private BigDecimal avgHoursOnMonth;
/**
* 人均加班工时
*/
private BigDecimal avgOverTimeHours = BigDecimal.ZERO;
/**
* 人均请假天数
*/
private BigDecimal avgLeaveDays = BigDecimal.ZERO;
/**
* 人均请假天数同比增长(正负表示增减,为空表示没有可对比性展示--)
*/
private BigDecimal avgLeaveDaysOnYear;
/**
* 人均请假天数环比增长(正负表示增减,为空表示没有可对比性展示--)
*/
private BigDecimal avgLeaveDaysMonth;
/**
* 人均旷工天数
*/
private BigDecimal avgAbsentDays = BigDecimal.ZERO;
/**
* 迟到人数
*/
private BigDecimal lateCount = BigDecimal.ZERO;
/**
* 迟到人数同比增长(正负表示增减,为空表示没有可对比性展示--)
*/
private BigDecimal lateCountOnYear;
/**
* 迟到人数环比增长(正负表示增减,为空表示没有可对比性展示--)
*/
private BigDecimal lateCountOnMonth;
/**
* 早退人数
*/
private BigDecimal earlyCount = BigDecimal.ZERO;
}

View File

@@ -0,0 +1,31 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MonthStatsFullSituationVo {
/**
* 日期
*/
private String day;
/**
* 考勤组人数
*/
private Integer groupUserCount;
/**
* 全勤人数
*/
private Integer fullCount;
/**
* 全勤占比
*/
private BigDecimal fullRatio;
}

View File

@@ -0,0 +1,23 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MonthStatsHoursRankingVo {
/**
* 用户名
*/
private String userName;
/**
* 工时(小时)
*/
private BigDecimal hours;
}

View File

@@ -0,0 +1,32 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MonthStatsOvertimeSituationVo {
/**
* 日期(MM.dd)
*/
private String day;
/**
* 加班工时(小时)
*/
private BigDecimal overtimeHours;
/**
* 加班工时同比增长(正负表示增减,为空表示没有可对比性展示--)
*/
private BigDecimal overtimeHoursOnYear;
/**
* 加班工时环比增长(正负表示增减,为空表示没有可对比性展示--)
*/
private BigDecimal overtimeHoursOnMonth;
}

View File

@@ -0,0 +1,23 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MonthStatsPerCapitaVo {
/**
* 日期(MM.dd)
*/
private String day;
/**
* 人均工时(小时)
*/
private BigDecimal avgHours;
}

View File

@@ -0,0 +1,27 @@
package jnpf.attendance.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MonthStatsSituationVo {
/**
* 日期MM.dd
*/
private String day;
/**
* 总人数
*/
private Integer totalCount;
/**
* 全勤人数
*/
private Integer onTimeCount;
}

View File

@@ -0,0 +1,51 @@
package jnpf.attendance.fallback;
import jnpf.attendance.AttendanceApi;
import jnpf.model.attendance.vo.attendance.AttendanceToThousandsFacesVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* describe
*
* @author HuangLinPan
* @date 2023/11/27
*/
@Slf4j
@Component
public class AttendanceApiFallback implements FallbackFactory<AttendanceApi> {
@Override
public AttendanceApi create(Throwable cause) {
cause.printStackTrace();
return new AttendanceApi() {
@Override
public Boolean invalidationCoupons(String tenantId) {
log.error("定时失效考勤劵失败...");
return Boolean.FALSE;
}
// @Override
// public Boolean overtimeVouchers(String tenantId) {
// log.error("定时发放加班劵失败...");
// return null;
// }
@Override
public Boolean storageRest(String tenantId) {
log.error("每月定时计算用户存休失败...");
return null;
}
@Override
public AttendanceToThousandsFacesVo attendanceToThousandsFaces() {
log.error("每月定时计算用户存休失败...");
return null;
}
};
}
}

View File

@@ -0,0 +1,37 @@
package jnpf.attendance.fallback;
import jnpf.attendance.AttendanceConfirmApi;
import jnpf.base.ActionResult;
import jnpf.model.thousandsfaces.TodayWorkVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
public class AttendanceConfirmApiFallback implements FallbackFactory<AttendanceConfirmApi> {
@Override
public AttendanceConfirmApi create(Throwable cause) {
cause.printStackTrace();
return new AttendanceConfirmApi() {
@Override
public ActionResult<Boolean> autoCreateConfirm() {
return null;
}
@Override
public ActionResult<Boolean> confirmAutoSlippage() {
return null;
}
@Override
public List<TodayWorkVo> getTodayWorkConfirmList() {
return List.of();
}
};
}
}

View File

@@ -0,0 +1,53 @@
package jnpf.attendance.fallback;
import jnpf.attendance.AttendanceDailyRuleApi;
import jnpf.base.ActionResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
/**
* 获取用户信息Api降级处理
*
* @author JNPF开发平台组
* @version V3.1.0
* @copyright 引迈信息技术有限公司https://www.jnpfsoft.com
* @date 2021-03-24
*/
@Component
@Slf4j
public class AttendanceDailyRuleApiFallback implements FallbackFactory<AttendanceDailyRuleApi> {
@Override
public AttendanceDailyRuleApi create(Throwable cause) {
cause.printStackTrace();
return new AttendanceDailyRuleApi() {
@Override
public ActionResult initFixedScheduleRule(String tenantId) {
return ActionResult.fail("请求失败");
}
@Override
public ActionResult autoGrantBalance(String tenantId) {
return ActionResult.fail("请求失败");
}
@Override
public ActionResult<Boolean> userIsScheduling(String tenantId, String userId) {
return ActionResult.fail("请求失败");
}
@Override
public ActionResult<List<String>> userIsSchedulingOrdinary(List<String> organizeId) {
return ActionResult.fail("请求失败");
}
@Override
public ActionResult<Boolean> hasRuleByUserIdAndTime(String userId, Date start, Date end) {
return ActionResult.fail("请求失败");
}
};
}
}

View File

@@ -0,0 +1,62 @@
package jnpf.attendance.fallback;
import jnpf.attendance.AttendanceGroupApi;
import jnpf.attendance.dto.AttendanceUserGroupVo;
import jnpf.attendance.dto.AttendanceUserListGroupVO;
import jnpf.base.ActionResult;
import jnpf.entity.AttendanceGroup;
import jnpf.model.attendance.vo.AttendanceGroupVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @Title:
* @Author: peng.hao
* @create: 2024/4/23:16:09
*/
@Slf4j
@Component
public class AttendanceGroupApiFallback implements FallbackFactory<AttendanceGroupApi> {
@Override
public AttendanceGroupApi create(Throwable cause) {
cause.printStackTrace();
return new AttendanceGroupApi() {
@Override
public AttendanceGroup queryTheNameOfTheAttendanceGroup(String groupId) {
return new AttendanceGroup();
}
@Override
public List<AttendanceUserGroupVo> getAttendanceUserGroup(List<String> userIds) {
log.error("AttendanceGroupApiFallback调用getAttendanceUserGroup失败");
return new ArrayList<>();
}
@Override
public List<AttendanceGroup> getGroupListByOrgId(String organizeId) {
return null;
}
@Override
public ActionResult<List<AttendanceUserListGroupVO>> getAttendanceUserListGroupVO(List<String> userIds) {
return null;
}
@Override
public ActionResult<AttendanceGroupVo> getAttendanceGroupByName(String organizeName, String groupName) {
return null;
}
@Override
public boolean checkScheduling() {
return false;
}
};
}
}

View File

@@ -0,0 +1,31 @@
package jnpf.attendance.fallback;
import jnpf.attendance.AttendanceApi;
import jnpf.attendance.AttendanceLineSchedulingConfigApi;
import jnpf.model.attendance.vo.attendance.AttendanceToThousandsFacesVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* describe
*
* @author HuangLinPan
* @date 2023/11/27
*/
@Slf4j
@Component
public class AttendanceLineSchedulingConfigApiFallback implements FallbackFactory<AttendanceLineSchedulingConfigApi> {
@Override
public AttendanceLineSchedulingConfigApi create(Throwable cause) {
cause.printStackTrace();
return new AttendanceLineSchedulingConfigApi() {
@Override
public Boolean noticeLineScheduling(String tenantId) {
return null;
}
};
}
}

View File

@@ -0,0 +1,30 @@
package jnpf.attendance.fallback;
import jnpf.attendance.AttendanceSimulateDataApi;
import jnpf.attendance.FtbClockInApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* 打卡api fallback
*
* @author yanwenfu
* @create 2023-12-04
*/
@Slf4j
@Component
public class AttendanceSimulateDataApiFallback implements FallbackFactory<AttendanceSimulateDataApi> {
@Override
public AttendanceSimulateDataApi create(Throwable cause) {
cause.printStackTrace();
return new AttendanceSimulateDataApi() {
@Override
public void clockIn(String tenantId) throws Exception {
log.error("调用模拟打卡失败...");
}
};
}
}

View File

@@ -0,0 +1,38 @@
package jnpf.attendance.fallback;
import jnpf.attendance.AttendanceUserApi;
import jnpf.attendance.dto.GroupUpdateByUserDTO;
import jnpf.base.ActionResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* describe
*
* @author HuangLinPan
* @date 2023/11/27
*/
@Slf4j
@Component
public class AttendanceUserApiFallback implements FallbackFactory<AttendanceUserApi> {
@Override
public AttendanceUserApi create(Throwable cause) {
cause.printStackTrace();
return new AttendanceUserApi() {
@Override
public ActionResult groupUpdateByPersonnel(GroupUpdateByUserDTO groupUpdateByUserDTO) {
log.error("定时失效考勤劵失败...");
return null;
}
@Override
public ActionResult<Boolean> userGroupUpdateBySecondNotice(String tenantId) {
return null;
}
};
}
}

View File

@@ -0,0 +1,60 @@
package jnpf.attendance.fallback;
import jnpf.attendance.FtbClockInApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* 打卡api fallback
*
* @author yanwenfu
* @create 2023-12-04
*/
@Slf4j
@Component
public class FtbClockInApiFallback implements FallbackFactory<FtbClockInApi> {
@Override
public FtbClockInApi create(Throwable cause) {
cause.printStackTrace();
return new FtbClockInApi() {
@Override
public Boolean dailyRuleChangeExecute() {
log.error("dailyRuleChangeExecute调用失败...");
return Boolean.FALSE;
}
@Override
public Boolean generateFtbAbsenceRecord(String tenantId) {
log.error("generateFtbAbsenceRecord调用失败...");
return Boolean.FALSE;
}
@Override
public Boolean generateAbsenceTask(String tenantId) {
log.error("generateAbsenceTask调用失败...");
return Boolean.FALSE;
}
@Override
public Boolean generateBeforeWorkRemind(String tenantId) {
log.error("generateBeforeWorkRemind调用失败...");
return false;
}
@Override
public Boolean generateRepairNum(String tenantId) {
log.error("生成补卡次数失败...");
return Boolean.FALSE;
}
@Override
public Boolean continuousCheck(String tenantId) {
log.error("判定连续动作失败...");
return Boolean.FALSE;
}
};
}
}

View File

@@ -0,0 +1,129 @@
package jnpf.attendance.fallback;
import jnpf.attendance.FtbStatisticsApi;
import jnpf.attendance.dto.*;
import jnpf.base.ActionResult;
import jnpf.model.attendance.dto.DayStatisticsDto;
import jnpf.model.attendance.dto.SalaryAttendanceSupportDto;
import jnpf.model.attendance.vo.attendance.AttendanceCustomizeTableVo;
import jnpf.model.attendance.vo.attendance.DayStatisticsPageListVo;
import jnpf.model.attendance.vo.attendance.DayStatisticsVo;
import jnpf.model.attendance.vo.attendance.SalaryAttendanceSupportVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Slf4j
@Component
public class FtbStatisticsApiFallback implements FallbackFactory<FtbStatisticsApi> {
@Override
public FtbStatisticsApi create(Throwable cause) {
// 打印降级原因
log.error("进入 FtbStatisticsApi 的 fallback降级原因", cause);
return new FtbStatisticsApi() {
@Override
public ActionResult<Boolean> userDayStatisticsInit(String tenantId) {
return ActionResult.success(Boolean.FALSE);
}
@Override
public ActionResult<Boolean> dayStatisticsNotice(String tenantId) {
return ActionResult.success(Boolean.FALSE);
}
@Override
public ActionResult<Boolean> monthStatisticsNotice(String tenantId) {
return ActionResult.success(Boolean.FALSE);
}
@Override
public ActionResult<Boolean> teamMonthStatisticsNotice(String tenantId) {
return ActionResult.success(Boolean.FALSE);
}
@Override
public void consentUnscheduledNotice(String tenantId) {
}
@Override
public ActionResult<Boolean> autoSealTimer(String tenantId) {
return ActionResult.success(Boolean.FALSE);
}
@Override
public List<AttendanceCountAvgHoursVo> countAttendanceAvgHours(AttendanceCountAvgHoursDto dto) {
return List.of();
}
@Override
public ActionResult<MonthStatsDetailsVo> getAttendanceAvgHoursDetails(MonthStatsDetailsDto dto) {
return ActionResult.success(null);
}
@Override
public ActionResult<List<MonthStatsPerCapitaVo>> getAttendanceMonthPerCapita(MonthStatsDetailsDto dto) {
return ActionResult.success(List.of());
}
@Override
public ActionResult<List<MonthStatsDailySituationVo>> getAttendanceDailySituation(MonthStatsDetailsDto dto) {
return ActionResult.success(List.of());
}
@Override
public ActionResult<List<MonthStatsHoursRankingVo>> getAttendanceHoursRanking(MonthStatsDetailsDto dto) {
return ActionResult.success(List.of());
}
@Override
public ActionResult<List<MonthStatsFullSituationVo>> getAttendanceFullSituation(MonthStatsDetailsDto dto) {
return ActionResult.success(List.of());
}
@Override
public ActionResult<MonthStatsAbnormalConditionVo> getAttendanceAbnormalCondition(MonthStatsDetailsDto dto) {
return ActionResult.success(null);
}
@Override
public ActionResult<List<MonthStatsOvertimeSituationVo>> getAttendanceOvertimeSituation(MonthStatsDetailsDto dto) {
return ActionResult.success(List.of());
}
@Override
public Map<String, SalaryAttendanceSupportVo> salaryAttendanceSupport(SalaryAttendanceSupportDto dto) {
return Map.of();
}
@Override
public List<AttendanceCustomizeTableVo> attendanceDayStaTable() {
return List.of();
}
@Override
public List<DayStatisticsPageListVo> attendanceDayStaList(SalaryAttendanceSupportDto dto) {
return List.of();
}
@Override
public List<DayStatisticsVo> getAttendanceDayStaList(DayStatisticsDto dto) {
return List.of();
}
@Override
public Map<String, List<DateDimensionsRangeVo>> getDimensionsAttendanceCountMap(DimensionsAttendanceCountDto dto) {
return Map.of();
}
@Override
public Map<String, Map<Date, Integer>> getDimensionsAttendanceDayCountMap(DimensionsAttendanceDayCountDto dto) {
return Map.of();
}
};
}
}

View File

@@ -0,0 +1,226 @@
package jnpf.authority;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.authority.fallback.FtbAuthorityApiFallback;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.model.authority.dto.menu.FunctionMenuRemoteDTO;
import jnpf.model.authority.dto.menu.FuntionMenuDTO;
import jnpf.model.authority.dto.permission.OrganizeWithPositionsDTO;
import jnpf.model.authority.dto.role.FtbPermissionRoleInfoDTO;
import jnpf.model.authority.vo.person.FtbRoleListDropDownVO;
import jnpf.model.authority.vo.role.FtbPermissionRoleIdentificationVO;
import jnpf.model.authority.vo.role.FtbPermissionRoleInfoVO;
import jnpf.model.cultivate.CultivatePage;
import jnpf.model.cultivate.vo.common.InnerPowerPositionVO;
import jnpf.model.cultivate.vo.common.InnerPowerUserVO;
import jnpf.model.login.MenuTreeVO;
import jnpf.permission.dto.v2.user.QueryPageUserDTO;
import jnpf.permission.dto.v2.user.QueryPageUserMoreKeywordDTO;
import jnpf.permission.eum.v2.OrganizeCategoryEnums;
import jnpf.permission.eum.v2.UserWorkStatusEnums;
import jnpf.permission.vo.organize.OrganizeAndPositionListVO;
import jnpf.permission.vo.store.StoreBaseListInfo;
import jnpf.permission.vo.v2.TargetAuthIdsVO;
import jnpf.permission.vo.v2.organzie.OrganizeGeneralDetailVO;
import jnpf.permission.vo.v2.organzie.OrganizeManagerFilterNodeVO;
import jnpf.permission.vo.v2.organzie.OrganizeManagerNodeVO;
import jnpf.permission.vo.v2.position.PositionListUserVO;
import jnpf.permission.vo.v2.user.UserBoundVO;
import jnpf.util.NoDataSourceBind;
import lombok.SneakyThrows;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* @Author: peng.hao
* @create: 2025/3/27
*/
@FeignClient(name = "jnpf-ftb", fallbackFactory = FtbAuthorityApiFallback.class)
public interface FtbAuthorityApi {
/**
* 获取当前人员的数据权限范围
*
* @return
*/
@GetMapping("/web/permission-role/query-user-scope-permission")
Map<String, String> queryUserScopeOfPermission(@RequestParam("userId") String userId);
@Operation(summary = "[门店基础信息列表auth api] 根据登录人权限得到权限范围内门店基础信息")
@GetMapping(value = "/permission/organize/list/store/login")
List<StoreBaseListInfo> authStoreBaseListInfo();
@Operation(summary = "[列表] 指定组织下得所有员工信息(FTB带权限)")
@GetMapping("/permission/users/list/targetOrganize/auth/api")
List<UserBoundVO> listTargetOrganizeIdAuthApi(@RequestParam(value = "organizeId") String organizeId, @RequestParam(value = "userWorkStatusEnumsList", required = false) List<UserWorkStatusEnums> userWorkStatusEnumsList);
@Operation(summary = "[列表] 指定组织集合下得所有员工信息(FTB带权限)")
@PostMapping("/permission/users/list/targetOrganizeIds/auth/api")
List<UserBoundVO> listTargetOrganizeIdsAuthApi(@RequestBody List<String> organizeIds, @RequestParam(value = "userWorkStatusEnumsList", required = false) List<UserWorkStatusEnums> userWorkStatusEnumsList);
@Operation(summary = "[分页] 在职人员列表")
@PostMapping("/permission/users/page")
@NoDataSourceBind
ActionResult<PageListVO<UserBoundVO>> pagePost(@RequestBody QueryPageUserDTO dto);
@Operation(summary = "[分页] 在职人员列表POST更多关键字信息")
@PostMapping("/permission/page/moreKeyword")
@NoDataSourceBind
ActionResult<PageListVO<UserBoundVO>> pagePostMoreKeyword(@RequestBody QueryPageUserMoreKeywordDTO dto);
@Operation(summary = "[列表]人员权限过滤后的岗位用户列表")
@GetMapping("/permission/position/tree/users")
ActionResult<List<PositionListUserVO>> authListPositionTreeUser();
@Operation(summary = "[api列表]权限范围内的多个组织及其岗位列表(不查人)")
@PostMapping("/permission/position/api/list/auth/organize-with-positions")
List<OrganizeAndPositionListVO> authOrganizeWithPositions(@RequestBody OrganizeWithPositionsDTO dto);
@Operation(summary = "[列表]权限过滤,获取所有用户信息,含绑定关系,或指定用户. 不使用权限则返回全部")
@PostMapping("/permission/users/info/all/auth")
@NoDataSourceBind
ActionResult<List<UserBoundVO>> authGetAllUserInfoBatch();
@Operation(summary = "[列表]权限范围内仅返回用户 id 列表,不查关系与 VO")
@PostMapping("/permission/users/ids/auth")
@NoDataSourceBind
ActionResult<List<String>> authGetPermissionScopeUserIds();
@Operation(summary = "[树形]人员权限过滤后的组织(公司,部门,门店,班主),人员")
@GetMapping(value = "/permission/organize/tree/users")
ActionResult<List<OrganizeManagerNodeVO>> listOrganizeTreeUsers();
@Operation(summary = "[列表]人员权限过滤后的组织列表")
@GetMapping(value = "/permission/organize/info/list/users")
@NoDataSourceBind
ActionResult<List<OrganizeGeneralDetailVO>> authOrganizesByUserBound(@RequestParam(value = "organizeCategoryEnums", required = false) List<OrganizeCategoryEnums> organizeCategoryEnums);
/**
* @param organizeCategoryEnums 组织类型
* @param workStatusEnums 工作状态
* @param withEmployee 是否包含人员
* @param filterBindOtherStore 过滤绑定第三方信息门店
* @param filterBindPayStore 过滤收银平台门店
* @return 组织树(with auth)
*/
@Operation(summary = "[api树形,滤掉没有的分支]人员权限过滤后的组织(公司,部门,门店,班主),默认不带人员")
@GetMapping(value = "/permission/organize/api/tree/users/filterNode")
List<OrganizeManagerFilterNodeVO> listOrganizeTreeFilterNode(
@RequestParam(value = "organizeCategoryEnums", required = false) List<OrganizeCategoryEnums> organizeCategoryEnums,
@RequestParam(value = "workStatusEnums", required = false) List<UserWorkStatusEnums> workStatusEnums,
@RequestParam(value = "withEmployee", required = false) Boolean withEmployee,
@RequestParam(value = "filterBindOtherStore", required = false) Boolean filterBindOtherStore,
@RequestParam(value = "filterBindPayStore", required = false) Boolean filterBindPayStore
);
@Operation(summary = "角色列表下拉")
@GetMapping(value = "/web/permission-role-authorize-person/allRolesOfTheCurrentPerson")
@NoDataSourceBind
ActionResult<List<FtbRoleListDropDownVO>> roleListDropDown(@RequestParam("userId") String userId, @RequestParam("tenantId") String tenantId);
/**
* 根据模块编码获取当前人员的数据权限范围
*
* @return
*/
@GetMapping("/web/permission-role/query-user-scope-permission-for-module-code")
InnerPowerUserVO queryUserScopeOfPermissionForModuleCode();
/**
* 远程client
* 包含新增修改
*/
@SneakyThrows
@PostMapping("/remoteClient")
@NoDataSourceBind
void remoteClient(FunctionMenuRemoteDTO dto);
@GetMapping("/position/get-curr-login-user-has-permission-position-id")
InnerPowerPositionVO getCurrLoginUserHasPermissionPositionId(@RequestParam(name = "permissionModule", defaultValue = "") String permissionModule);
/**
* 更具岗位id清除所有绑定的角色
*/
@PostMapping("/web/permission-role-authorize-post/clearRoleByPostId")
void clearRoleByPostId(@RequestBody List<String> postIds);
/**
* @param userIds 用户列表
* @param status 状态 1禁用 0启用 -1-所有
* @param tenantId 租户id
* @return
*/
@Operation(summary = "[列表]根据用户列表批量查询权限范围的门店")
@PostMapping(value = "/permission/organize/batchForUserIds/{status}/{moduleId}/{tenantId}")
@NoDataSourceBind
Map<String, List<String>> batchAuthOrganizesForUserIdsAndTenantId(@RequestBody List<String> userIds, @PathVariable("status") Integer status,@PathVariable("moduleId") String moduleId,@PathVariable("tenantId") String tenantId);
/**
* @param userIds 用户列表
* @param status 状态 1禁用 0启用 -1-所有
* @param tenantId 租户id
* @return
*/
@Operation(summary = "[列表]根据用户列表批量查询权限范围的门店")
@PostMapping(value = "/permission/organize/batchAllForUserIds/{status}/{moduleId}/{tenantId}")
@NoDataSourceBind
Map<String, List<String>> batchAuthOrganizesAllForUserIdsAndTenantId(@RequestBody List<String> userIds, @PathVariable("status") Integer status,@PathVariable("moduleId") String moduleId,@PathVariable("tenantId") String tenantId);
/**
* @param userIds 用户列表
* @param status 状态 1禁用 0启用 -1-所有
* @return
*/
@Operation(summary = "[列表]根据用户列表批量查询权限范围的门店")
@PostMapping(value = "/permission/organize/batchForUserIds/{status}")
Map<String, List<String>> batchAuthOrganizesForUserIds(@RequestBody List<String> userIds, @PathVariable("status") Integer status);
/**
* 角色列表
*
* @return {@link ActionResult }<{@link PageListVO }<{@link FtbPermissionRoleInfoVO }>>
*/
@GetMapping("/web/permission-role/role-list")
ActionResult<PageListVO<FtbPermissionRoleInfoVO>> permissionList(@SpringQueryMap CultivatePage cultivatePage, @SpringQueryMap FtbPermissionRoleInfoDTO roleInfoDTO);
/**
* 当前登录用户所有权限用户id
*
* @return 用户id集合
*/
@GetMapping("/web/permission-role-authorize-person/permission-user")
ActionResult<List<String>> permissionUser(@RequestParam("userId") String userId, @RequestParam("permissionModule") String permissionModule, @RequestParam("category") String category);
/**
* 员工已有的功能权限标识集合
*/
@GetMapping("/web/permission-role/permission-identification-collection")
ActionResult<FtbPermissionRoleIdentificationVO> permissionIdentificationCollection();
/**
* 查询那些人员具有当前按钮权限
*/
@PostMapping("/web/permission-role-authorize-person/queryButtonPermission")
List<String> queryButtonPermission(@RequestBody FuntionMenuDTO funtion);
/**
* 根据用户Id获取用户权限的用户Id集合超级管理员不要调用此方法
* @return 用户id->所具有的权限用户id集合
*/
@PostMapping("/web/permission-role-authorize-person/collection-of-user")
Map<String,List<String>> getUserPermissionUserCollection(@RequestBody List<String> userIds);
@GetMapping(value = "/permission/organize/user-auth")
TargetAuthIdsVO getUserAuth();
/**
* 当前登录用户菜单树(与 FTB 登录菜单一致type=Web|App
*/
@GetMapping("/web/permission-role/obtain-menus-on-token")
ActionResult<List<MenuTreeVO>> obtainMenusOnToken(@RequestParam(value = "type", defaultValue = "Web") String type);
}

View File

@@ -0,0 +1,188 @@
package jnpf.authority.fallback;
import jnpf.authority.FtbAuthorityApi;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.model.authority.dto.menu.FunctionMenuRemoteDTO;
import jnpf.model.authority.dto.menu.FuntionMenuDTO;
import jnpf.model.authority.dto.permission.OrganizeWithPositionsDTO;
import jnpf.model.authority.dto.role.FtbPermissionRoleInfoDTO;
import jnpf.model.authority.vo.person.FtbRoleListDropDownVO;
import jnpf.model.authority.vo.role.FtbPermissionRoleIdentificationVO;
import jnpf.model.authority.vo.role.FtbPermissionRoleInfoVO;
import jnpf.model.cultivate.CultivatePage;
import jnpf.model.cultivate.vo.common.InnerPowerPositionVO;
import jnpf.model.cultivate.vo.common.InnerPowerUserVO;
import jnpf.model.login.MenuTreeVO;
import jnpf.permission.dto.v2.user.QueryPageUserDTO;
import jnpf.permission.dto.v2.user.QueryPageUserMoreKeywordDTO;
import jnpf.permission.eum.v2.OrganizeCategoryEnums;
import jnpf.permission.eum.v2.UserWorkStatusEnums;
import jnpf.permission.vo.organize.OrganizeAndPositionListVO;
import jnpf.permission.vo.store.StoreBaseListInfo;
import jnpf.permission.vo.v2.TargetAuthIdsVO;
import jnpf.permission.vo.v2.organzie.OrganizeGeneralDetailVO;
import jnpf.permission.vo.v2.organzie.OrganizeManagerFilterNodeVO;
import jnpf.permission.vo.v2.organzie.OrganizeManagerNodeVO;
import jnpf.permission.vo.v2.position.PositionListUserVO;
import jnpf.permission.vo.v2.user.UserBoundVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@Slf4j
@Component
public class FtbAuthorityApiFallback implements FallbackFactory<FtbAuthorityApi> {
@Override
public FtbAuthorityApi create(Throwable cause) {
cause.printStackTrace();
return new FtbAuthorityApi() {
@Override
public Map<String, String> queryUserScopeOfPermission(String userId) {
log.error("queryUserScopeOfPermission 调用失败...");
return null;
}
@Override
public List<StoreBaseListInfo> authStoreBaseListInfo() {
return List.of();
}
@Override
public List<UserBoundVO> listTargetOrganizeIdAuthApi(String organizeId, List<UserWorkStatusEnums> userWorkStatusEnumsList) {
return List.of();
}
@Override
public List<UserBoundVO> listTargetOrganizeIdsAuthApi(List<String> organizeIds, List<UserWorkStatusEnums> userWorkStatusEnumsList) {
return List.of();
}
@Override
public ActionResult<PageListVO<UserBoundVO>> pagePost(QueryPageUserDTO dto) {
return null;
}
@Override
public ActionResult<PageListVO<UserBoundVO>> pagePostMoreKeyword(QueryPageUserMoreKeywordDTO dto) {
return null;
}
@Override
public ActionResult<List<PositionListUserVO>> authListPositionTreeUser() {
return null;
}
@Override
public List<OrganizeAndPositionListVO> authOrganizeWithPositions(OrganizeWithPositionsDTO dto) {
return List.of();
}
@Override
public ActionResult<List<UserBoundVO>> authGetAllUserInfoBatch() {
return null;
}
@Override
public ActionResult<List<String>> authGetPermissionScopeUserIds() {
return null;
}
@Override
public ActionResult<List<OrganizeManagerNodeVO>> listOrganizeTreeUsers() {
return null;
}
@Override
public ActionResult<List<OrganizeGeneralDetailVO>> authOrganizesByUserBound(List<OrganizeCategoryEnums> organizeCategoryEnums) {
return null;
}
@Override
public List<OrganizeManagerFilterNodeVO> listOrganizeTreeFilterNode(List<OrganizeCategoryEnums> organizeCategoryEnums, List<UserWorkStatusEnums> workStatusEnums, Boolean withEmployee, Boolean filterBindOtherStore, Boolean filterBindPayStore) {
return List.of();
}
@Override
public ActionResult<List<FtbRoleListDropDownVO>> roleListDropDown(String userId, String tenantId) {
return null;
}
@Override
public InnerPowerUserVO queryUserScopeOfPermissionForModuleCode() {
return null;
}
@Override
public void remoteClient(FunctionMenuRemoteDTO dto) {
}
@Override
public InnerPowerPositionVO getCurrLoginUserHasPermissionPositionId(String permissionModule) {
return null;
}
@Override
public void clearRoleByPostId(List<String> postIds) {
}
@Override
public Map<String, List<String>> batchAuthOrganizesForUserIdsAndTenantId(List<String> userIds, Integer status, String moduleId, String tenantId) {
return Map.of();
}
@Override
public Map<String, List<String>> batchAuthOrganizesAllForUserIdsAndTenantId(List<String> userIds, Integer status, String moduleId, String tenantId) {
return Map.of();
}
@Override
public Map<String, List<String>> batchAuthOrganizesForUserIds(List<String> userIds, Integer status) {
return null;
}
@Override
public ActionResult<PageListVO<FtbPermissionRoleInfoVO>> permissionList(CultivatePage cultivatePage, FtbPermissionRoleInfoDTO roleInfoDTO) {
return null;
}
@Override
public ActionResult<List<String>> permissionUser(String userId, String permissionModule, String category) {
return null;
}
@Override
public ActionResult<FtbPermissionRoleIdentificationVO> permissionIdentificationCollection() {
return null;
}
@Override
public List<String> queryButtonPermission(FuntionMenuDTO funtion) {
return null;
}
@Override
public Map<String, List<String>> getUserPermissionUserCollection(List<String> userIds) {
return Collections.emptyMap();
}
@Override
public ActionResult<List<MenuTreeVO>> obtainMenusOnToken(String type) {
log.warn("FtbAuthorityApi fallback: obtainMenusOnToken type={}", type);
return ActionResult.success(Collections.emptyList());
}
@Override
public TargetAuthIdsVO getUserAuth() {
return null;
}
};
}
}

View File

@@ -0,0 +1,57 @@
package jnpf.certificate;
import jnpf.base.ActionResult;
import jnpf.certificate.fallback.CertificateManageFallbackApi;
import jnpf.model.certificate.vo.CertificateOrganizeBusinessLicenseVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
@FeignClient(name = "jnpf-ftb", fallback = CertificateManageFallbackApi.class, path = "/web/certificate-manage-api")
public interface CertificateManageApi {
/**
* 根据组织ID查询营业执照信息。
*
* @param organizeId 组织ID
* @return 营业执照信息
*/
@GetMapping("/query-business-license")
ActionResult<CertificateOrganizeBusinessLicenseVO> queryBusinessLicense(@RequestParam("organizeId") String organizeId);
/**
* 根据组织ID列表批量查询营业执照信息。
*
* @param organizeIds 组织ID列表
* @return 营业执照信息列表
*/
@PostMapping("/query-business-license-batch")
ActionResult<List<CertificateOrganizeBusinessLicenseVO>> queryBusinessLicenseBatch(@RequestBody Collection<String> organizeIds);
/**
* 保存组织营业执照信息。
*
* @param req 营业执照保存参数
* @return 操作结果
*/
@PostMapping("/save-business-license")
ActionResult<Void> saveBusinessLicense(@Valid @RequestBody CertificateOrganizeBusinessLicenseVO req);
/**
* 根据组织ID删除该主体下全部证照信息。
*
* @param organizeId 组织ID
* @param loginUserId 登录用户ID
* @return 操作结果
*/
@DeleteMapping("/delete-business-license")
ActionResult<Void> deleteBusinessLicense(@RequestParam("organizeId") String organizeId,
@RequestParam(value = "loginUserId", required = false) String loginUserId);
}

View File

@@ -0,0 +1,19 @@
package jnpf.certificate;
import jnpf.base.ActionResult;
import jnpf.certificate.fallback.CertificateWarningFallbackApi;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "jnpf-ftb", fallback = CertificateWarningFallbackApi.class, path = "/web/certificate-warning-api")
public interface CertificateWarningApi {
/**
* 检查并发送预警
* @param tenantId
* @return
*/
@PostMapping("/checkAndSendCertificateWarning")
ActionResult<Boolean> checkAndSendCertificateWarning(@RequestParam("tenantId") String tenantId);
}

View File

@@ -0,0 +1,44 @@
package jnpf.certificate.fallback;
import jnpf.base.ActionResult;
import jnpf.certificate.CertificateManageApi;
import jnpf.model.certificate.vo.CertificateOrganizeBusinessLicenseVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* 组织营业执照接口降级实现。
*/
@Slf4j
@Component
public class CertificateManageFallbackApi implements CertificateManageApi {
@Override
public ActionResult<CertificateOrganizeBusinessLicenseVO> queryBusinessLicense(String organizeId) {
log.error("queryBusinessLicense调用失败organizeId={}", organizeId);
return ActionResult.success(null);
}
@Override
public ActionResult<List<CertificateOrganizeBusinessLicenseVO>> queryBusinessLicenseBatch(Collection<String> organizeIds) {
log.error("queryBusinessLicenseBatch调用失败organizeIds={}", organizeIds);
return ActionResult.success(Collections.emptyList());
}
@Override
public ActionResult<Void> saveBusinessLicense(CertificateOrganizeBusinessLicenseVO req) {
log.error("saveBusinessLicense调用失败req={}", req);
return ActionResult.success();
}
@Override
public ActionResult<Void> deleteBusinessLicense(String organizeId, String loginUserId) {
log.error("deleteBusinessLicense failed, organizeId={}.loginUserId={}", organizeId,loginUserId);
return ActionResult.success();
}
}

View File

@@ -0,0 +1,16 @@
package jnpf.certificate.fallback;
import jnpf.base.ActionResult;
import jnpf.certificate.CertificateWarningApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class CertificateWarningFallbackApi implements CertificateWarningApi {
@Override
public ActionResult<Boolean> checkAndSendCertificateWarning(String tenantId) {
log.error("checkAndSendCertificateWarning调用失败tenantId={}", tenantId);
return ActionResult.success(Boolean.FALSE);
}
}

View File

@@ -0,0 +1,33 @@
package jnpf.cultivate;
import jnpf.cultivate.fallback.FtbCultivateIdentifyApiFallback;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 鉴定api
*/
@FeignClient(name = "jnpf-ftb", fallbackFactory = FtbCultivateIdentifyApiFallback.class, path = "/cultivate")
public interface FtbCultivateIdentifyApi {
/**
* 处理鉴定逾期数据
*
* @param tenantId
* @return
*/
@PostMapping(value = "/apply/setIdentifyBeOverdue")
@NoDataSourceBind
Boolean setIdentifyBeOverdue(@RequestParam(value = "tenantId") String tenantId);
/**
* 计划鉴定时间提醒
*
* @param tenantId
* @return
*/
@PostMapping(value = "/apply/setIdentifyRemind")
@NoDataSourceBind
Boolean setIdentifyRemind(@RequestParam(value = "tenantId") String tenantId);
}

View File

@@ -0,0 +1,33 @@
package jnpf.cultivate;
import jnpf.cultivate.fallback.FtbCultivateLearnTaskApiFallback;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* 鉴定api
*/
@FeignClient(name = "jnpf-ftb", fallbackFactory = FtbCultivateLearnTaskApiFallback.class, path = "/web/learnTaskList/")
public interface FtbCultivateLearnTaskListApi {
/**
* 定时加入任务
*
* @return
*/
@GetMapping("/timing/timingAddNewPersonToTaskNew/{tenantId}")
@NoDataSourceBind
void timingAddNewPersonToTaskNew(@PathVariable("tenantId") String tenantId);
/**
* 定时提醒任务(每天定点)
*
* @return
*/
@GetMapping("/timing/timingTaskLearningAlertNew/{tenantId}")
@NoDataSourceBind
void timingTaskLearningAlertNew(@PathVariable("tenantId") String tenantId);
}

View File

@@ -0,0 +1,42 @@
package jnpf.cultivate;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.base.ActionResult;
import jnpf.cultivate.fallback.FtbCultivatePromotionFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* @Title:FtbCultivatePromotionMemberApi
* @Author:peng.hao
* @create: 2024/1/1815:58
*/
@FeignClient(name = "jnpf-ftb", fallbackFactory = FtbCultivatePromotionFallback.class )
public interface FtbCultivatePromotionApi {
/**
* 拖动删除成员启用对应晋升通道
* @param userIds 用户id
* @return
*/
@PutMapping("/cul_pro_member/deleteMembersToProChannel")
ActionResult<Boolean> deleteMembersToProChannel(@RequestBody List<String> userIds);
/**
* 获取是否有人岗位职等的晋升申请
* @param postId 公司岗位ID
* @param grandId 职等ID
* @param userId 用户ID
* @return 成功结果
*/
@GetMapping("/cul-post-apply/userIsHasApply")
@Operation(summary = "查看当前的人岗位职等是否存在晋升申请")
ActionResult<Boolean> isThereAnAppForThePosLevel(@RequestParam("postId") String postId,
@RequestParam("grandId") String grandId,
@RequestParam("userId") String userId);
}

View File

@@ -0,0 +1,101 @@
package jnpf.cultivate;
import jnpf.base.ActionResult;
import jnpf.cultivate.fallback.FtbCultivateStoreStatisticApiFallback;
import jnpf.model.cultivate.dto.storestatistics.*;
import jnpf.model.cultivate.vo.position.*;
import jnpf.model.thousandsfaces.TodayWorkVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
/**
* 鉴定api
*/
@FeignClient(name = "jnpf-ftb", fallbackFactory = FtbCultivateStoreStatisticApiFallback.class, path = "/web/store-index-statistics")
public interface FtbCultivateStoreStatisticApi {
/**
* 店长页面 培训统计数量
*/
@PostMapping("/get-cultivate-count")
ActionResult<FtbCultivateStoreCountVO> getCultivateCount(@RequestBody FtbCultivateStoreStatisticsReq req);
/**
* 查询我的考试列表
*
* @param req
* @return
*/
@PostMapping("/queryMyExamList")
ActionResult<List<TodayWorkVo>> queryMyExamList(@RequestBody StoreStatisticsMyExamReq req);
/**
* 待我批阅(下属的)
*
* @param req
* @return
*/
@PostMapping("/queryWaitMyReadOver")
ActionResult<List<TodayWorkVo>> queryWaitMyReadOver(@RequestBody StoreStatisticsWaitMyCheckExamReq req);
/**
* 今日工作-待参与及参与中的线下培训<负责人及参与人均属于相关人员>列表
*
* @param req
* @return
*/
@PostMapping("/queryOfflineTrainList")
ActionResult<List<TodayWorkVo>> queryOfflineTrainList(@RequestBody StoreOfflineTrainReq req);
/**
* 今日工作-我的任务(当日开始的培训任务)列表
*
* @param req
* @return
*/
@PostMapping("/myTaskList")
ActionResult<List<TodayWorkVo>> storeMyTaskList(@RequestBody StoreOfflineTrainReq req);
/**
* 今日工作-鉴定他人(当日的待鉴定)列表
*
* @param req
* @return
*/
@PostMapping("/myIdentityList")
ActionResult<List<TodayWorkVo>> storeMyIdentityList(@RequestBody StoreIdentityReq req);
/**
* 员工界面 查询未学习的通用课程数量
*
* @param req
* @return
*/
@PostMapping("/get-worker-cultivate-count")
ActionResult<FtbCultivateWorkerCountVO> getWorkerCultivateCount(@RequestBody FtbCultivateStoreStatisticsReq req);
/**
* 工作台-员工
*/
@PostMapping("/person/training-statistics")
ActionResult<FtbPersonTrainingStatisticsVO> personTrainingStatistics(@RequestBody FtbCultivateStoreStatisticsReq req);
/**
* 工作台-店长
*/
@PostMapping("/store-manager/training-statistics")
ActionResult<FtbStoreManagerTrainingStatisticsVO> storeManagerTrainingStatistics(@RequestBody FtbCultivateStoreStatisticsReq req);
/**
* 工作台-管理层
*/
@PostMapping("/manager/training-statistics")
ActionResult<FtbManagerTrainingStatisticsVO> managerTrainingStatistics(@RequestBody FtbCultivateStoreStatisticsReq req);
}

View File

@@ -0,0 +1,51 @@
package jnpf.cultivate;
import jnpf.cultivate.fallback.FtbCultivateTeachingApiFallback;
import jnpf.model.cultivate.vo.teaching.MyPracticeSummaryVo;
import jnpf.model.cultivate.vo.teaching.SuperiorTeachingSummaryVo;
import jnpf.model.cultivate.vo.teaching.TeachingStoreCountVo;
import jnpf.model.cultivate.vo.teaching.TodaySummaryDataVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 带教api
*/
@FeignClient(name = "jnpf-ftb", fallbackFactory = FtbCultivateTeachingApiFallback.class, path = "/")
public interface FtbCultivateTeachingApi {
/**
* 店长界面我的带教统计
* @param storeId 门店id
* @return TeachingStoreCountVo
*/
@GetMapping("app/teachingRecord/storeTeachingCount")
TeachingStoreCountVo storeTeachingCount(@RequestParam("storeId") String storeId);
/**
* App店长界面-今日练习汇总数据
*
* @param storeId 查询参数
* @return 练习汇总数据
*/
@GetMapping("teachingRecord/app/getTodaySummary")
TodaySummaryDataVo getTodaySummary(@RequestParam("storeId") String storeId);
/**
* 员工界面-上级带教统计
* @param storeId 门店id
* @return TeachingStoreCountVo
*/
@GetMapping("app/teachingRecord/getSuperiorTeachingSummary")
SuperiorTeachingSummaryVo getSuperiorTeachingSummary(@RequestParam("storeId") String storeId);
/**
* 员工界面--我的练习汇总数据
*
* @param storeId 查询参数
* @return 练习汇总数据
*/
@GetMapping("teachingRecord/app/getMyPracticeSummary")
MyPracticeSummaryVo getMyPracticeSummary(@RequestParam("storeId") String storeId);
}

View File

@@ -0,0 +1,51 @@
package jnpf.cultivate;
import jnpf.base.ActionResult;
import jnpf.cultivate.fallback.V2CultivateOldDealApiFallback;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "jnpf-ftb", fallbackFactory = V2CultivateOldDealApiFallback.class, path = "/v2/cultivate/old")
public interface V2CultivateOldDealApi {
/**
* 课程心得评论旧数据处理
*
* @param tenantId 租户ID
* @return 响应
*/
@NoDataSourceBind
@GetMapping("/gained/comment")
ActionResult<Boolean> dealOldData(@RequestParam("tenantId") String tenantId);
@NoDataSourceBind
@GetMapping("/identify/deal-old")
ActionResult<Boolean> identifyDealOldData(@RequestParam("tenantId") String tenantId);
/**
* 迁移旧培训任务数据到新的任务日志表
*
* @param tenantId 租户ID
* @return 响应
*/
@NoDataSourceBind
@GetMapping("/task/old-data")
ActionResult<String> migrateOldTaskData(@RequestParam("tenantId") String tenantId);
/**
* 旧晋升通道成员数据迁移接口
* 将 ftb_cultivate_promotion_member_new 表中的用户数据迁移到
* ftb_cultivate_promotion_user 和 ftb_cultivate_promotion_setting 表中
* 同时校验用户是否为正常状态enabledMark = 1
*
* @param tenantId 租户ID
* @param promotionId 晋升通道ID可选如果传入则只迁移指定通道的数据
* @return 响应结果
*/
@NoDataSourceBind
@GetMapping("/promotion/migrate-old-data")
ActionResult<String> migratePromotionOldData(@RequestParam("tenantId") String tenantId,
@RequestParam(value = "promotionId", required = false) String promotionId);
}

View File

@@ -0,0 +1,29 @@
package jnpf.cultivate.fallback;
import jnpf.cultivate.FtbCultivateIdentifyApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class FtbCultivateIdentifyApiFallback implements FallbackFactory<FtbCultivateIdentifyApi> {
@Override
public FtbCultivateIdentifyApi create(Throwable cause) {
cause.printStackTrace();
return new FtbCultivateIdentifyApi() {
@Override
public Boolean setIdentifyBeOverdue(String tenantId) {
log.error("setIdentifyBeOverdue调用失败...");
return false;
}
@Override
public Boolean setIdentifyRemind(String tenantId) {
log.error("setIdentifyRemind调用失败...");
return false;
}
};
}
}

View File

@@ -0,0 +1,31 @@
package jnpf.cultivate.fallback;
import jnpf.cultivate.FtbCultivateLearnTaskListApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class FtbCultivateLearnTaskApiFallback implements FallbackFactory<FtbCultivateLearnTaskListApi> {
@Override
public FtbCultivateLearnTaskListApi create(Throwable cause) {
log.error("进入 FtbCultivateLearnTaskApiFallback 的 fallback降级原因", cause); // 打印降级原因
return new FtbCultivateLearnTaskListApi() {
@Override
public void timingAddNewPersonToTaskNew(String tenantId) {
}
@Override
public void timingTaskLearningAlertNew(String tenantId) {
}
};
}
}

View File

@@ -0,0 +1,34 @@
package jnpf.cultivate.fallback;
import jnpf.base.ActionResult;
import jnpf.cultivate.FtbCultivatePromotionApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
public class FtbCultivatePromotionFallback implements FallbackFactory<FtbCultivatePromotionApi> {
@Override
public FtbCultivatePromotionApi create(Throwable cause) {
cause.printStackTrace();
return new FtbCultivatePromotionApi() {
@Override
public ActionResult<Boolean> deleteMembersToProChannel(List<String> userIds) {
log.error("deleteMembersToProChannel 调用失败...");
return null;
}
@Override
public ActionResult<Boolean> isThereAnAppForThePosLevel(String postId, String grandId, String userId) {
log.error("isThereAnAppForThePosLevel 调用失败...");
return null;
}
};
}
}

View File

@@ -0,0 +1,76 @@
package jnpf.cultivate.fallback;
import jnpf.base.ActionResult;
import jnpf.cultivate.FtbCultivateStoreStatisticApi;
import jnpf.model.cultivate.dto.storestatistics.*;
import jnpf.model.cultivate.vo.position.*;
import jnpf.model.thousandsfaces.TodayWorkVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
public class FtbCultivateStoreStatisticApiFallback implements FallbackFactory<FtbCultivateStoreStatisticApi> {
@Override
public FtbCultivateStoreStatisticApi create(Throwable cause) {
log.error("进入 FtbCultivateStoreStatisticApi 的 fallback降级原因", cause); // 打印降级原因
return new FtbCultivateStoreStatisticApi() {
@Override
public ActionResult<FtbCultivateStoreCountVO> getCultivateCount(FtbCultivateStoreStatisticsReq req) {
return null;
}
@Override
public ActionResult<List<TodayWorkVo>> queryMyExamList(StoreStatisticsMyExamReq req) {
return null;
}
@Override
public ActionResult<List<TodayWorkVo>> queryWaitMyReadOver(StoreStatisticsWaitMyCheckExamReq req) {
return null;
}
@Override
public ActionResult<List<TodayWorkVo>> queryOfflineTrainList(StoreOfflineTrainReq req) {
return null;
}
@Override
public ActionResult<List<TodayWorkVo>> storeMyTaskList(StoreOfflineTrainReq req) {
return null;
}
@Override
public ActionResult<List<TodayWorkVo>> storeMyIdentityList(StoreIdentityReq req) {
return null;
}
@Override
public ActionResult<FtbCultivateWorkerCountVO> getWorkerCultivateCount(FtbCultivateStoreStatisticsReq req) {
return null;
}
@Override
public ActionResult<FtbPersonTrainingStatisticsVO> personTrainingStatistics(FtbCultivateStoreStatisticsReq req) {
return null;
}
@Override
public ActionResult<FtbStoreManagerTrainingStatisticsVO> storeManagerTrainingStatistics(FtbCultivateStoreStatisticsReq req) {
return null;
}
@Override
public ActionResult<FtbManagerTrainingStatisticsVO> managerTrainingStatistics(FtbCultivateStoreStatisticsReq req) {
return null;
}
};
}
}

View File

@@ -0,0 +1,42 @@
package jnpf.cultivate.fallback;
import jnpf.cultivate.FtbCultivateTeachingApi;
import jnpf.model.cultivate.vo.teaching.MyPracticeSummaryVo;
import jnpf.model.cultivate.vo.teaching.SuperiorTeachingSummaryVo;
import jnpf.model.cultivate.vo.teaching.TeachingStoreCountVo;
import jnpf.model.cultivate.vo.teaching.TodaySummaryDataVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class FtbCultivateTeachingApiFallback implements FallbackFactory<FtbCultivateTeachingApi> {
@Override
public FtbCultivateTeachingApi create(Throwable cause) {
cause.printStackTrace();
return new FtbCultivateTeachingApi() {
@Override
public TeachingStoreCountVo storeTeachingCount(String storeId) {
return null;
}
@Override
public TodaySummaryDataVo getTodaySummary(String storeId) {
return new TodaySummaryDataVo();
}
@Override
public SuperiorTeachingSummaryVo getSuperiorTeachingSummary(String storeId) {
return null;
}
@Override
public MyPracticeSummaryVo getMyPracticeSummary(String storeId) {
return null;
}
};
}
}

View File

@@ -0,0 +1,39 @@
package jnpf.cultivate.fallback;
import jnpf.base.ActionResult;
import jnpf.cultivate.V2CultivateOldDealApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class V2CultivateOldDealApiFallback implements FallbackFactory<V2CultivateOldDealApi> {
@Override
public V2CultivateOldDealApi create(Throwable cause) {
cause.printStackTrace();
return new V2CultivateOldDealApi() {
@Override
public ActionResult<Boolean> dealOldData(String tenantId) {
return null;
}
@Override
public ActionResult<Boolean> identifyDealOldData(String tenantId) {
return null;
}
@Override
public ActionResult<String> migrateOldTaskData(String tenantId) {
return null;
}
@Override
public ActionResult<String> migratePromotionOldData(String tenantId, String promotionId) {
return null;
}
};
}
}

View File

@@ -0,0 +1,205 @@
package jnpf.doclibrary;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.base.ActionResult;
import jnpf.doclibrary.fallback.StoreFallback;
import jnpf.entity.StoreEntity;
import jnpf.model.store.Store;
import jnpf.model.store.StorePositionInfoVo;
import jnpf.model.store.StoreUserNumVo;
import jnpf.model.store.dto.StoreAbnormalIdsQueryDTO;
import jnpf.model.store.dto.StorePageByIdsNoDsQueryDTO;
import jnpf.model.store.dto.StorePageByIdsQueryDTO;
import jnpf.model.store.vo.StoreBaseListVO;
import jnpf.model.store.vo.StoreLocationVO;
import jnpf.model.store.vo.UserStoreListVo;
import jnpf.model.vo.StoreExecutionVo;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 门店内部调用api
*
* @author yanwenfu
* @create 2023-07-12
*/
@FeignClient(name = "jnpf-ftb", fallback = StoreFallback.class, path = "/Store")
public interface StoreApi {
/**
* 查询门店下岗位的成员数量
* @param storeId 门店id
* @param positionList 岗位id集合
* @return java.util.List<jnpf.model.store.StorePositionInfoVo>
*/
@GetMapping(value = "/storePositionInfo/list")
List<StorePositionInfoVo> getStorePositionInfoList(@RequestParam(value = "storeId") String storeId, @RequestParam(value = "positionList") List<String> positionList);
/**
* 查询门店下的成员数量
* @param storeIds 门店ids
* @return java.util.List<jnpf.model.store.StorePositionInfoVo>
*/
@GetMapping(value = "/storePositionInfo/getUserNum")
List<StoreUserNumVo> getUserNum(@RequestParam(value = "storeIds") List<String> storeIds);
/**
* 获取用户门店列表信息
* @return 返回值
*/
@GetMapping(value = "/userStoreList")
ActionResult<List<Store>> getUserStoreList();
/**
* 获取用户门店列表信息(值班)
*
* @return 返回值
*/
@GetMapping(value = "/userStoreListDuty")
ActionResult<List<Store>> getUserStoreListDuty();
/**
* 查询用户所属的门店
* @param userId 用户id
* @return jnpf.base.ActionResult<java.util.List < jnpf.model.store.Store>>
*/
@GetMapping(value = "/listByUserId/{userId}")
List<Store> getListByUserId(@PathVariable(value = "userId") String userId);
@GetMapping(value = "/getAllUserStores")
List<UserStoreListVo> getAllUserStores();
/**
* 根据门店ids查询门店信息
* @param storeIds 门店ids
* @return java.util.List<jnpf.model.store.Store>
*/
@GetMapping(value = "/listByIds")
List<Store> getListByIds(@RequestParam(value = "storeIds") List<String> storeIds);
@GetMapping("/getList")
ActionResult<List<Store>> getList(@RequestParam("selectKey") String selectKey,
@RequestParam("organizeid") String organizeId,
@RequestParam("longitude") String longitude,
@RequestParam("latitude") String latitude);
/**
* 查询组织下的门店
* @param organizeId 组织id
* @return java.util.List<jnpf.model.store.Store>
*/
@GetMapping(value = "/list")
List<Store> getStoreList(@RequestParam(value = "organizeId", required = false) String organizeId);
/**
* 查询组织下的门店(返回了上级组织)
* @param organizeId 组织id
* @return java.util.List<jnpf.model.store.Store>
*/
@GetMapping(value = "/orgList")
List<Store> getOrgStoreList(@RequestParam(value = "organizeId", required = false) String organizeId, @RequestParam(value = "queryStoreIds", required = false) List<String> queryStoreIds);
/**
* 查询门店信息
* @param id 门店id
* @return jnpf.model.store.Store
*/
@GetMapping("/info/{id}")
Store getStoreInfo(@PathVariable("id") String id);
/**
* 查询门店信息
* @param id 门店id
* @return jnpf.model.store.Store
*/
@GetMapping("/getStoreInfoNoData")
StoreEntity getStoreInfoNoData(@RequestParam("id") String id, @RequestParam("tenantId") String tenantId);
/**
* 根据门店ids分页查询门店记录
* @param storeIds 门店ids
* @param currentPage 当前页码
* @param pageSize 每页条数
* @return com.github.pagehelper.PageInfo<jnpf.model.vo.StoreExecutionVo>
*/
@PostMapping(value = "/store/pageByIds")
PageInfo<StoreExecutionVo> getStorePageByIds(@RequestBody StorePageByIdsQueryDTO query);
/**
* 根据门店ids分页查询门店记录
* @param storeIds 门店ids
* @param currentPage 当前页码
* @param pageSize 每页条数
* @return com.github.pagehelper.PageInfo<jnpf.model.vo.StoreExecutionVo>
*/
@PostMapping(value = "/store/pageByIdsNoDataSource")
PageInfo<StoreExecutionVo> getStorePageByIdsNoDataSource(@RequestBody StorePageByIdsNoDsQueryDTO query);
/**
* 查询异常的门店
* @param storeIds 门店ids
* @return java.util.List<java.lang.String>
*/
@PostMapping(value = "/store/abnormal/record")
List<String> getAbnormalStoreIds(@RequestBody StoreAbnormalIdsQueryDTO query);
/**
* 根据组织ids查询门店列表
* @param organizeIdList 组织ids
* @return java.util.List<jnpf.model.store.Store>
*/
@PostMapping(value = "/store/list/byOrganizeList")
List<Store> getStoreListByOrganizeList(@RequestBody List<String> organizeIdList);
/**
* 查询门店信息(未绑定数据库)
* @param tenantId 租户id
* @param storeIds 门店ids
* @return java.util.List<jnpf.model.store.Store>
*/
@GetMapping(value = "/store/list/noDataSource")
List<Store> getListByIdsNoDataSource(@RequestParam(value = "tenantId") String tenantId, @RequestParam(value = "storeIds") List<String> storeIds);
/**
* 校验是否是值班人
* @param userId 用户Id
*/
@GetMapping(value = "/store/checkStoreUser")
boolean checkStoreUser(@RequestParam(value = "userId") String userId);
@Operation(description = "[列表] 目标组织,及其子组织所有已启用的门店")
@GetMapping(value = "/store/list/organizeChildren")
ActionResult<List<StoreBaseListVO>> getStoreListByOrganizeIdAndChildOrganize(@RequestParam(value = "organizeId") String organizeId, @RequestParam(required = false, value = "disabled") Boolean disabled);
/**
* 查询门店下岗位的成员数量
* @param storeId 门店id
* @param positionList 岗位id集合
* @return java.util.List<jnpf.model.store.StorePositionInfoVo>
*/
@NoDataSourceBind
@GetMapping(value = "/storePositionInfo/list/nodata")
List<StorePositionInfoVo> getStorePositionInfoListNodata(@RequestParam(value = "storeId") String storeId, @RequestParam(value = "positionList") List<String> positionList, @RequestParam(value = "tenantId") String tenantId);
/**
* 查询用户所属的门店
* @param userId 用户id
* @return jnpf.base.ActionResult<java.util.List < jnpf.model.store.Store>>
*/
@GetMapping(value = "/listByUserId/nodata/{userId}")
@NoDataSourceBind
List<Store> getListByUserIdNodata(@PathVariable(value = "userId") String userId, @RequestParam(value = "tenantId") String tenantId);
/**
* 获取门店位置信息
* @param storeId 门店id
* @return jnpf.model.store.vo.StoreLocationVO
*/
@GetMapping(value = "/getStoreLocation")
StoreLocationVO getStoreLocation(@RequestParam(value = "storeId") String storeId);
}

View File

@@ -0,0 +1,178 @@
package jnpf.doclibrary.fallback;
import com.github.pagehelper.PageInfo;
import jnpf.base.ActionResult;
import jnpf.doclibrary.StoreApi;
import jnpf.entity.StoreEntity;
import jnpf.model.store.Store;
import jnpf.model.store.StorePositionInfoVo;
import jnpf.model.store.StoreUserNumVo;
import jnpf.model.store.dto.StoreAbnormalIdsQueryDTO;
import jnpf.model.store.dto.StorePageByIdsNoDsQueryDTO;
import jnpf.model.store.dto.StorePageByIdsQueryDTO;
import jnpf.model.store.vo.StoreBaseListVO;
import jnpf.model.store.vo.StoreLocationVO;
import jnpf.model.store.vo.UserStoreListVo;
import jnpf.model.vo.StoreExecutionVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 资料库内部调用fallback
*
* @author yanwenfu
* @create 2023-07-12
*/
@Slf4j
@Component
public class StoreFallback implements StoreApi {
@Override
public List<StorePositionInfoVo> getStorePositionInfoList(String storeId, List<String> positionList) {
log.error("类名: StoreFallback, 方法名: getStorePositionInfoList, 错误信息: 调用失败...");
return new ArrayList<>();
}
@Override
public List<StoreUserNumVo> getUserNum(List<String> storeIds) {
log.error("类名: StoreFallback, 方法名: getUserNum, 错误信息: 调用失败...");
return new ArrayList<>();
}
@Override
public List<Store> getListByUserId(String userId) {
log.error("类名: StoreFallback, 方法名: getStoreListByUser, 错误信息: 调用失败...");
return new ArrayList<>();
}
@Override
public List<UserStoreListVo> getAllUserStores() {
log.error("类名: StoreFallback, 方法名: getAllUserStores, 错误信息: 调用失败...");
return null;
}
@Override
public List<Store> getListByIds(List<String> storeIds) {
log.error("类名: StoreFallback, 方法名: getListByIds, 错误信息: 调用失败...");
return new ArrayList<>();
}
@Override
public ActionResult<List<Store>> getList(String selectKey, String organizeId, String longitude, String latitude) {
log.error("类名: StoreFallback, 方法名: getList, 错误信息: 调用失败...");
return null;
}
@Override
public ActionResult<List<Store>> getUserStoreList() {
log.error("类名: StoreFallback, 方法名: getUserStoreList, 错误信息: 调用失败...");
return null;
}
@Override
public ActionResult<List<Store>> getUserStoreListDuty() {
return null;
}
@Override
public List<Store> getStoreList(String organizeId) {
log.error("类名: StoreFallback, 方法名: getStoreList, 错误信息: 调用失败...");
return new ArrayList<>();
}
@Override
public List<Store> getOrgStoreList(String organizeId, List<String> queryStoreIds) {
log.error("类名: StoreFallback, 方法名: getOrgStoreList, 错误信息: 调用失败...");
return null;
}
@Override
public Store getStoreInfo(String id) {
log.error("类名: StoreFallback, 方法名: getStoreInfo, 错误信息: 调用失败...");
return null;
}
/**
* 查询门店信息
*
* @param id 门店id
* @param tenantId
* @return jnpf.model.store.Store
*/
@Override
public StoreEntity getStoreInfoNoData(String id, String tenantId) {
log.error("FTB_类名: StoreFallback, 方法名: getStoreInfoDutyQuery, 错误信息: 调用失败..., 当前租户号[{}] - 门店号[{}]", tenantId, id);
log.error(" getStoreInfoDutyQuery 没有拿到门店信息,走了fallback");
return null;
}
@Override
public PageInfo<StoreExecutionVo> getStorePageByIds(StorePageByIdsQueryDTO query) {
return new PageInfo<>();
}
@Override
public boolean checkStoreUser(String userId) {
log.error("类名: StoreFallback, 方法名: checkStoreUser, 错误信息: 调用失败...");
return false;
}
@Override
public ActionResult<List<StoreBaseListVO>> getStoreListByOrganizeIdAndChildOrganize(String organizeId, Boolean disabled) {
return null;
}
@Override
public List<StorePositionInfoVo> getStorePositionInfoListNodata(String storeId, List<String> positionList, String tenantId) {
return null;
}
@Override
public List<Store> getListByUserIdNodata(String userId, String tenantId) {
return null;
}
@Override
public StoreLocationVO getStoreLocation(String storeId) {
return null;
}
@Override
public PageInfo<StoreExecutionVo> getStorePageByIdsNoDataSource(StorePageByIdsNoDsQueryDTO query) {
return new PageInfo<>();
}
@Override
public List<String> getAbnormalStoreIds(StoreAbnormalIdsQueryDTO query) {
return new ArrayList<>();
}
@Override
public List<Store> getStoreListByOrganizeList(List<String> organizeIdList) {
return new ArrayList<>();
}
@Override
public List<Store> getListByIdsNoDataSource(String tenantId, List<String> storeIds) {
return new ArrayList<>();
}
// @Override
// public ActionResult<Store> info(String id) {
// return null;
// }
}

View File

@@ -0,0 +1,43 @@
package jnpf.exam;
import jnpf.base.ActionResult;
import jnpf.exam.fallback.V2CultivateTimingApiFallback;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "jnpf-ftb", fallback = V2CultivateTimingApiFallback.class, path = "/v2/cultivate/timing")
public interface V2CultivateTimingApi {
/**
* 培训相关每分钟执行
*
* @return 响应
*/
@NoDataSourceBind
@GetMapping("/per-minute")
ActionResult<Integer> perMinute(@RequestParam("tenantId") String tenantId);
/**
* 每小时执行
*
* @param tenantId 租户ID
* @return 响应
*/
@NoDataSourceBind
@GetMapping("/per-hour")
ActionResult<Integer> perHour(@RequestParam("tenantId") String tenantId);
/**
* 每半个小时执行
*
* @param tenantId 租户ID
* @return 响应
*/
@NoDataSourceBind
@GetMapping("/per-half-hour")
ActionResult<Integer> perHalfHour(@RequestParam("tenantId") String tenantId);
}

View File

@@ -0,0 +1,25 @@
package jnpf.exam.fallback;
import jnpf.base.ActionResult;
import jnpf.exam.V2CultivateTimingApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class V2CultivateTimingApiFallback implements V2CultivateTimingApi {
@Override
public ActionResult<Integer> perMinute(String tenantId) {
return null;
}
@Override
public ActionResult<Integer> perHour(String tenantId) {
return null;
}
@Override
public ActionResult<Integer> perHalfHour(String tenantId) {
return null;
}
}

View File

@@ -0,0 +1,71 @@
package jnpf.franchisee;
import jnpf.franchisee.fallback.FranchiseeFallbackApi;
import jnpf.model.franchisee.vo.FranchiseeIdName;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Collection;
import java.util.List;
@FeignClient(name = "jnpf-ftb", fallback = FranchiseeFallbackApi.class, path = "/web/franchise-api")
public interface FranchiseeApi {
/**
* 获取所有加盟商信息
* @return
*/
@GetMapping("/getFranchiseeIdNameList")
List<FranchiseeIdName> getFranchiseeIdNameList();
/**
* 根据ID列表获取加盟商信息
* @param ids
* @return
*/
@GetMapping("/getFranchiseeIdNameListByIds")
List<FranchiseeIdName> getFranchiseeIdNameListByIds(Collection<String> ids);
/**
* 根据ID获取加盟商信息
* @param id
* @return
*/
@GetMapping("/getFranchiseeIdNameListById")
FranchiseeIdName getFranchiseeIdNameListById(String id);
/**
* 根据名称获取加盟商信息
* @param name
* @return
*/
@GetMapping("/getFranchiseeIdNameListByName")
List<FranchiseeIdName> getFranchiseeIdNameListByName(String name);
/**
* 根据名称列表获取加盟商信息
* @param names
* @return
*/
@GetMapping("/getFranchiseeIdNameListByNames")
List<FranchiseeIdName> getFranchiseeIdNameListByNames(Collection<String> names);
/**
* 根据编码获取加盟商信息
* @param code
* @return
*/
@GetMapping("/getFranchiseeIdNameListByCode")
FranchiseeIdName getFranchiseeIdNameListByCode(String code);
/**
* 根据编码列表获取加盟商信息
* @param codes
* @return
*/
@GetMapping("/getFranchiseeIdNameListByCodes")
List<FranchiseeIdName> getFranchiseeIdNameListByCodes(Collection<String> codes);
}

View File

@@ -0,0 +1,55 @@
package jnpf.franchisee.fallback;
import jnpf.franchisee.FranchiseeApi;
import jnpf.model.franchisee.vo.FranchiseeIdName;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
@Slf4j
@Component
public class FranchiseeFallbackApi implements FranchiseeApi {
@Override
public List<FranchiseeIdName> getFranchiseeIdNameList() {
log.error("getFranchiseeIdNameList fallback");
return null;
}
@Override
public List<FranchiseeIdName> getFranchiseeIdNameListByIds(Collection<String> ids) {
log.error("getFranchiseeIdNameListByIds fallback.ids:{}",ids);
return null;
}
@Override
public FranchiseeIdName getFranchiseeIdNameListById(String id) {
log.error("getFranchiseeIdNameListById fallback.id{}",id);
return null;
}
@Override
public List<FranchiseeIdName> getFranchiseeIdNameListByName(String name) {
log.error("getFranchiseeIdNameListByName fallback.name{}",name);
return null;
}
@Override
public List<FranchiseeIdName> getFranchiseeIdNameListByNames(Collection<String> names) {
log.error("getFranchiseeIdNameListByNames fallback.names{}",names);
return null;
}
@Override
public FranchiseeIdName getFranchiseeIdNameListByCode(String code) {
log.error("getFranchiseeIdNameListByCode fallback.names{}",code);
return null;
}
@Override
public List<FranchiseeIdName> getFranchiseeIdNameListByCodes(Collection<String> codes) {
log.error("getFranchiseeIdNameListByCodes fallback.names{}",codes);
return null;
}
}

View File

@@ -0,0 +1,23 @@
package jnpf.notice;
import jnpf.base.ActionResult;
import jnpf.notice.fallback.FtbNoticeFallBackApi;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "jnpf-ftb", fallback = FtbNoticeFallBackApi.class, path = "/web/noticeAnnouncements")
public interface FtbNoticeApi {
/**
* 每半个小时检测一下是否有发布的通知公告
*
* @param tenantId 租户ID
* @return
*/
@NoDataSourceBind
@GetMapping("/checkPublishNotice")
ActionResult<Boolean> checkPublishNotice(@RequestParam("tenantId") String tenantId);
}

View File

@@ -0,0 +1,18 @@
package jnpf.notice.fallback;
import jnpf.base.ActionResult;
import jnpf.notice.FtbNoticeApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class FtbNoticeFallBackApi implements FtbNoticeApi {
@Override
public ActionResult<Boolean> checkPublishNotice(String tenantId) {
return null;
}
}

View File

@@ -0,0 +1,72 @@
package jnpf.personnels;
import jnpf.model.attendance.vo.DailyApprovalVo;
import jnpf.model.personnels.dto.emp.FtbEmpQueryDTO;
import jnpf.model.personnels.dto.roster.meta.FtbPersonnlesJobTenureDTO;
import jnpf.model.personnels.dto.secondment.FtbSecondMentQueryDTO;
import jnpf.model.personnels.dto.staff.roster.FtbPersonnelsStaffRosterDto;
import jnpf.model.personnels.vo.roster.FtbPersonnelsChangeInfoVO;
import jnpf.model.personnels.vo.roster.FtbPersonnlesJobTenureVO;
import jnpf.model.personnels.vo.secondment.FtbPersonnelsSecondmentVO;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.Map;
/**
* @Author: peng.hao
* @create: 2025/10/3
*/
@FeignClient(name = "jnpf-ftb")
public interface FtbPersonneApi {
/**
*根据userId 查询人事异动信息
*/
@GetMapping("/web-app/staff-home-page/get-personnel-change-info")
FtbPersonnelsChangeInfoVO getPersonnelChangeInfo(@RequestParam(name = "userId") String userId);
@PostMapping("/web-app/staff-home-page/get-personnel-change-info-batch")
Map<String, FtbPersonnelsChangeInfoVO> getPersonnelChangeInfoBatch(@RequestBody List<String> userIds);
/**
* 根据userI 和开始时间结束时间查询借调记录
*/
@GetMapping("/web/secondment/get-secondment-record")
List<FtbPersonnelsSecondmentVO> getSecondmentRecord(@RequestParam("userId") String userId,
@RequestParam(value = "startLeaveTime",required = false) String startLeaveTime,
@RequestParam(value = "endLeaveTime",required = false)String endLeaveTime);
/**
* 批量查询审批中和审批完成的借调记录
*/
@PostMapping("/web/secondment/get-secondment-record-bath")
List<FtbPersonnelsSecondmentVO> getSecondmentRecordBath(@RequestBody FtbSecondMentQueryDTO dto);
/**
* 查询审批中和审批完成的借调记录
*/
@PostMapping("/web/secondment/list-query-approval")
List<DailyApprovalVo> queryListApproval(@RequestBody FtbSecondMentQueryDTO dto);
/**
* 模糊搜索 电话 名称
*/
@PostMapping("/web/personnels-emp-entry/search-phone-name")
List<FtbPersonnelsStaffRosterDto> searchPhoneName(@RequestBody FtbEmpQueryDTO dto);
/**
* 查询岗龄
* @param req
* @return
*/
@PostMapping("/web-app/staff-home-page/query-positionTenure-age")
@NoDataSourceBind
List<FtbPersonnlesJobTenureVO> queryPositionTenureAge(@RequestBody FtbPersonnlesJobTenureDTO req);
}

View File

@@ -0,0 +1,38 @@
package jnpf.personnels;
import jnpf.base.ActionResult;
import jnpf.model.personnels.dto.contractinfo.ContactStatusInfo;
import jnpf.personnels.fallback.FtbPersonnelsContaceInfoManagerFallBackApi;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "jnpf-ftb", fallback = FtbPersonnelsContaceInfoManagerFallBackApi.class, path = "/web/personnelsContactInfo")
public interface FtbPersonnelsContactInfoManagerApi {
/**
* 同步合同信息
*
* @param info
* @return
*/
@PostMapping("/syncContactInfo")
@NoDataSourceBind
ActionResult<Boolean> syncContactInfo(@RequestBody ContactStatusInfo info);
/**
* 定时任务检测未签署合同消息
*
* @param tenantId
* @return
*/
@GetMapping("/checkNotSendContactSign")
@NoDataSourceBind
ActionResult<Boolean> checkNotSendContactSign(@RequestParam("tenantId") String tenantId);
}

View File

@@ -0,0 +1,19 @@
package jnpf.personnels;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 入职api
*/
@FeignClient(name = "jnpf-ftb", path = "/web/personnels-emp-entry")
public interface FtbPersonnelsEmEntryApi {
/**
* 根据userId修改手机号信息
*/
@PutMapping("/update-phone-by-userId")
void updatePhoneByUserId( @RequestParam("userId") String userId,
@RequestParam("phone") String phone);
}

View File

@@ -0,0 +1,30 @@
package jnpf.personnels;
import jnpf.model.personnels.vo.employeetype.FtbPersonnelsEmployeeTypeVO;
import jnpf.personnels.fallback.FtbPersonnelsEmployeeTypeRemoteFallBackApi;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
import java.util.Map;
/**
* 员工类型模块
*
* @author wangchunxiang
* @date 2025/09/30
*/
@FeignClient(name = "jnpf-ftb", fallback = FtbPersonnelsEmployeeTypeRemoteFallBackApi.class, path = "/web/employee-type")
public interface FtbPersonnelsEmployeeTypeRemoteApi {
/**
* 根据用户ID集合查询员工类型ID、名称
*
* @param userIds 用户ID集合
* @return key为用户Idvalue为员工类型
*/
@PostMapping("/get-employee-type-by-user-ids")
Map<String, FtbPersonnelsEmployeeTypeVO> getEmployeeTypeByUserIds(@RequestBody List<String> userIds);
}

View File

@@ -0,0 +1,28 @@
package jnpf.personnels;
import jnpf.base.ActionResult;
import jnpf.model.personnels.dto.oa.FtbPersonnelsEmployInfoForOA;
import jnpf.personnels.fallback.FtbPersonnelsEmploymentApplyFallBackApi;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(name = "jnpf-ftb", fallback = FtbPersonnelsEmploymentApplyFallBackApi.class, path = "/web/personnels-staff-employment-apply")
public interface FtbPersonnelsEmploymentApplyApi {
@GetMapping("/updateEmploymentApplyStatus")
@NoDataSourceBind
public ActionResult updateEmploymentApplyStatus(@RequestParam("tenantId") String tenantId);
/**
* 入职办理列表查询 ForOA 回显 姓名 手机
*/
@GetMapping("/inquire-about-the-entry-list")
ActionResult<List<FtbPersonnelsEmployInfoForOA>> inquireAboutTheEntryList(@RequestParam(required = false, name = "keyWords") String keyWords,
@RequestParam(required = false,name = "phone")String phone,
@RequestParam(required = false,name = "workerName")String workerName);
}

View File

@@ -0,0 +1,44 @@
package jnpf.personnels;
import jnpf.base.ActionResult;
import jnpf.model.personnels.dto.oa.FtbPersonnelsEmployInfoForOA;
import jnpf.model.personnels.vo.range.FtbRangeConfigDIYVO;
import jnpf.model.personnels.vo.range.FtbRangeConfigVO;
import jnpf.personnels.fallback.FtbPersonnelsEmploymentApplyFallBackApi;
import jnpf.personnels.fallback.FtbPersonnelsInfoConfigBackApi;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.annotation.Resource;
import java.util.List;
@FeignClient(name = "jnpf-ftb", fallback = FtbPersonnelsInfoConfigBackApi.class, path = "/web/range-config")
public interface FtbPersonnelsInfoConfigApi {
/**
* 查询是平均范围配置 还是自定义配置
* @param type 1 年龄 2 薪资 3 工龄
* @return 配置类型 1平均 2自定义
*/
@GetMapping("/query-type")
ActionResult<Integer> queryType(@RequestParam("type") Integer type);
/**
* 查询平均区间配置范围
* @param type 1 年龄 2 薪资 3 工龄
*/
@GetMapping("/query-info")
ActionResult<FtbRangeConfigVO> queryInfo(@RequestParam("type") Integer type);
/**
* 查询自定义区间配置范围
*
* @param type 1 年龄 2 薪资 3 工龄
*/
@GetMapping("/query-info-diy")
ActionResult<List<FtbRangeConfigDIYVO>> queryDiyInfo(@RequestParam("type") Integer type);
}

View File

@@ -0,0 +1,59 @@
package jnpf.personnels;
import jnpf.model.personnels.dto.roster.meta.PersonnelsMetaDTO;
import jnpf.model.personnels.dto.salary.FtbXcCustomFieldDto;
import jnpf.model.personnels.req.roster.FtbPersonnelsMetaDataReq;
import jnpf.model.personnels.req.roster.FtbPersonnelsMetaFuctionReq;
import jnpf.model.personnels.vo.salary.FtbXcCustomFieldVo;
import jnpf.personnels.fallback.FtbPersonnelsMetaDataManagerFallBackApi;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
import java.util.Map;
@FeignClient(name = "jnpf-ftb", fallback = FtbPersonnelsMetaDataManagerFallBackApi.class, path = "/web/personnels/metaData")
public interface FtbPersonnelsMetaDataManagerApi {
@PostMapping("/getMetaData")
@NoDataSourceBind
public List<PersonnelsMetaDTO> getMetaData(@RequestBody FtbPersonnelsMetaDataReq info);
/**
* 查询用户当月是否已经离职
*
* @param userId 用户ID
* @param tenantId 租户ID
* @return true-已经离职 false-未离职
*/
@GetMapping("/queryCurrMonthDepartStatus/{userId}/{tenantId}")
@NoDataSourceBind
public Boolean queryCurrMonthDepartStatus(@PathVariable("userId") String userId, @PathVariable("tenantId") String tenantId);
/**
* 查询用户当月是否已经离职
*
* @param req 请求
* @return true-已经离职 false-未离职
*/
@PostMapping("/queryCurrMonthDepartStatusByDate")
@NoDataSourceBind
Boolean queryCurrMonthDepartStatusByDate(@RequestBody FtbPersonnelsMetaFuctionReq req);
/**
* 批量查询员工是否离职
*/
@PostMapping("/query-current-month-leave")
@NoDataSourceBind
Map<String,Boolean> queryCurrMonthLeave(@RequestBody FtbPersonnelsMetaFuctionReq req);
/**
* 批量查询员工自定义字段
*/
@PostMapping("/query-custom-filed")
List<FtbXcCustomFieldVo> queryCustomFiled(@RequestBody FtbXcCustomFieldDto req);
}

View File

@@ -0,0 +1,50 @@
package jnpf.personnels;
import jnpf.model.personnels.dto.rewardspunishments.FtbPersonnelSalaryRewardDTO;
import jnpf.model.personnels.dto.salary.FtbSalaryMetaDataQueryDto;
import jnpf.model.personnels.vo.rewardspunishments.FtbPersonnelSalaryRewardVO;
import jnpf.model.personnels.vo.rewardspunishments.FtbXcEmployeeRewardRecordsVO;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* 查询指定用户,指定月份内,被奖励和处罚的合计
*/
@FeignClient(name = "jnpf-ftb", path = "/ftb-personnels-rewards-punishments")
public interface FtbPersonnelsRewardsPunishmentsApi {
/**
* 薪酬奖励合计
*/
@PostMapping(value = "/salary-reward")
List<FtbPersonnelSalaryRewardVO> salaryReward(@RequestBody FtbPersonnelSalaryRewardDTO ftbPersonnelSalaryRewardDTO);
/**
* 薪酬惩罚合计
*/
@PostMapping(value = "/pay-penalty")
List<FtbPersonnelSalaryRewardVO> totalSalaryPenalty(@RequestBody FtbPersonnelSalaryRewardDTO ftbPersonnelSalaryRewardDTO);
/**
* 薪酬获取奖励和惩罚api
*/
@PostMapping(value = "/salaryMetaDataQuery")
@NoDataSourceBind
BigDecimal salaryMetaDataQuery(@RequestBody FtbSalaryMetaDataQueryDto dto);
/**
* 薪酬获取奖励和惩罚api(批量)
*/
@PostMapping(value = "/salary-meta-data-batch-query")
@NoDataSourceBind
List<FtbXcEmployeeRewardRecordsVO> salaryBatchMetaDataQuery(@RequestBody FtbSalaryMetaDataQueryDto dto);
}

View File

@@ -0,0 +1,25 @@
package jnpf.personnels;
import jnpf.base.ActionResult;
import jnpf.model.personnels.vo.rewardspunishments.FtbPersonnelsRewardsPunishmentApprovalVO;
import jnpf.personnels.fallback.FtbPersonnelsRewardsPunishmentsRemoteFallBackApi;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(name = "jnpf-ftb", path = "/ftb-personnels-rewards-punishments", fallback = FtbPersonnelsRewardsPunishmentsRemoteFallBackApi.class)
public interface FtbPersonnelsRewardsPunishmentsRemoteApi {
/**
* OA审批获取奖惩规则
*
* @param type 类型0奖励1惩罚
* @return {@link ActionResult }
*/
@GetMapping(value = "/list-approval")
ActionResult<List<FtbPersonnelsRewardsPunishmentApprovalVO>> listApproval(@RequestParam("type") Integer type);
}

View File

@@ -0,0 +1,147 @@
package jnpf.personnels;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.model.personnels.dto.roster.QueryCompanyAgeDto;
import jnpf.model.personnels.dto.staff.employment.FtbPersonnelsStaffEmploymentApplyDto;
import jnpf.model.personnels.dto.staff.roster.FtbPersonnelsStaffRosterDto;
import jnpf.model.personnels.dto.staff.roster.ShopManagerUserDto;
import jnpf.model.personnels.dto.staff.roster.StaffRosterInfoDto;
import jnpf.model.personnels.req.roster.StaffRosterListReq;
import jnpf.model.personnels.req.roster.StaffRosterReq;
import jnpf.permission.model.user.UserListVO;
import jnpf.permission.vo.user.UserListMatchVO;
import jnpf.personnels.fallback.FtbPersonnelsRosterManagerFallBackApi;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@FeignClient(name = "jnpf-ftb", fallback = FtbPersonnelsRosterManagerFallBackApi.class, path = "/web/personnels-staff-roster")
public interface FtbPersonnelsRosterManagerApi {
@NoDataSourceBind
@GetMapping("/updateCompanyAge")
ActionResult updateCompanyAge(@RequestParam("tenantId") String tenantId);
/**
* 绑定手机号
*
* @param userId 用户ID
* @param phone 手机号
* @return
*/
@PostMapping("/bindPhone")
@NoDataSourceBind
public ActionResult<Boolean> bindPhone(@RequestParam("tenantCode") String tenantCode, @RequestParam("userId") String userId, @RequestParam("phone") String phone);
@NoDataSourceBind
@PostMapping("/queryCompanyAge/{tenantId}")
ActionResult<List<QueryCompanyAgeDto>> queryCompanyAge(@RequestBody List<String> userIds, @PathVariable("tenantId") String tenantId);
/**
* 查询未提交入职登记表的用户
*
* @param tenantId
* @return
*/
@NoDataSourceBind
@GetMapping("/queryNoSubmitForm")
ActionResult<List<StaffRosterInfoDto>> queryNoSubmitForm(@RequestParam("tenantId") String tenantId);
/**
* 查询健康证过期的用户
*
* @param tenantId
* @param days 距离健康证 快过期的天数
* @return
*/
@NoDataSourceBind
@GetMapping("/queryHealthExpire")
ActionResult<List<StaffRosterInfoDto>> queryHealthExpire(@RequestParam("tenantId") String tenantId, @RequestParam(value = "days", required = false) Long days, @RequestParam(value = "months", required = false) Long months);
/**
* 花名册查询列表
*
* @param req
* @return {@link ActionResult}<{@link FtbPersonnelsStaffEmploymentApplyDto}>
*/
@GetMapping("/query-list")
ActionResult<PageListVO<FtbPersonnelsStaffRosterDto>> pageLists(@Validated @SpringQueryMap StaffRosterListReq req);
/**
* 花名册查询列表 _post
*
* @param req
* @return {@link ActionResult}<{@link FtbPersonnelsStaffEmploymentApplyDto}>
*/
@PostMapping("/query-list/postWithSalary")
ActionResult<PageListVO<FtbPersonnelsStaffRosterDto>> postWithSalary(@Validated @RequestBody StaffRosterListReq req);
/**
* 花名册查询列表不分页
*/
@PostMapping("/query-list/post-with-salary-no-page")
List<FtbPersonnelsStaffRosterDto> postWithSalaryNoPage(@Validated @RequestBody StaffRosterListReq req);
/**
* 花名册列表查询-无权限
* @param req
* @return ActionResult<FtbPersonnelsStaffRosterDto>
*/
@PostMapping("/query-list/byUserIds")
ActionResult<List<FtbPersonnelsStaffRosterDto>> getPersonnelByUserIds(@RequestBody StaffRosterListReq req);
@PostMapping("/query-list/post")
ActionResult<PageListVO<FtbPersonnelsStaffRosterDto>> pageListsPost(@Validated @RequestBody StaffRosterListReq req);
/**
* 查询离职人员信息
* @return
*/
@PostMapping("/queryDepartUser")
ActionResult<List<FtbPersonnelsStaffRosterDto>> queryDepartUser(@RequestBody List<String> userIds);
@Operation(summary = "[匹配]-指定user们哪些存在,哪些不存在, 包含花名册离职的用户")
@PostMapping("/user/list/match/ids")
ActionResult<UserListMatchVO> getUserListByMatch(@RequestBody List<String> userIds);
/**
* 根据用户ID查询所属门店负责人信息
*
* @param tenantId
* @param userIds 用户ID
* @return
*/
@NoDataSourceBind
@PostMapping("/queryShopManagerUser/{tenantId}")
ActionResult<ShopManagerUserDto> queryShopManagerUser(@PathVariable("tenantId") String tenantId, @RequestBody List<String> userIds);
@NoDataSourceBind
@GetMapping("/timingAlertTrialJob/{tenantId}")
@Deprecated(since = "人事2.1废弃试岗状态")
ActionResult<List<UserListVO>> timingAlertTrialJob(@PathVariable("tenantId") String tenantId);
/**
* 根据用户ID查询用户信息
* @param req
* @return
*/
@PostMapping("/queryWithUserIds")
List<FtbPersonnelsStaffRosterDto> queryWithUserIds( @RequestBody StaffRosterListReq req);
/**
* 查询指定userIds用户信息
*/
@PostMapping("/queryWithUserIds/post")
List<FtbPersonnelsStaffRosterDto> queryWithUserIdsPost(@RequestBody StaffRosterReq req);
}

View File

@@ -0,0 +1,52 @@
package jnpf.personnels;
import jnpf.base.ActionResult;
import jnpf.model.personnels.dto.turnover.FtbDepUserDTO;
import jnpf.model.personnels.vo.turnover.FtbPersonnelsTurnoverManagementVO;
import jnpf.permission.vo.v2.user.UserBoundVO;
import jnpf.personnels.fallback.FtbPersonnelsTurnoverManagementFallBackApi;
import jnpf.util.NoDataSourceBind;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* @Title: FtbPersonnelsTurnoverManagementApi
* @Author: peng.hao
* @create: 2024/2/19 10:36
*/
@FeignClient(name = "jnpf-ftb", fallback = FtbPersonnelsTurnoverManagementFallBackApi.class, path = "/web/personnels-turnover")
public interface FtbPersonnelsTurnoverManagementApi {
@GetMapping("/closeUserAccountRegularlyAfterResignation")
@NoDataSourceBind
ActionResult<String> closeUserAccountRegularlyAfterResignation(@RequestParam("tenantId") String tenantId);
/**
* 离职用户已签署离职协议
* @param userId 用户id
* @return
*/
@GetMapping("/user-has-sign-an-agreement")
Boolean userHasSignedASeparationAgreement(@RequestParam("userId") String userId,@RequestParam("flag") Integer flag);
/**
* 获取所有离职人员信息
*/
@GetMapping("/query-turnover-list")
List<FtbPersonnelsTurnoverManagementVO> queryTurnoverList();
/**
* 获取离职人员信息
* 1.按多个userId
* 2.按多个组织id
* 3.按多个岗位id
*/
@PostMapping("/get-dep-user")
List<UserBoundVO> getInformationAboutTheDepartingPerson(@RequestBody FtbDepUserDTO dto);
@PostMapping("/not-token-get-dep-user")
List<UserBoundVO> getInformationAboutTheDepartingPersonNotToken(@RequestBody FtbDepUserDTO dto);
}

View File

@@ -0,0 +1,24 @@
package jnpf.personnels.fallback;
import jnpf.base.ActionResult;
import jnpf.model.personnels.dto.contractinfo.ContactStatusInfo;
import jnpf.personnels.FtbPersonnelsContactInfoManagerApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class FtbPersonnelsContaceInfoManagerFallBackApi implements FtbPersonnelsContactInfoManagerApi {
@Override
public ActionResult<Boolean> syncContactInfo(ContactStatusInfo info) {
return null;
}
@Override
public ActionResult<Boolean> checkNotSendContactSign(String tenantId) {
return null;
}
}

View File

@@ -0,0 +1,18 @@
package jnpf.personnels.fallback;
import jnpf.model.personnels.vo.employeetype.FtbPersonnelsEmployeeTypeVO;
import jnpf.personnels.FtbPersonnelsEmployeeTypeRemoteApi;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
@Component
public class FtbPersonnelsEmployeeTypeRemoteFallBackApi implements FtbPersonnelsEmployeeTypeRemoteApi {
@Override
public Map<String, FtbPersonnelsEmployeeTypeVO> getEmployeeTypeByUserIds(List<String> userIds) {
return null;
}
}

View File

@@ -0,0 +1,26 @@
package jnpf.personnels.fallback;
import jnpf.base.ActionResult;
import jnpf.model.personnels.dto.oa.FtbPersonnelsEmployInfoForOA;
import jnpf.personnels.FtbPersonnelsEmploymentApplyApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class FtbPersonnelsEmploymentApplyFallBackApi implements FtbPersonnelsEmploymentApplyApi {
@Override
public ActionResult updateEmploymentApplyStatus(String tenantId) {
return null;
}
@Override
public ActionResult<List<FtbPersonnelsEmployInfoForOA>> inquireAboutTheEntryList(String keyWords, String phone, String workerName) {
return null;
}
}

View File

@@ -0,0 +1,29 @@
package jnpf.personnels.fallback;
import jnpf.base.ActionResult;
import jnpf.model.personnels.vo.range.FtbRangeConfigDIYVO;
import jnpf.model.personnels.vo.range.FtbRangeConfigVO;
import jnpf.personnels.FtbPersonnelsInfoConfigApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class FtbPersonnelsInfoConfigBackApi implements FtbPersonnelsInfoConfigApi {
@Override
public ActionResult<Integer> queryType(Integer type) {
return null;
}
@Override
public ActionResult<FtbRangeConfigVO> queryInfo(Integer type) {
return null;
}
@Override
public ActionResult<List<FtbRangeConfigDIYVO>> queryDiyInfo(Integer type) {
return null;
}
}

View File

@@ -0,0 +1,47 @@
package jnpf.personnels.fallback;
import jnpf.model.personnels.dto.roster.meta.PersonnelsMetaDTO;
import jnpf.model.personnels.dto.salary.FtbXcCustomFieldDto;
import jnpf.model.personnels.req.roster.FtbPersonnelsMetaDataReq;
import jnpf.model.personnels.req.roster.FtbPersonnelsMetaFuctionReq;
import jnpf.model.personnels.vo.salary.FtbXcCustomFieldVo;
import jnpf.personnels.FtbPersonnelsMetaDataManagerApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Component
@Slf4j
public class FtbPersonnelsMetaDataManagerFallBackApi implements FtbPersonnelsMetaDataManagerApi {
@Override
public List<PersonnelsMetaDTO> getMetaData(FtbPersonnelsMetaDataReq info) {
return null;
}
@Override
public Boolean queryCurrMonthDepartStatus(String userId, String tenantId) {
return null;
}
@Override
public Boolean queryCurrMonthDepartStatusByDate(FtbPersonnelsMetaFuctionReq req) {
return null;
}
@Override
public Map<String, Boolean> queryCurrMonthLeave(FtbPersonnelsMetaFuctionReq req) {
return Map.of();
}
@Override
public List<FtbXcCustomFieldVo> queryCustomFiled(FtbXcCustomFieldDto req) {
return List.of();
}
}

Some files were not shown because too many files have changed in this diff Show More