feat(opportunity): 新增全量商机查询内部接口
1. 新增OpportunityFullDTO封装全量商机精简字段 2. 实现listAllOpportunities接口及Mapper、SQL查询,返回全量商机不含数据权限过滤 3. 新增/api/opportunities/all内部接口,支持X-Internal-Secret鉴权 4. 配置放行接口路径到生产和开发环境配置文件 5. 补充完整接口文档说明main
parent
010d3052c8
commit
8477aac22f
|
|
@ -5,41 +5,13 @@
|
|||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="4c558d98-824e-4a48-ba48-bd2e6172f9f4" name="更改" comment="修改定位信息 0323">
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/controller/OmsCallbackController.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/controller/OmsCallbackController.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/dto/opportunity/OpportunityDuplicateCheckDTO.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/dto/opportunity/OpportunityDuplicateCheckDTO.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/mapper/LlmMcpMapper.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/mapper/LlmMcpMapper.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/security/McpDataPermissionInterceptor.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/security/McpDataPermissionInterceptor.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmCheckinSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmCheckinSearchToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmDetailSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmDetailSearchToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmDictOptionsToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmDictOptionsToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmEntityDetailToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmEntityDetailToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmExpansionSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmExpansionSearchToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmFollowupSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmFollowupSearchToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmMetadataToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmMetadataToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmOrgUserSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmOrgUserSearchToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmReportCatalogToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmReportCatalogToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmReportQueryToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmReportQueryToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmTodoSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmTodoSearchToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmUniversalSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmUniversalSearchToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmWorkTodayStatusToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmWorkTodayStatusToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/OpportunitySearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/OpportunitySearchToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/UserProfileToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/UserProfileToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/WorkReportSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/WorkReportSearchToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/support/PermissionedMcpToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/support/PermissionedMcpToolProvider.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/controller/OpportunityController.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/controller/OpportunityController.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/mapper/OpportunityMapper.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/mapper/OpportunityMapper.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/service/OpportunityService.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/service/OpportunityService.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/service/impl/OpportunityServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/service/impl/OpportunityServiceImpl.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/resources/application-prod.yml" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/resources/application-prod.yml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/resources/application.yml" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/resources/application.yml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/resources/mapper/llm/LlmMcpMapper.xml" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/resources/mapper/llm/LlmMcpMapper.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/main/resources/mapper/opportunity/OpportunityMapper.xml" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/resources/mapper/opportunity/OpportunityMapper.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/test/java/com/unis/crm/llm/security/McpDataPermissionInterceptorTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/test/java/com/unis/crm/llm/security/McpDataPermissionInterceptorTest.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/backend/src/test/java/com/unis/crm/service/impl/OpportunityServiceImplTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/test/java/com/unis/crm/service/impl/OpportunityServiceImplTest.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/src/lib/auth.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/src/lib/auth.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/src/pages/Opportunities.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/src/pages/Opportunities.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/frontend/src/pages/Work.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/src/pages/Work.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/sql/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/sql/README.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/sql/init_full_pg17.sql" beforeDir="false" afterPath="$PROJECT_DIR$/sql/init_full_pg17.sql" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
|
|
@ -139,7 +111,8 @@
|
|||
<workItem from="1789033867432" duration="17000" />
|
||||
<workItem from="1789440226191" duration="937000" />
|
||||
<workItem from="1789459774783" duration="30000" />
|
||||
<workItem from="1789529046094" duration="3889000" />
|
||||
<workItem from="1789529046094" duration="5499000" />
|
||||
<workItem from="1789969706373" duration="858000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="修改定位信息 0323">
|
||||
<option name="closed" value="true" />
|
||||
|
|
|
|||
|
|
@ -2,18 +2,23 @@ package com.unis.crm.controller;
|
|||
|
||||
import com.unis.crm.common.ApiResponse;
|
||||
import com.unis.crm.common.CurrentUserUtils;
|
||||
import com.unis.crm.common.UnauthorizedException;
|
||||
import com.unis.crm.config.InternalAuthProperties;
|
||||
import com.unis.crm.dto.opportunity.CreateOpportunityFollowUpRequest;
|
||||
import com.unis.crm.dto.opportunity.CreateOpportunityRequest;
|
||||
import com.unis.crm.dto.opportunity.OmsPreSalesOptionDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityDuplicateCheckDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityFullDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityItemDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityMetaDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityOverviewDTO;
|
||||
import com.unis.crm.dto.opportunity.PushOpportunityToOmsRequest;
|
||||
import com.unis.crm.service.OpportunityService;
|
||||
import com.unisbase.common.annotation.Log;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
|
@ -28,10 +33,16 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
@RequestMapping("/api/opportunities")
|
||||
public class OpportunityController {
|
||||
|
||||
private final OpportunityService opportunityService;
|
||||
private static final String INTERNAL_UNAUTHORIZED_MESSAGE = "内部接口鉴权失败";
|
||||
|
||||
public OpportunityController(OpportunityService opportunityService) {
|
||||
private final OpportunityService opportunityService;
|
||||
private final InternalAuthProperties internalAuthProperties;
|
||||
|
||||
public OpportunityController(
|
||||
OpportunityService opportunityService,
|
||||
InternalAuthProperties internalAuthProperties) {
|
||||
this.opportunityService = opportunityService;
|
||||
this.internalAuthProperties = internalAuthProperties;
|
||||
}
|
||||
|
||||
@GetMapping("/meta")
|
||||
|
|
@ -39,6 +50,36 @@ public class OpportunityController {
|
|||
return ApiResponse.success(opportunityService.getMeta());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全量商机精简字段清单(含已归档/已签单,不做数据权限过滤)
|
||||
* <p>内部接口,使用 X-Internal-Secret 鉴权。</p>
|
||||
*/
|
||||
@GetMapping("/all")
|
||||
@Log(type = "商机集成", value = "查询全量商机")
|
||||
public ApiResponse<List<OpportunityFullDTO>> listAll(HttpServletRequest httpServletRequest) {
|
||||
validateInternalSecret(httpServletRequest);
|
||||
return ApiResponse.success(opportunityService.listAllOpportunities());
|
||||
}
|
||||
|
||||
private void validateInternalSecret(HttpServletRequest httpServletRequest) {
|
||||
if (!internalAuthProperties.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
String expectedSecret = trimToNull(internalAuthProperties.getSecret());
|
||||
String actualSecret = trimToNull(httpServletRequest.getHeader(internalAuthProperties.resolveHeaderName()));
|
||||
if (expectedSecret == null || !Objects.equals(expectedSecret, actualSecret)) {
|
||||
throw new UnauthorizedException(INTERNAL_UNAUTHORIZED_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = value.trim();
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
|
||||
@GetMapping("/overview")
|
||||
public ApiResponse<OpportunityOverviewDTO> getOverview(
|
||||
@RequestHeader("X-User-Id") Long userId,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
package com.unis.crm.dto.opportunity;
|
||||
|
||||
/**
|
||||
* 全量商机精简字段 DTO,用于对外提供全量商机清单。
|
||||
*/
|
||||
public class OpportunityFullDTO {
|
||||
|
||||
/** 商机编号 */
|
||||
private String opportunityCode;
|
||||
/** 商机名称 */
|
||||
private String opportunityName;
|
||||
/** 商机阶段编码 */
|
||||
private String stageCode;
|
||||
/** 商机阶段名称 */
|
||||
private String stage;
|
||||
/** 把握度等级(A/B/C) */
|
||||
private String confidence;
|
||||
/** 是否已推送OMS */
|
||||
private Boolean pushedToOms;
|
||||
/** 推送OMS时间 */
|
||||
private String omsPushTime;
|
||||
/** 创建时间 */
|
||||
private String createdAt;
|
||||
/** 商机负责人ID */
|
||||
private Long ownerUserId;
|
||||
/** 商机负责人姓名 */
|
||||
private String ownerName;
|
||||
|
||||
public String getOpportunityCode() {
|
||||
return opportunityCode;
|
||||
}
|
||||
|
||||
public void setOpportunityCode(String opportunityCode) {
|
||||
this.opportunityCode = opportunityCode;
|
||||
}
|
||||
|
||||
public String getOpportunityName() {
|
||||
return opportunityName;
|
||||
}
|
||||
|
||||
public void setOpportunityName(String opportunityName) {
|
||||
this.opportunityName = opportunityName;
|
||||
}
|
||||
|
||||
public String getStageCode() {
|
||||
return stageCode;
|
||||
}
|
||||
|
||||
public void setStageCode(String stageCode) {
|
||||
this.stageCode = stageCode;
|
||||
}
|
||||
|
||||
public String getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
public void setStage(String stage) {
|
||||
this.stage = stage;
|
||||
}
|
||||
|
||||
public String getConfidence() {
|
||||
return confidence;
|
||||
}
|
||||
|
||||
public void setConfidence(String confidence) {
|
||||
this.confidence = confidence;
|
||||
}
|
||||
|
||||
public Boolean getPushedToOms() {
|
||||
return pushedToOms;
|
||||
}
|
||||
|
||||
public void setPushedToOms(Boolean pushedToOms) {
|
||||
this.pushedToOms = pushedToOms;
|
||||
}
|
||||
|
||||
public String getOmsPushTime() {
|
||||
return omsPushTime;
|
||||
}
|
||||
|
||||
public void setOmsPushTime(String omsPushTime) {
|
||||
this.omsPushTime = omsPushTime;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(String createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public Long getOwnerUserId() {
|
||||
return ownerUserId;
|
||||
}
|
||||
|
||||
public void setOwnerUserId(Long ownerUserId) {
|
||||
this.ownerUserId = ownerUserId;
|
||||
}
|
||||
|
||||
public String getOwnerName() {
|
||||
return ownerName;
|
||||
}
|
||||
|
||||
public void setOwnerName(String ownerName) {
|
||||
this.ownerName = ownerName;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import com.unis.crm.dto.opportunity.OpportunityCustomerSnapshotDTO;
|
|||
import com.unis.crm.dto.opportunity.OpportunityDictOptionDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityDuplicateCheckDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityFollowUpDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityFullDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityIntegrationTargetDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityItemDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityOmsPushDataDTO;
|
||||
|
|
@ -59,6 +60,11 @@ public interface OpportunityMapper {
|
|||
@Param("excludeStageCodes") List<String> excludeStageCodes,
|
||||
@Param("stageCodes") List<String> stageCodes);
|
||||
|
||||
/**
|
||||
* 查询全量商机精简字段(含已归档/已签单,不做数据权限过滤)
|
||||
*/
|
||||
List<OpportunityFullDTO> selectAllOpportunities();
|
||||
|
||||
OpportunityItemDTO selectOpportunityDetail(
|
||||
@Param("userId") Long userId,
|
||||
@Param("opportunityId") Long opportunityId,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.unis.crm.dto.opportunity.CreateOpportunityFollowUpRequest;
|
|||
import com.unis.crm.dto.opportunity.CreateOpportunityRequest;
|
||||
import com.unis.crm.dto.opportunity.OmsPreSalesOptionDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityDuplicateCheckDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityFullDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityItemDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityMetaDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityOverviewDTO;
|
||||
|
|
@ -15,6 +16,11 @@ public interface OpportunityService {
|
|||
|
||||
OpportunityMetaDTO getMeta();
|
||||
|
||||
/**
|
||||
* 查询全量商机精简字段(含已归档/已签单,不做数据权限过滤)
|
||||
*/
|
||||
List<OpportunityFullDTO> listAllOpportunities();
|
||||
|
||||
OpportunityOverviewDTO getOverview(Long userId, String keyword, String stage);
|
||||
|
||||
OpportunityOverviewDTO getOverview(Long userId, String keyword, String stage, boolean includeDetails);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.unis.crm.dto.opportunity.OpportunityDictOptionDTO;
|
|||
import com.unis.crm.dto.opportunity.OpportunityDuplicateCheckDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityMetaDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityFollowUpDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityFullDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityIntegrationTargetDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityItemDTO;
|
||||
import com.unis.crm.dto.opportunity.OpportunityOmsPushDataDTO;
|
||||
|
|
@ -207,6 +208,11 @@ public class OpportunityServiceImpl implements OpportunityService {
|
|||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OpportunityFullDTO> listAllOpportunities() {
|
||||
return opportunityMapper.selectAllOpportunities();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpportunityDuplicateCheckDTO checkDuplicateOpportunity(Long userId, String name, Long excludeId, List<String> excludeStageCodes) {
|
||||
if (isBlank(name)) {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ unisbase:
|
|||
permit-all-urls:
|
||||
- /actuator/health
|
||||
- /api/wecom/sso/**
|
||||
- /api/opportunities/all
|
||||
- /api/opportunities/integration/**
|
||||
- /api/oms/callback/order-return
|
||||
internal-auth:
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ unisbase:
|
|||
permit-all-urls:
|
||||
- /actuator/health
|
||||
- /api/wecom/sso/**
|
||||
- /api/opportunities/all
|
||||
- /api/opportunities/integration/**
|
||||
- /api/oms/callback/order-return
|
||||
internal-auth:
|
||||
|
|
|
|||
|
|
@ -322,6 +322,53 @@
|
|||
</if>
|
||||
</select>
|
||||
|
||||
<select id="selectAllOpportunities" resultType="com.unis.crm.dto.opportunity.OpportunityFullDTO">
|
||||
select
|
||||
coalesce(o.opportunity_code, '') as opportunityCode,
|
||||
coalesce(o.opportunity_name, '') as opportunityName,
|
||||
coalesce(stage_dict.item_value, o.stage, '') as stageCode,
|
||||
coalesce(
|
||||
stage_dict.item_label,
|
||||
case coalesce(o.stage, 'initial_contact')
|
||||
when 'initial_contact' then '初步沟通'
|
||||
when 'solution_discussion' then '方案交流'
|
||||
when 'bidding' then '招投标'
|
||||
when 'business_negotiation' then '商务谈判'
|
||||
when 'won' then '已成交'
|
||||
when 'lost' then 'L-已丢单'
|
||||
else coalesce(o.stage, '初步沟通')
|
||||
end
|
||||
) as stage,
|
||||
coalesce(o.confidence_pct, '') as confidence,
|
||||
coalesce(o.pushed_to_oms, false) as pushedToOms,
|
||||
coalesce(to_char(o.oms_push_time, 'YYYY-MM-DD HH24:MI'), '') as omsPushTime,
|
||||
coalesce(to_char(o.created_at, 'YYYY-MM-DD HH24:MI'), '') as createdAt,
|
||||
o.owner_user_id as ownerUserId,
|
||||
coalesce(nullif(btrim(u.display_name), ''), nullif(btrim(u.username), ''), '') as ownerName
|
||||
from crm_opportunity o
|
||||
left join sys_user u on u.user_id = o.owner_user_id
|
||||
left join sys_dict_item stage_dict
|
||||
on stage_dict.type_code = 'sj_xmjd'
|
||||
and (
|
||||
stage_dict.item_value = o.stage
|
||||
or stage_dict.item_label = case coalesce(o.stage, 'initial_contact')
|
||||
when 'initial_contact' then '初步沟通'
|
||||
when 'solution_discussion' then '方案交流'
|
||||
when 'bidding' then '招投标'
|
||||
when 'business_negotiation' then '商务谈判'
|
||||
when 'won' then '已成交'
|
||||
when 'lost' then 'L-已丢单'
|
||||
else o.stage
|
||||
end
|
||||
)
|
||||
and (
|
||||
stage_dict.status = 1
|
||||
or coalesce(o.archived, false) = true
|
||||
)
|
||||
and coalesce(stage_dict.is_deleted, 0) = 0
|
||||
order by o.created_at desc, o.id desc
|
||||
</select>
|
||||
|
||||
<select id="selectOpportunityDetail" resultType="com.unis.crm.dto.opportunity.OpportunityItemDTO">
|
||||
select
|
||||
o.id,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
# 全量商机查询接口文档
|
||||
|
||||
> 提供 CRM 全量商机精简字段清单,包含已归档/已签单商机,不分页。
|
||||
|
||||
## 基本信息
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 接口路径 | `GET /api/opportunities/all` |
|
||||
| 调用方 | 内部系统 / 系统间集成(不面向前端用户) |
|
||||
| 请求头鉴权 | `X-Internal-Secret`(内部接口密钥) |
|
||||
| 数据范围 | 全量商机(含已归档/已签单),不做数据权限过滤 |
|
||||
| 排序 | 按创建时间倒序,创建时间相同按 id 倒序 |
|
||||
| 分页 | 无(一次性返回全部) |
|
||||
|
||||
## 鉴权方式
|
||||
|
||||
与商机集成更新接口 `PUT /api/opportunities/integration/update`、OMS 退单回调接口保持一致,使用 `unisbase.internal-auth` 配置的密钥校验。
|
||||
|
||||
- 请求头字段:`X-Internal-Secret`(可通过 `unisbase.internal-auth.header-name` 覆盖)
|
||||
- 秘钥值:`unisbase.internal-auth.secret`
|
||||
- 校验逻辑:
|
||||
- 当 `unisbase.internal-auth.enabled=true` 时强制校验,头信息与配置密钥不一致则返回 `401`(`内部接口鉴权失败`);
|
||||
- 当 `enabled=false` 时跳过鉴权(仅用于本地开发)。
|
||||
- 该路径已加入 `unisbase.security.permit-all-urls`,**不需要** `Authorization: Bearer` 登录态。
|
||||
|
||||
## 请求
|
||||
|
||||
```
|
||||
GET /api/opportunities/all
|
||||
X-Internal-Secret: <配置的secret>
|
||||
```
|
||||
|
||||
无查询参数、无请求体。
|
||||
|
||||
## 响应
|
||||
|
||||
统一响应结构 `ApiResponse<List<OpportunityFullDTO>>`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `code` | string | 状态码,`"0"` 表示成功,`"-1"` 表示失败 |
|
||||
| `msg` | string | 提示信息,成功为 `success` |
|
||||
| `data` | array | 商机列表 |
|
||||
|
||||
`data` 元素字段:
|
||||
|
||||
| 字段 | 类型 | 对应库表字段 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `opportunityCode` | string | `crm_opportunity.opportunity_code` | 商机编号 |
|
||||
| `opportunityName` | string | `crm_opportunity.opportunity_name` | 商机名称 |
|
||||
| `stageCode` | string | `crm_opportunity.stage` | 商机阶段编码(取自字典 `sj_xmjd`,未匹配时回退原值) |
|
||||
| `stage` | string | 字典 `sj_xmjd.item_label` | 商机阶段名称(未匹配字典时按内置映射回退) |
|
||||
| `confidence` | string | `crm_opportunity.confidence_pct` | 把握度等级:`A` / `B` / `C` |
|
||||
| `pushedToOms` | boolean | `crm_opportunity.pushed_to_oms` | 是否已推送 OMS |
|
||||
| `omsPushTime` | string | `crm_opportunity.oms_push_time` | 推送 OMS 时间,格式 `yyyy-MM-dd HH:mm`,未推送为空串 |
|
||||
| `createdAt` | string | `crm_opportunity.created_at` | 创建时间,格式 `yyyy-MM-dd HH:mm` |
|
||||
| `ownerUserId` | long | `crm_opportunity.owner_user_id` | 商机负责人 ID |
|
||||
| `ownerName` | string | `sys_user.display_name` / `username` | 商机负责人姓名 |
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "0",
|
||||
"msg": "success",
|
||||
"data": [
|
||||
{
|
||||
"opportunityCode": "OPP-20260916-001",
|
||||
"opportunityName": "某某医院云桌面项目",
|
||||
"stageCode": "bidding",
|
||||
"stage": "招投标",
|
||||
"confidence": "A",
|
||||
"pushedToOms": true,
|
||||
"omsPushTime": "2026-09-16 10:30",
|
||||
"createdAt": "2026-09-10 09:12",
|
||||
"ownerUserId": 1001,
|
||||
"ownerName": "张三"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 实现说明
|
||||
|
||||
| 层 | 文件 |
|
||||
| --- | --- |
|
||||
| Controller | `backend/src/main/java/com/unis/crm/controller/OpportunityController.java`(`GET /all`) |
|
||||
| Service | `OpportunityService#listAllOpportunities` / `OpportunityServiceImpl` |
|
||||
| Mapper | `OpportunityMapper#selectAllOpportunities` |
|
||||
| SQL | `backend/src/main/resources/mapper/opportunity/OpportunityMapper.xml` |
|
||||
| DTO | `backend/src/main/java/com/unis/crm/dto/opportunity/OpportunityFullDTO.java` |
|
||||
|
||||
要点:
|
||||
|
||||
- 查询语句未加 `archived` 过滤,返回包含已归档/已签单在内的全部商机;
|
||||
- 未拼接数据权限条件(`opportunityVisibilityCondition`),即不受当前用户数据可见范围限制,因此**仅允许内部系统调用**;
|
||||
- 鉴权由 `OpportunityController#validateInternalSecret` 完成,并在 `application.yml` / `application-prod.yml` 的 `unisbase.security.permit-all-urls` 中放开 `/api/opportunities/all`;
|
||||
- 已添加 `@Log(type = "商机集成", value = "查询全量商机")` 审计日志,记录调用留痕;
|
||||
- 阶段名称通过 `sys_dict_item`(`type_code = 'sj_xmjd'`)关联获取,归档商机即使字典项被禁用也能取到名称;
|
||||
- 时间字段统一由数据库格式化后返回字符串,未推送 OMS 时 `omsPushTime` 返回空串。
|
||||
Loading…
Reference in New Issue