Compare commits

..

94 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
d20bc1a168 Controller修改、controller删除
Some checks failed
API接口参数变更检测 / api-param-check (push) Failing after 1m40s
2026-06-03 15:36:21 +08:00
95126e85e7 Controller修改、controller删除 2026-06-03 15:36:03 +08:00
4405 changed files with 451093 additions and 397 deletions

View File

@@ -31,26 +31,72 @@ class ParameterChange:
required: Optional[bool] = None required: Optional[bool] = None
old_required: Optional[bool] = None old_required: Optional[bool] = None
detail: Optional[str] = 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: if self.change_type == ChangeType.RENAMED:
return f" - 重命名: {self.old_type} {self.old_name} -> {self.param_type} {self.param_name}" lines.append(f"**{index}. `{self.param_name}`** {self._change_tag()}")
if self.change_type == ChangeType.MODIFIED: lines.append(f"> `{self.old_name}` → `{self.param_name}`")
parts = [f" - 修改: {self.param_name}"] if desc:
if self.detail: lines.append(f"> 说明:{desc}")
parts.append(f" ({self.detail})") return "\n".join(lines)
return "".join(parts)
return f" - {self.change_type.value}: {self.param_name}" 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 @dataclass
@@ -61,9 +107,14 @@ class EndpointChangeReport:
http_method: str http_method: str
controller_class: str controller_class: str
method_name: str method_name: str
source_file: str = ""
parameter_changes: List[ParameterChange] = field(default_factory=list) parameter_changes: List[ParameterChange] = field(default_factory=list)
is_new_endpoint: bool = False is_new_endpoint: bool = False
is_removed_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 @property
def has_changes(self) -> bool: def has_changes(self) -> bool:
@@ -71,6 +122,8 @@ class EndpointChangeReport:
return ( return (
self.is_new_endpoint self.is_new_endpoint
or self.is_removed_endpoint or self.is_removed_endpoint
or self.is_renamed_endpoint
or self.is_method_changed
or len(self.parameter_changes) > 0 or len(self.parameter_changes) > 0
) )
@@ -84,6 +137,11 @@ def _param_key(p: ApiParameter) -> Tuple[str, str]:
return (p.source, p.name) 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( def compare_parameters(
old_params: List[ApiParameter], new_params: List[ApiParameter] old_params: List[ApiParameter], new_params: List[ApiParameter]
) -> List[ParameterChange]: ) -> List[ParameterChange]:
@@ -114,9 +172,16 @@ def compare_parameters(
new_p = new_map[key] new_p = new_map[key]
detail_parts = [] detail_parts = []
if old_p.type != new_p.type: 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: 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: if detail_parts:
changes.append( changes.append(
ParameterChange( ParameterChange(
@@ -126,6 +191,11 @@ def compare_parameters(
required=new_p.required, required=new_p.required,
old_required=old_p.required, old_required=old_p.required,
detail=", ".join(detail_parts), 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_name=r_param.name,
old_type=r_param.type, old_type=r_param.type,
required=a_param.required, 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) matched_removed.add(r_key)
@@ -168,6 +243,10 @@ def compare_parameters(
change_type=ChangeType.REMOVED, change_type=ChangeType.REMOVED,
param_name=param.name, param_name=param.name,
param_type=param.type, 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_name=param.name,
param_type=param.type, param_type=param.type,
required=param.required, 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 端点,生成变更报告列表。 对比新旧两个版本的全部 Controller 端点,生成变更报告列表。
:param old_endpoints: 旧版本 { endpoint_key: ApiEndpoint } 支持以下变更类型检测:
:param new_endpoints: 新版本 { endpoint_key: ApiEndpoint } - HTTP 方法变更GET → POST 等)
:return: 有变更的接口报告列表 - URI 路径变更(路径重命名)
- 新增 / 删除接口
- 参数变更
""" """
reports: List[EndpointChangeReport] = [] 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): removed_keys = old_keys - new_keys
old_ep = old_endpoints.get(key) added_keys = new_keys - old_keys
new_ep = new_endpoints.get(key) 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( reports.append(
EndpointChangeReport( EndpointChangeReport(
uri=new_ep.uri, uri=ep.uri,
http_method=new_ep.http_method, http_method=ep.http_method,
controller_class=new_ep.controller_class, controller_class=ep.controller_class,
method_name=new_ep.method_name, 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, is_new_endpoint=True,
parameter_changes=[ parameter_changes=[
ParameterChange( ParameterChange(
@@ -220,27 +419,20 @@ def compare_endpoints(
param_name=p.name, param_name=p.name,
param_type=p.type, param_type=p.type,
required=p.required, 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: # 5. 共同 URI对比参数变更
# 接口被删除 for key in common_keys:
reports.append( old_ep = old_endpoints[key]
EndpointChangeReport( new_ep = new_endpoints[key]
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 对比参数
param_changes = compare_parameters(old_ep.parameters, new_ep.parameters) param_changes = compare_parameters(old_ep.parameters, new_ep.parameters)
if param_changes: if param_changes:
reports.append( reports.append(
@@ -249,6 +441,7 @@ def compare_endpoints(
http_method=new_ep.http_method, http_method=new_ep.http_method,
controller_class=new_ep.controller_class, controller_class=new_ep.controller_class,
method_name=new_ep.method_name, method_name=new_ep.method_name,
source_file=new_ep.source_file,
parameter_changes=param_changes, parameter_changes=param_changes,
) )
) )

View File

@@ -7,12 +7,13 @@ from __future__ import annotations
import re import re
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional, Union from typing import Dict, List, Optional
import javalang import javalang
from javalang.tree import ( from javalang.tree import (
Annotation, Annotation,
ClassDeclaration, ClassDeclaration,
ElementValuePair,
FieldDeclaration, FieldDeclaration,
FormalParameter, FormalParameter,
Literal, Literal,
@@ -22,9 +23,49 @@ from javalang.tree import (
from models import ApiEndpoint, ApiParameter from models import ApiEndpoint, ApiParameter
# javax.validation 必填注解
REQUIRED_FIELD_ANNS = {"NotNull", "NotEmpty", "NotBlank"}
MAPPING_ANNS = {"GetMapping", "PostMapping", "PutMapping", "DeleteMapping", "PatchMapping", "RequestMapping"} MAPPING_ANNS = {"GetMapping", "PostMapping", "PutMapping", "DeleteMapping", "PatchMapping", "RequestMapping"}
CONTROLLER_ANNS = {"RestController", "Controller"} 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: def _ann_simple_name(ann: Annotation) -> str:
"""获取注解简单类名。""" """获取注解简单类名。"""
@@ -32,14 +73,18 @@ def _ann_simple_name(ann: Annotation) -> str:
def _literal_str(node) -> str: def _literal_str(node) -> str:
"""Literal 节点提取字符串值。""" """AST 节点提取字符串或布尔值。"""
if node is None: if node is None:
return "" return ""
if isinstance(node, Literal): if isinstance(node, Literal):
v = node.value or "" v = node.value
return str(v).strip('"').strip("'") if isinstance(v, bool):
return str(v).lower()
return str(v or "").strip('"').strip("'")
if isinstance(node, MemberReference): if isinstance(node, MemberReference):
return node.member return node.member
if isinstance(node, bool):
return str(node).lower()
return str(node).strip('"').strip("'") return str(node).strip('"').strip("'")
@@ -52,12 +97,12 @@ def _collect_ann_members(ann: Annotation) -> Dict[str, str]:
el = ann.element el = ann.element
if el is None: if el is None:
return members return members
if isinstance(el, list): if isinstance(el, ElementValuePair):
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"):
members[el.name] = _literal_str(el.value) 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: else:
members["value"] = _literal_str(el) members["value"] = _literal_str(el)
return members return members
@@ -116,7 +161,10 @@ def _join_paths(base: str, sub: str) -> str:
def _http_method(ann_name: str, ann: Annotation) -> str: def _http_method(ann_name: str, ann: Annotation) -> str:
"""从映射注解推断 HTTP 方法。""" """从映射注解推断 HTTP 方法。
支持大小写不敏感匹配,避免 PUTMapping、POSTMapping 等不规范写法导致解析失败。
"""
mapping = { mapping = {
"GetMapping": "GET", "GetMapping": "GET",
"PostMapping": "POST", "PostMapping": "POST",
@@ -124,9 +172,11 @@ def _http_method(ann_name: str, ann: Annotation) -> str:
"DeleteMapping": "DELETE", "DeleteMapping": "DELETE",
"PatchMapping": "PATCH", "PatchMapping": "PATCH",
} }
if ann_name in mapping: # 大小写不敏感匹配
return mapping[ann_name] for key, value in mapping.items():
if ann_name == "RequestMapping": if key.lower() == ann_name.lower():
return value
if ann_name.lower() == "requestmapping":
m = _ann_string(ann, "method") m = _ann_string(ann, "method")
if m: if m:
return m.replace("RequestMethod.", "").upper() return m.replace("RequestMethod.", "").upper()
@@ -185,20 +235,89 @@ def _param_required(param: FormalParameter) -> bool:
return not _has_ann(param, "Nullable") 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: class ControllerAstParser:
""" """
基于 javalang 的 Controller 解析器。 基于 javalang 的 Controller 解析器。
只解析传入的文件不扫描整个目录CI 更快)。 只解析传入的文件不扫描整个目录CI 更快)。
""" """
def __init__(self, repo_root: Path, source_dir: Path): def __init__(self, repo_root: Path, source_dirs: List[Path]):
""" """
:param repo_root: 仓库根目录 :param repo_root: 仓库根目录
:param source_dir: Java 源码根目录repo_root 下的相对路径对应的绝对路径 :param source_dirs: Java 源码根目录列表(用于查找 DTO 等
""" """
self.repo_root = repo_root self.repo_root = repo_root
self.source_dir = source_dir self.source_dirs = source_dirs
self._dto_cache: Dict[str, List[ApiParameter]] = {} self._dto_cache: Dict[str, List[ApiParameter]] = {}
self._current_source = ""
def parse_file_content(self, source: str, repo_relative_path: str) -> List[ApiEndpoint]: def parse_file_content(self, source: str, repo_relative_path: str) -> List[ApiEndpoint]:
""" """
@@ -209,6 +328,7 @@ class ControllerAstParser:
:return: 端点列表 :return: 端点列表
""" """
endpoints: List[ApiEndpoint] = [] endpoints: List[ApiEndpoint] = []
self._current_source = source
try: try:
tree = javalang.parse.parse(source) tree = javalang.parse.parse(source)
except (javalang.parser.JavaSyntaxError, RecursionError) as exc: except (javalang.parser.JavaSyntaxError, RecursionError) as exc:
@@ -252,9 +372,16 @@ class ControllerAstParser:
continue continue
method_path = _ann_string(ann, "value", "path") 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 = [] params = []
for p in method.parameters or []: for p in method.parameters or []:
params.extend(self._extract_param(p)) params.extend(self._extract_param(p, javadoc_params))
return ApiEndpoint( return ApiEndpoint(
http_method=_http_method(ann_name, ann), http_method=_http_method(ann_name, ann),
@@ -266,38 +393,82 @@ class ControllerAstParser:
) )
return None return None
def _extract_param(self, param: FormalParameter) -> List[ApiParameter]: def _extract_param(
"""提取方法参数,@RequestBody 展开 DTO 字段。""" self, param: FormalParameter, javadoc_params: Optional[Dict[str, str]] = None
) -> List[ApiParameter]:
"""提取方法参数,@RequestBody 展开 DTO 字段;忽略框架注入参数。"""
type_name = _type_to_str(param.type) type_name = _type_to_str(param.type)
if _has_ann(param, "RequestBody"): name = _param_name(param)
return self._expand_dto(type_name, "body") 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 [ return [
ApiParameter( ApiParameter(
name=_param_name(param), name=name,
type=type_name, type=type_name,
required=_param_required(param), required=_param_required(param),
source=_param_source(param), source=_param_source(param),
description=description,
) )
] ]
def _expand_dto(self, type_name: str, source: str) -> List[ApiParameter]: def _expand_dto(
"""展开 @RequestBody 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() simple = type_name.split(".")[-1].replace(">", "").replace("<", "").strip()
if simple in self._dto_cache: cache_key = f"{simple}:{body_param_name}"
return self._dto_cache[simple] if cache_key in self._dto_cache:
return self._dto_cache[cache_key]
dto_file = self._find_dto_file(simple) dto_file = self._find_dto_file(simple)
if not dto_file: if not dto_file:
result = [ApiParameter(name=simple, type=type_name, required=True, source=source)] result = [
self._dto_cache[simple] = 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 return result
try: 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): except (javalang.parser.JavaSyntaxError, OSError):
result = [ApiParameter(name=simple, type=type_name, required=True, source=source)] result = [
self._dto_cache[simple] = 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 return result
fields: List[ApiParameter] = [] fields: List[ApiParameter] = []
@@ -307,56 +478,81 @@ class ControllerAstParser:
for field in type_decl.fields or []: for field in type_decl.fields or []:
if "static" in (field.modifiers or []): if "static" in (field.modifiers or []):
continue 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: for decl in field.declarators:
fields.append( fields.append(
ApiParameter( ApiParameter(
name=decl.name, name=decl.name,
type=_type_to_str(field.type), type=_type_to_str(field.type),
required=not _has_ann(field, "Nullable"), required=_field_required(field),
source=source, source=source,
description=field_desc,
parent_dto=simple,
body_param_name=body_param_name or None,
) )
) )
if not fields: 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 return fields
def _find_dto_file(self, simple_name: str) -> Optional[Path]: def _find_dto_file(self, simple_name: str) -> Optional[Path]:
"""在源码目录中查找 DTO 文件。""" """配置的源码目录及仓库内 src/main/java 中查找 DTO 文件。"""
if not self.source_dir.exists():
return None
target = f"{simple_name}.java" target = f"{simple_name}.java"
for path in self.source_dir.rglob(target): for source_dir in self.source_dirs:
return path 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 return None
def parse_controller_files( def parse_controller_files(
repo_root: Path, repo_root: Path,
source_subdir: str, source_subdirs: List[str],
file_paths: List[str], file_paths: List[str],
file_contents: Dict[str, str], file_contents: Dict[str, str],
) -> List[ApiEndpoint]: ) -> List[ApiEndpoint]:
""" """
批量解析指定 Controller 文件(仅解析传入的文件,不全量扫描)。 批量解析指定 Controller 文件(仅解析传入的文件,不全量扫描)。
:param repo_root: 仓库根目录 :param repo_root: 仓库根目录
:param source_subdir: 源码子目录(相对仓库根) :param source_subdirs: 源码子目录列表(相对仓库根)
:param file_paths: 要解析的文件路径列表(相对仓库根) :param file_paths: 要解析的文件路径列表(相对仓库根)
:param file_contents: {文件路径: 源码内容} :param file_contents: {文件路径: 源码内容}
:return: 所有端点 :return: 所有端点
""" """
source_dir = repo_root / source_subdir.replace("/", Path.sep) source_dirs = [(repo_root / sub).resolve() for sub in source_subdirs]
parser = ControllerAstParser(repo_root, source_dir) parser = ControllerAstParser(repo_root, source_dirs)
endpoints: List[ApiEndpoint] = [] endpoints: List[ApiEndpoint] = []
for path in file_paths: for path in file_paths:
content = file_contents.get(path) norm = path.replace("\\", "/")
content = file_contents.get(norm)
if not content: if not content:
continue continue
norm = path.replace("\\", "/")
endpoints.extend(parser.parse_file_content(content, norm)) endpoints.extend(parser.parse_file_content(content, norm))
return endpoints return endpoints

View File

@@ -25,19 +25,19 @@ def filter_endpoints_by_files(
def parse_endpoints_from_files( def parse_endpoints_from_files(
repo_root: Path, repo_root: Path,
source_subdir: str, source_subdirs: List[str],
file_paths: List[str], file_paths: List[str],
file_contents: Dict[str, str], file_contents: Dict[str, str],
) -> List[ApiEndpoint]: ) -> List[ApiEndpoint]:
""" """
解析指定 Controller 文件,提取接口参数(仅解析传入文件,不全量扫描)。 解析指定 Controller 文件,提取接口参数(仅解析传入文件,不全量扫描)。
:param repo_root: 仓库根 :param repo_root: 仓库根
:param source_subdir: 源码目录(相对仓库根) :param source_subdirs: 源码目录列表(相对仓库根)
:param file_paths: 文件路径列表 :param file_paths: 文件路径列表
:param file_contents: 路径 -> 源码内容 :param file_contents: 路径 -> 源码内容
:return: ApiEndpoint 列表 :return: ApiEndpoint 列表
""" """
from controller_ast_parser import parse_controller_files 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 接口参数变更审核模块。 豆包 LLM 接口参数变更审核模块。
仅审核 Controller 层接口参数的增删改,不对 Java 源码做通用代码审查 LLM 仅输出简短的兼容性提示,详细变更由 AST + Markdown 通知展示
""" """
import json import json
@@ -10,14 +10,17 @@ import requests
from comparator import EndpointChangeReport 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: def is_llm_enabled(config: Dict[str, Any]) -> bool:
""" """判断大模型总开关是否开启。"""
判断大模型总开关是否开启。
:param config: 完整配置字典
:return: True=启用 LLM 审核
"""
return config.get("llm", {}).get("enabled", True) return config.get("llm", {}).get("enabled", True)
@@ -26,14 +29,7 @@ def call_doubao_api(
prompt: str, prompt: str,
config: Dict[str, Any], config: Dict[str, Any],
) -> Optional[str]: ) -> Optional[str]:
""" """调用豆包 API。"""
调用火山引擎豆包 Chat Completions API。
:param api_key: API Key
:param prompt: 用户提示词
:param config: 完整配置
:return: LLM 回复文本;失败返回 None
"""
if not api_key or api_key == "YOUR_DOUBAO_API_KEY": if not api_key or api_key == "YOUR_DOUBAO_API_KEY":
print("[警告] 未配置豆包 API Key跳过 LLM 审核。") print("[警告] 未配置豆包 API Key跳过 LLM 审核。")
return None return None
@@ -41,7 +37,7 @@ def call_doubao_api(
llm_cfg = config.get("llm", {}) llm_cfg = config.get("llm", {})
model = llm_cfg.get("model") or llm_cfg.get("endpoint_id", "") model = llm_cfg.get("model") or llm_cfg.get("endpoint_id", "")
if not model: if not model:
print("[警告] 未配置 llm.model 或 llm.endpoint_id,跳过 LLM 审核。") print("[警告] 未配置 llm.model跳过 LLM 审核。")
return None return None
api_url = llm_cfg.get( api_url = llm_cfg.get(
@@ -59,9 +55,9 @@ def call_doubao_api(
{ {
"role": "system", "role": "system",
"content": ( "content": (
"你是 Java Spring Boot Controller 接口参数变更分析专家。" "你是 Java Spring Boot API 变更分析专家。"
"的职责是识别并整理 Controller 层接口参数的增、删、改、重命名," "只负责输出简短的兼容性风险提示,不重复罗列接口参数明细。"
"确认 AST 解析结果是否准确,并指出对调用方的兼容性影响。" + FRAMEWORK_IGNORE_HINT
), ),
}, },
{"role": "user", "content": prompt}, {"role": "user", "content": prompt},
@@ -73,13 +69,11 @@ def call_doubao_api(
kwargs = {"headers": headers, "json": payload} kwargs = {"headers": headers, "json": payload}
if timeout is not None: if timeout is not None:
kwargs["timeout"] = timeout kwargs["timeout"] = timeout
resp = requests.post(api_url, **kwargs) resp = requests.post(api_url, **kwargs)
resp.raise_for_status() resp.raise_for_status()
data = resp.json() data = resp.json()
if "choices" in data and data["choices"]: if "choices" in data and data["choices"]:
return data["choices"][0]["message"]["content"] return data["choices"][0]["message"]["content"]
print("[错误] AI 返回格式异常")
return None return None
except requests.RequestException as exc: except requests.RequestException as exc:
print(f"[错误] 豆包 API 调用失败: {exc}") print(f"[错误] 豆包 API 调用失败: {exc}")
@@ -92,66 +86,49 @@ def build_parameter_change_prompt(
git_diff: str = "", git_diff: str = "",
) -> str: ) -> str:
""" """
构造接口参数变更审核提示词(对齐第一版需求:识别 Controller 参数增删改) 构造 LLM 提示词:只要求输出兼容性摘要,不要求重复参数列表
:param reports: AST 解析对比结果
:param changed_files: 本次变更的 Controller 文件列表
:param git_diff: 相关文件的 Git diff 内容
:return: 完整 prompt
""" """
ast_report = [] ast_report = []
for r in reports: for r in reports:
ast_report.append( ast_report.append(
{ {
"uri": f"{r.http_method} {r.uri}", "uri": f"{r.http_method} {r.uri}",
"controller": r.controller_class, "is_new": r.is_new_endpoint,
"method": r.method_name, "is_removed": r.is_removed_endpoint,
"is_new_endpoint": r.is_new_endpoint, "changes": [
"is_removed_endpoint": r.is_removed_endpoint,
"parameter_changes": [
{ {
"type": c.change_type.value, "type": c.change_type.value,
"name": c.param_name, "name": c.param_name,
"java_type": c.param_type, "java_type": c.param_type,
"old_name": c.old_name,
"old_type": c.old_type,
"required": c.required, "required": c.required,
"detail": c.detail,
} }
for c in r.parameter_changes for c in r.parameter_changes
], ],
} }
) )
diff_block = git_diff.strip() if git_diff.strip() else "(无 diff 内容" diff_block = git_diff.strip()[:6000] if git_diff.strip() else "(无)"
if len(diff_block) > 8000:
diff_block = diff_block[:8000] + "\n... [diff 过长,已截断]"
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)} {json.dumps(changed_files, ensure_ascii=False)}
## AST 自动解析的参数变更报告 ## AST 变更摘要
{json.dumps(ast_report, ensure_ascii=False, indent=2)} {json.dumps(ast_report, ensure_ascii=False, indent=2)}
## Git DiffController 相关) ## Git Diff
```diff
{diff_block} {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], changed_files: List[str],
git_diff: str = "", git_diff: str = "",
) -> Optional[str]: ) -> Optional[str]:
""" """LLM 审核,返回简短兼容性提示。"""
使用 LLM 审核 Controller 接口参数变更AST 结果的二次确认与整理)。 if not is_llm_enabled(config) or not reports:
: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:
return None return None
llm_cfg = config.get("llm", {}) llm_cfg = config.get("llm", {})

View File

@@ -42,6 +42,14 @@ def load_config(config_path: Path) -> dict:
return yaml.safe_load(f) or {} 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: def _read_file_safe(path: Path) -> str:
"""读取文件内容。""" """读取文件内容。"""
try: try:
@@ -73,7 +81,7 @@ def _load_version_contents(
def parse_changed_endpoints( def parse_changed_endpoints(
repo_root: Path, repo_root: Path,
source_subdir: str, source_subdirs: list,
changed_files: list, changed_files: list,
old_sha: str, old_sha: str,
label: str, label: str,
@@ -86,7 +94,7 @@ def parse_changed_endpoints(
print(f"[AST] 解析 {label} 版本 {len(contents)} 个 Controller 文件") print(f"[AST] 解析 {label} 版本 {len(contents)} 个 Controller 文件")
endpoints = parse_endpoints_from_files( 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)} 个接口") print(f"[AST] {label} 版本共 {len(endpoints)} 个接口")
return endpoints_to_map(endpoints) return endpoints_to_map(endpoints)
@@ -111,7 +119,11 @@ def main() -> int:
config_path = repo_root / config_path config_path = repo_root / config_path
config = load_config(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() commit_info = get_current_commit()
push_user = args.push_user or commit_info.author push_user = args.push_user or commit_info.author
@@ -121,6 +133,8 @@ def main() -> int:
print("=" * 40) print("=" * 40)
print(f"推送人: {push_user}") print(f"推送人: {push_user}")
print(f"推送时间: {push_time}") 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"LLM 审核: {config.get('llm', {}).get('enabled', True)}")
print(f"记录日志: {config.get('log', {}).get('enabled', False)}") print(f"记录日志: {config.get('log', {}).get('enabled', False)}")
print("=" * 40) print("=" * 40)
@@ -130,7 +144,7 @@ def main() -> int:
print("[Git] 首次提交,无可对比版本,跳过。") print("[Git] 首次提交,无可对比版本,跳过。")
return 0 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: if not changed_files:
print("[Git] 本次提交未变更 Controller 文件,跳过。") print("[Git] 本次提交未变更 Controller 文件,跳过。")
return 0 return 0
@@ -142,10 +156,10 @@ def main() -> int:
git_diff = get_controller_files_diff(prev_sha, commit_info.sha, changed_files) git_diff = get_controller_files_diff(prev_sha, commit_info.sha, changed_files)
new_map = parse_changed_endpoints( 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( 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( new_filtered = endpoints_to_map(

View File

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

View File

@@ -1,162 +1,283 @@
""" """
企业微信机器人通知模块。 企业微信 Markdown 通知模块。
按第一版模板发送 Controller 接口参数变更通知,支持超长内容分段发送 支持加粗、颜色info/comment/warning新增接口与变更接口使用不同展示模板
""" """
import json import json
from typing import List, Optional import re
from collections import OrderedDict
from typing import List, Optional, Tuple
import requests import requests
from comparator import EndpointChangeReport from comparator import EndpointChangeReport, ParameterChange
from git_utils import CommitInfo
# 企微 text 消息字节上限约 2048留余量按字符分段 # 企微 Markdown 单条上限 4096 字符,留余量
MAX_TEXT_LENGTH = 2000 MAX_MD_LENGTH = 3800
def truncate_text(text: str, max_length: int = MAX_TEXT_LENGTH) -> str: def truncate_text(text: str, max_length: int = MAX_MD_LENGTH) -> str:
""" """截断超长消息。"""
截断文本,避免超出企微单条消息限制。
:param text: 原始文本
:param max_length: 最大字符数
:return: 截断后文本
"""
if len(text) <= max_length: if len(text) <= max_length:
return text return text
return text[:max_length] + "\n... [消息过长,已截断]" return text[:max_length] + "\n\n<font color=\"comment\">... 消息过长,已截断</font>"
def build_single_endpoint_message( def _format_param_change_list(changes: List[ParameterChange]) -> List[str]:
report: EndpointChangeReport, """生成企微友好的普通参数变更列表(卡片式)。"""
push_user: str, if not changes:
push_time: str, return ['<font color="comment">无</font>']
) -> str: 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:
""" """
按第一版模板构建单个接口的通知正文 格式化单个接口块,按模板匹配格式输出
全路径类名显示为 source_file相对仓库根的完整 .java 路径)。
模板示例:
[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: 通知文本
""" """
lines = [ change_type = "新增接口" if report.is_new_endpoint else ("删除接口" if report.is_removed_endpoint else "修改参数")
"[API变更通知]", uri_line = f"**{report.http_method}** `{report.uri}`"
f"URI: {report.http_method} {report.uri}", file_path = report.source_file or report.controller_class
f"修改人:{push_user}", class_line = f"- **全路径类名:** <font color=\"info\">**{file_path}**</font>"
f"修改时间:{push_time}",
header = [
f"- **变更类型:** <font color=\"warning\">**{change_type}**</font>",
f"- **URI** {uri_line}",
class_line,
] ]
if report.is_new_endpoint: if report.is_removed_endpoint:
lines.append("接口状态:新增接口") return "\n".join(header + ["", f"<font color=\"warning\">**该接口已被移除**</font>"])
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())
return "\n".join(lines) return "\n".join(header + _format_param_details_section(report))
def build_all_notifications( def build_markdown_notification(
reports: List[EndpointChangeReport], reports: List[EndpointChangeReport],
push_user: str, push_user: str,
push_time: str, push_time: str,
llm_review: Optional[str] = None, llm_summary: Optional[str] = None,
) -> List[str]: ) -> str:
""" """
将所有接口变更组装为通知消息列表,超长时自动分段 构建完整 Markdown 通知正文
:param reports: 变更报告列表 :param reports: AST 变更报告
:param push_user: 推送人 :param push_user: 推送人
:param push_time: 推送时间 :param push_time: 推送时间
:param llm_review: LLM 参数变更审核结论(可选,附在最后一条或单独一条 :param llm_summary: LLM 兼容性摘要(可选,简短
:return: 待发送的消息段落列表 :return: Markdown 文本
""" """
if not reports: parts: List[str] = []
return []
messages: List[str] = [] # 所有 API 级变更(新增、修改路径、修改请求方式、删除、参数变更)统一走 model1.md 路径变更通知
current = "" 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: # 1. 新增接口 → 走 API路径变更通知
block = build_single_endpoint_message(report, push_user, push_time) for report in new_reports:
separator = "\n\n---\n\n" 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: # 2. 修改请求方式 → 使用独立的新模板 【API请求方式变更通知】
current = block for report in method_changed_reports:
elif len(current) + len(separator) + len(block) <= MAX_TEXT_LENGTH: method_md = build_method_change_markdown(
current += separator + block uri=report.uri,
else: old_method=report.old_http_method or "?",
messages.append(current) new_method=report.http_method,
current = block 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: # 3. 修改路径 → 走 API路径变更通知
messages.append(current) 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 审核结论单独或追加发送 # 4. 删除接口 → 走 API路径变更通知
if llm_review: for report in removed_reports:
review_msg = f"[AI参数变更审核]\n修改人:{push_user}\n修改时间:{push_time}\n\n{llm_review}" path_md = build_path_change_markdown(
if messages and len(messages[-1]) + len(review_msg) + 4 <= MAX_TEXT_LENGTH: old_uri=report.uri,
messages[-1] += "\n\n" + review_msg new_uri="已删除",
elif len(review_msg) <= MAX_TEXT_LENGTH: change_type="删除接口",
messages.append(review_msg) push_user=push_user,
else: push_time=push_time,
messages.extend(_split_long_text(review_msg, MAX_TEXT_LENGTH)) 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") lines = text.split("\n")
chunks: List[str] = [] chunks: List[str] = []
current = "" current: List[str] = []
for line in lines: for line in lines:
candidate = current + line + "\n" if line.startswith("### ") and current and len("\n".join(current)) > 200:
if len(candidate) > max_len and current: chunks.append("\n".join(current))
chunks.append(current.rstrip()) current = [line]
current = line + "\n"
else: else:
current = candidate current.append(line)
if current.strip(): if len("\n".join(current)) >= max_len:
chunks.append(current.rstrip()) chunks.append("\n".join(current))
return chunks 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: def _post_wecom_markdown(webhook_url: str, content: str) -> bool:
""" """发送企微 Markdown 消息。"""
发送单条 text 消息到企业微信。
:param webhook_url: Webhook URL
:param content: 消息正文
:return: 是否成功
"""
if not webhook_url or "YOUR_WECOM_KEY" in webhook_url: if not webhook_url or "YOUR_WECOM_KEY" in webhook_url:
print("[警告] 未配置有效的企业微信 Webhook URL。") print("[警告] 未配置有效的企业微信 Webhook URL。")
print("--- 通知预览 ---") print("--- 通知预览 ---")
print(content[:800]) print(content[:1000])
return False return False
payload = { payload = {
"msgtype": "text", "msgtype": "markdown",
"text": {"content": truncate_text(content)}, "markdown": {"content": truncate_text(content)},
} }
try: try:
@@ -184,35 +305,221 @@ def send_parameter_change_notification(
mentioned_users: Optional[List[str]] = None, mentioned_users: Optional[List[str]] = None,
) -> int: ) -> int:
""" """
发送 Controller 接口参数变更通知(支持分段) 发送 Markdown 格式的接口变更通知
:param webhook_url: 企微 Webhook 严格按变更类型拆分,各自独立构建和发送企微通知:
:param reports: AST 参数变更报告 - 方法变更 → 独立调用 build_method_change_markdown
:param push_user: 推送人 - 路径变更(新增/修改/删除) → 独立调用 build_path_change_markdown
:param push_time: 推送时间 - 参数变更 → 独立调用 _format_endpoint_block
:param llm_review: LLM 参数变更审核(可选)
:param mentioned_users: @ 成员 userid 列表 不同类型之间完全互不干扰,各自走独立分支。
:return: 成功发送条数
""" """
if not reports and not llm_review: if not reports and not llm_review:
print("无接口参数变更,不发送到企业微信") print("无接口参数变更,不发送到企业微信")
return 0 return 0
segments = build_all_notifications(reports, push_user, push_time, llm_review) # 按类型严格分组(互不重叠)
if not segments: method_changed_reports = [r for r in reports if r.is_method_changed]
return 0 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 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 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: if sent > 0:
print(f"总共发送 {sent} 条通知到企业微信") print(f"总共发送 {sent} 条通知到企业微信")
return 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 # Python 依赖(版本锁定,避免 CI pip 冲突
PyYAML>=6.0.1 PyYAML==6.0.2
requests>=2.31.0 requests==2.32.3
javalang>=0.13.0 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/ 目录,与业务代码解耦) # AI-Check 配置文件(位于 .gitea/ 目录,与业务代码解耦)
# ============================================================ # ============================================================
# ---------- API 变动检查 ----------
# 总开关false 时跳过 Controller 接口参数变更检测(不对比、不通知)
check:
enabled: true
# 业务 Java 源码目录(相对仓库根目录) # 业务 Java 源码目录(相对仓库根目录)
# 单模块: src/main/java # 单模块: source_dir: "src/main/java"
# 多模块: ftb/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" source_dir: "ftb/src/main/java"
# ---------- 企业微信机器人 ---------- # ---------- 企业微信机器人 ----------
@@ -13,7 +21,7 @@ wecom:
# ---------- 豆包 LLM审核接口参数变更---------- # ---------- 豆包 LLM审核接口参数变更----------
llm: llm:
enabled: true enabled: false
api_key: "2f3f7ee9-a6f7-46b7-a709-a36743a83a04" api_key: "2f3f7ee9-a6f7-46b7-a709-a36743a83a04"
model: "doubao-seed-1-8-251228" model: "doubao-seed-1-8-251228"
endpoint_id: "" 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 exit 1
fi fi
- name: 安装 Python 依赖 - name: 安装 Python 依赖
run: | run: |
sudo apt-get update if ! python3 -m venv .gitea/.venv 2>/dev/null; then
sudo apt-get install -y python3 python3-pip sudo apt-get update -qq
python3 -m pip install --break-system-packages -r .gitea/checker/requirements.txt 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 接口参数变更 - name: 检测 Controller 接口参数变更
run: | run: |
COMMIT_TIME=$(git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S') 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 \ --config .gitea/config.yaml \
--repo-root . \ --repo-root . \
"${{ gitea.actor }}" \ "${{ gitea.actor }}" \

View File

@@ -2,7 +2,9 @@
<module type="JAVA_MODULE" version="4"> <module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true"> <component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output /> <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="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </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") Long id, @RequestParam(value = "includeDisabled", 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