|
|
@@ -1,17 +1,23 @@
|
|
|
using Aspose.Cells;
|
|
|
using Aspose.Words;
|
|
|
+using Dm.util;
|
|
|
+using EyeSoft.Collections.Generic;
|
|
|
using EyeSoft.Extensions;
|
|
|
using Microsoft.IdentityModel.Tokens;
|
|
|
+using NPOI.SS.UserModel;
|
|
|
using OASystem.API.OAMethodLib;
|
|
|
+using OASystem.API.OAMethodLib.HunYuanAPI;
|
|
|
using OASystem.API.OAMethodLib.QiYeWeChatAPI.AppNotice;
|
|
|
using OASystem.Domain.AesEncryption;
|
|
|
using OASystem.Domain.Dtos.Groups;
|
|
|
+using OASystem.Domain.Entities.Customer;
|
|
|
using OASystem.Domain.Entities.Groups;
|
|
|
using OASystem.Domain.ViewModels.Groups;
|
|
|
using OASystem.Infrastructure.Repositories.Groups;
|
|
|
using System.ComponentModel.DataAnnotations;
|
|
|
using System.Data;
|
|
|
using System.Diagnostics;
|
|
|
+using TencentCloud.Common;
|
|
|
using static OASystem.API.OAMethodLib.JWTHelper;
|
|
|
|
|
|
namespace OASystem.API.Controllers
|
|
|
@@ -24,8 +30,10 @@ namespace OASystem.API.Controllers
|
|
|
public class ResourceController : ControllerBase
|
|
|
{
|
|
|
private readonly IMapper _mapper;
|
|
|
+ private readonly ILogger<ResourceController> _logger;
|
|
|
private readonly SqlSugarClient _sqlSugar;
|
|
|
private readonly IConfiguration _config;
|
|
|
+ private readonly IHunyuanService _hunyuanService;
|
|
|
private readonly CarDataRepository _carDataRep;
|
|
|
private readonly LocalGuideDataRepository _localGuideDataRep;
|
|
|
private readonly ThreeCodeRepository _ThreeCodeRep;
|
|
|
@@ -62,6 +70,8 @@ namespace OASystem.API.Controllers
|
|
|
public ResourceController(
|
|
|
IMapper mapper,
|
|
|
IConfiguration config,
|
|
|
+ ILogger<ResourceController> logger,
|
|
|
+ IHunyuanService hunyuanService,
|
|
|
SqlSugarClient sqlSugar,
|
|
|
CarDataRepository carDataRep,
|
|
|
LocalGuideDataRepository localGuideDataRep,
|
|
|
@@ -88,6 +98,8 @@ namespace OASystem.API.Controllers
|
|
|
{
|
|
|
_mapper = mapper;
|
|
|
_config = config;
|
|
|
+ _logger = logger;
|
|
|
+ _hunyuanService = hunyuanService;
|
|
|
_sqlSugar = sqlSugar;
|
|
|
_carDataRep = carDataRep;
|
|
|
_localGuideDataRep = localGuideDataRep;
|
|
|
@@ -1525,8 +1537,6 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
|
|
|
_ioaDatas = _ioaDatas.Where(x => string.IsNullOrWhiteSpace(x.Contact) || !x.Contact.Contains(item)).ToList();
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-
|
|
|
List<Sys_Users> _Users = _sqlSugar.Queryable<Sys_Users>().Where(it => it.IsDel == 0).ToList();
|
|
|
|
|
|
var _countryData = _ioaDatas.Select(it => it.Country).Distinct().ToList(); _countryData.Remove("");
|
|
|
@@ -1719,7 +1729,6 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
/// <summary>
|
|
|
/// 商邀资料查询-欧洲
|
|
|
/// </summary>
|
|
|
@@ -2262,6 +2271,343 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
+ #region 商邀资料 AI
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 商邀资料AI 基础数据源
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ [HttpGet]
|
|
|
+ [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
|
|
|
+ public async Task<IActionResult> InvitationAIInit()
|
|
|
+ {
|
|
|
+ // 预定义 UnionAll 查询
|
|
|
+ var query1 = _sqlSugar.Queryable<Grp_DelegationInfo>()
|
|
|
+ .Where(x => x.IsDel == 0 && !string.IsNullOrEmpty(x.TeamName))
|
|
|
+ .Select(x => new InvitationAIInvNameView { Id = x.Id, Name = x.TeamName, Source = 1, SortTime = x.VisitDate });
|
|
|
+
|
|
|
+ var query2 = _sqlSugar.Queryable<Res_InvitationAI>()
|
|
|
+ .Where(x => x.IsDel == 0 && !string.IsNullOrEmpty(x.InvName))
|
|
|
+ .Select(x => new InvitationAIInvNameView { Id = x.Id, Name = x.InvName, Source = 2, SortTime = x.CreateTime });
|
|
|
+
|
|
|
+ var itemNames = await _sqlSugar.UnionAll(query1, query2)
|
|
|
+ .OrderByDescending(x => x.SortTime)
|
|
|
+ .Select(x => new { x.Id, x.Name, x.Source })
|
|
|
+ .ToListAsync();
|
|
|
+
|
|
|
+ var invDatas = await _sqlSugar.Queryable<Crm_NewClientData>()
|
|
|
+ .Where(x => x.IsDel == 0)
|
|
|
+ .Select(x => x.Client)
|
|
|
+ .ToListAsync();
|
|
|
+
|
|
|
+ var countries = await _sqlSugar.Queryable<Sys_Countries>()
|
|
|
+ .Where(x => x.IsDel == 0)
|
|
|
+ .Select(x => x.Name_CN)
|
|
|
+ .Distinct()
|
|
|
+ .ToListAsync();
|
|
|
+
|
|
|
+ // 解密与去重(使用 HashSet 提升性能)
|
|
|
+ var unitNames = invDatas
|
|
|
+ .Select(item => AesEncryptionHelper.Decrypt(item))
|
|
|
+ .Where(val => !string.IsNullOrEmpty(val))
|
|
|
+ .Distinct()
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ return Ok(new
|
|
|
+ {
|
|
|
+ itemNames,
|
|
|
+ unitNames,
|
|
|
+ countries
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 商邀资料AI 混元AI查询资料
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ [HttpGet("{name}")]
|
|
|
+ [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
|
|
|
+ public async Task<IActionResult> InvitationAIItemByName(string name)
|
|
|
+ {
|
|
|
+ // 基础校验
|
|
|
+ if (string.IsNullOrWhiteSpace(name) )
|
|
|
+ return Ok(JsonView(false, "请传入有效的名称!"));
|
|
|
+
|
|
|
+ var infos = await _sqlSugar.Queryable<Res_InvitationAI>()
|
|
|
+ .Where(x => x.IsDel == 0 && x.InvName == name)
|
|
|
+ .Select(x => new {
|
|
|
+ x.Id,
|
|
|
+ x.InvName,
|
|
|
+ x.GroupId,
|
|
|
+ x.AiCrawledDetails,
|
|
|
+ Entry = x.Entries.FirstOrDefault(),
|
|
|
+ })
|
|
|
+ .FirstAsync();
|
|
|
+
|
|
|
+ return Ok(JsonView(true, $"查询成功!", infos));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 商邀资料AI 混元AI查询资料
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ [HttpPost]
|
|
|
+ [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
|
|
|
+ public async Task<IActionResult> InvitationAISearch(InvitationAISearchDto dto)
|
|
|
+ {
|
|
|
+ // 基础校验
|
|
|
+ if (string.IsNullOrWhiteSpace(dto.UnitName) || string.IsNullOrWhiteSpace(dto.Country))
|
|
|
+ return Ok(JsonView(false, "请传入有效的单位名称和国家!"));
|
|
|
+
|
|
|
+ // 当前企业领域
|
|
|
+ string industryFocus = string.Empty;
|
|
|
+
|
|
|
+ // 动态计算 AI 需求缺口
|
|
|
+ int totalTarget = 15;
|
|
|
+
|
|
|
+ // 词条
|
|
|
+ string question = string.Empty;
|
|
|
+
|
|
|
+ var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>()
|
|
|
+ .Where(x => x.IsDel == 0 && x.Id == dto.GroupId)
|
|
|
+ .Select(x => new { x.TeamName, x.VisitPurpose })
|
|
|
+ .FirstAsync();
|
|
|
+
|
|
|
+ // 当前企业出访目的
|
|
|
+ string objective = groupInfo?.VisitPurpose ?? "商务考察与合作对接";
|
|
|
+
|
|
|
+ string operatorName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
|
|
|
+
|
|
|
+ var localInvDatas = new List<InvitationAIInfo>(); // 本地数据源(商邀资料)
|
|
|
+
|
|
|
+ #region 本地数据源(商邀资料)
|
|
|
+
|
|
|
+ var rawData = await _sqlSugar.Queryable<Res_InvitationOfficialActivityData>()
|
|
|
+ .Where(x => x.IsDel == 0 && x.Country.Equals(dto.Country))
|
|
|
+ .ToListAsync();
|
|
|
+
|
|
|
+ // 解密
|
|
|
+ localInvDatas = rawData.AsParallel().AsOrdered().Select(item => new InvitationAIInfo
|
|
|
+ {
|
|
|
+ Source = 0, // 明确来源标识
|
|
|
+ Region = dto.UnitName,
|
|
|
+ NameCn = AesEncryptionHelper.Decrypt(item.UnitName),
|
|
|
+ Address = AesEncryptionHelper.Decrypt(item.Address),
|
|
|
+ Scope = AesEncryptionHelper.Decrypt(item.Field),
|
|
|
+ Contact = AesEncryptionHelper.Decrypt(item.Contact),
|
|
|
+ Phone = AesEncryptionHelper.Decrypt(item.Tel),
|
|
|
+ Email = AesEncryptionHelper.Decrypt(item.Email),
|
|
|
+ OperatedAt = DateTime.Now,
|
|
|
+ Operator = operatorName,
|
|
|
+ }).ToList();
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region 混元AI 获取商邀资料
|
|
|
+
|
|
|
+ var hunyuanAIInvDatas = new List<InvitationAIInfo>();
|
|
|
+
|
|
|
+ int aiNeedCount = Math.Max(0, totalTarget - localInvDatas.Count);
|
|
|
+
|
|
|
+ if (aiNeedCount > 0)
|
|
|
+ {
|
|
|
+ question = @$"# [SYSTEM_CONTEXT]
|
|
|
+- Role: Senior Business Consultant (Global Supply Chain & Cross-border Trade Expert)
|
|
|
+- Framework: S.P.A.R. (Situation-Problem-Action-Result)
|
|
|
+- Target Architecture: .NET 6 DTO Compatible Interface
|
|
|
+
|
|
|
+# [INPUT_CONFIG]
|
|
|
+- OriginUnit: {{{dto.UnitName}}}
|
|
|
+- TargetCountry: {{{dto.Country}}}
|
|
|
+- Objective: {{{objective}}}
|
|
|
+- DataCount: {{{aiNeedCount}}}
|
|
|
+- OtherConstraints: {{{dto.Remark}}}
|
|
|
+
|
|
|
+# [ROLE_DEFINITION]
|
|
|
+你是一位精通全球跨境经贸、海外园区政策及 {{TargetCountry}} 本地准入法规的【顶级商务咨询顾问】。你擅长通过“产业链对等原则(Chain-Parity Principle)”为跨国企业精准匹配具备落地价值的合作伙伴。
|
|
|
+
|
|
|
+# [BUSINESS_LOGIC_CoT]
|
|
|
+在生成结果前,请严格执行以下思维链拆解:
|
|
|
+1. **EntityProfiling**: 识别 {{OriginUnit}} 的核心生态位(如:Supply/Demand/Capital/Logistics)。
|
|
|
+2. **ParityMatching**: 检索 {{TargetCountry}} 境内业务闭环对等机构(例如:若 Origin 为分销,则 Target 为源头工厂/种植园)。
|
|
|
+3. **RegulatoryCheck**: 验证目标机构的合规性(如:GACC 备案、SPS 协议、或当地政府特许经营权)。
|
|
|
+4. **DataSynthesis**: 针对无法直接获取的动态(如 PostUrl),优先检索其官网 News 频道或 LinkedIn 企业号。
|
|
|
+
|
|
|
+# [CONSTRAINTS_&_STANDARDS]
|
|
|
+- **NamingConvention**: 所有 JSON Key 必须严格遵循 **PascalCase**(例如:`UnitNameCn` 而非 `unit_name_cn`)。
|
|
|
+- **DataIntegrity**:
|
|
|
+ - 必须输出精确的 {{DataCount}} 条记录。
|
|
|
+ - 优先级:Core (核心机构) > Backup (关联替代机构)。
|
|
|
+- **ValidationRules**:
|
|
|
+ - `Phone`: 必须包含 {{TargetCountry}} 国际区号(如 +856, +66 等)。
|
|
|
+ - `IntgAdvice`: 必须包含“对等性分析”,解释该机构如何与 {{OriginUnit}} 形成业务闭环(50-100字)。
|
|
|
+ - `Status`: 若字段确实无法获取,统一填充 'N/A',禁止编造。
|
|
|
+- **Safety**: 确保推荐机构不涉及敏感黑名单或已破产企业。
|
|
|
+
|
|
|
+# [INFORMATION_SCHEMA]
|
|
|
+请将结果填充至以下结构的 JSON 数组中:
|
|
|
+- NameCn: 单位名称(中文)
|
|
|
+- NameEn: 单位名称(英文)
|
|
|
+- Address: 详细地理位置(含省市区街道)
|
|
|
+- Scope: 经营范围(需强调其出口配额、生产能力或行业地位)
|
|
|
+- Contact: 联系人姓名及职务
|
|
|
+- Phone: 拨打全号(含区号)
|
|
|
+- Email: 商务联络邮箱
|
|
|
+- SiteUrl: 官方网站或权威社媒主页
|
|
|
+- PostUrl: 近一年内的商务动态/新闻链接
|
|
|
+- RecLevel: 推荐等级(枚举值:Core, Backup)
|
|
|
+- IntgAdvice: 对接深度建议(基于产业链交合、互补逻辑)
|
|
|
+
|
|
|
+# [OUTPUT_PROTOCOL]
|
|
|
+- 仅输出一个标准的 JSON Array 字符串。
|
|
|
+- 严禁任何 Markdown 说明文字、代码块之外的解释或开场白。
|
|
|
+- 确保 JSON 语法在 .NET `JsonSerializer.Deserialize` 下可直接解析。
|
|
|
+
|
|
|
+# [EXECUTION]
|
|
|
+根据以上配置,开始生成。";
|
|
|
+
|
|
|
+ string response = string.Empty;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ response = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(question);
|
|
|
+ }
|
|
|
+ catch (HttpRequestException ex)
|
|
|
+ {
|
|
|
+ _logger.LogError(ex, "网络连接失败,无法调用混元API");
|
|
|
+ return Ok(JsonView(false, $"网络连接异常,请检查网络后重试!Ex:{ex.Message}"));
|
|
|
+ }
|
|
|
+ catch (TaskCanceledException ex)
|
|
|
+ {
|
|
|
+ _logger.LogError(ex, "请求超时");
|
|
|
+ return Ok(JsonView(false, $"AI 响应超时,请稍后再试!Ex:{ex.Message}"));
|
|
|
+ }
|
|
|
+ catch (TencentCloudSDKException ex)
|
|
|
+ {
|
|
|
+ // 记录完整日志
|
|
|
+ _logger.LogError(ex, "腾讯云API调用失败 | 错误码: {ErrorCode} | 请求ID: {RequestId}", ex.ErrorCode, ex.RequestId);
|
|
|
+
|
|
|
+ // 根据错误码做差异化处理
|
|
|
+ if (ex.ErrorCode != null && ex.ErrorCode == "AuthFailure.SignatureExpire")
|
|
|
+ {
|
|
|
+ return Ok(JsonView(false, $"系统时间异常,请同步时间后重试!Ex:{ex.Message}"));
|
|
|
+ }
|
|
|
+ else if (ex.ErrorCode != null && ex.ErrorCode.StartsWith("Unauthorized"))
|
|
|
+ {
|
|
|
+ return Ok(JsonView(false, $"当前账号无权限访问该服务!Ex:{ex.Message}"));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return Ok(JsonView(false, $"腾讯云服务调用失败,请稍后重试!Ex:{ex.Message}"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ _logger.LogError(ex, "未知错误,调用混元API失败");
|
|
|
+ return Ok(JsonView(false, $"未知错误,调用混元API失败!Ex:{ex.Message}"));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!string.IsNullOrWhiteSpace(response))
|
|
|
+ {
|
|
|
+ // 预处理:过滤 AI 可能返回的 Markdown 标记
|
|
|
+ string cleanJson = response.Trim();
|
|
|
+ if (cleanJson.StartsWith("```json"))
|
|
|
+ cleanJson = cleanJson.Substring(7, cleanJson.Length - 10).Trim();
|
|
|
+ else if (cleanJson.StartsWith("```"))
|
|
|
+ cleanJson = cleanJson.Substring(3, cleanJson.Length - 6).Trim();
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ // 解析并注入 Source 标识
|
|
|
+ hunyuanAIInvDatas = JsonConvert.DeserializeObject<List<InvitationAIInfo>>(cleanJson);
|
|
|
+
|
|
|
+ if (hunyuanAIInvDatas != null)
|
|
|
+ {
|
|
|
+ hunyuanAIInvDatas = hunyuanAIInvDatas.Select(x => {
|
|
|
+ x.Source = 1;
|
|
|
+ x.Region = dto.Country;
|
|
|
+ x.Operator = operatorName;
|
|
|
+ x.OperatedAt = DateTime.Now;
|
|
|
+ return x;
|
|
|
+ }).ToList();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (JsonException ex)
|
|
|
+ {
|
|
|
+ // 记录日志并考虑 fallback 策略
|
|
|
+ _logger.LogError(ex, "Hunyuan AI 响应解析失败。原始数据:{Response}", response);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region 数据库操作
|
|
|
+
|
|
|
+ // 数据整合 (高效合并)
|
|
|
+ var finalResult = localInvDatas.Concat(hunyuanAIInvDatas).ToList();
|
|
|
+
|
|
|
+ // 词条信息
|
|
|
+ var entries = new List<EntryInfo>() { new EntryInfo() {
|
|
|
+ Details = question,
|
|
|
+ Operator = operatorName,
|
|
|
+ OperatedAt = DateTime.Now
|
|
|
+ } };
|
|
|
+
|
|
|
+ string invName = $"{dto.UnitName}拜访{dto.Country}";
|
|
|
+
|
|
|
+ // 数据库信息获取方式
|
|
|
+ // 1.通过团组 GroupId 关联查询
|
|
|
+ var dataInfo = await _sqlSugar.Queryable<Res_InvitationAI>().Where(x => x.IsDel == 0 && x.GroupId == dto.GroupId).FirstAsync();
|
|
|
+
|
|
|
+ // 2.通过InvName 关联查询(考虑到可能存在同名情况,优先使用 GroupId 关联查询,确保数据准确性)
|
|
|
+ dataInfo ??= await _sqlSugar.Queryable<Res_InvitationAI>().Where(x => x.IsDel == 0 && x.InvName == invName).FirstAsync();
|
|
|
+
|
|
|
+ // 3.如果以上两种方式都没有查询到数据,则说明是新数据,需要添加到数据库
|
|
|
+ if (dataInfo == null)
|
|
|
+ {
|
|
|
+ // 3.1 新数据,需要添加到数据库
|
|
|
+ dataInfo = new Res_InvitationAI()
|
|
|
+ {
|
|
|
+ InvName = groupInfo?.TeamName ?? invName, //默认团组名称
|
|
|
+ GroupId = dto.GroupId,
|
|
|
+ AiCrawledDetails = finalResult,
|
|
|
+ Entries = entries,
|
|
|
+ CreateUserId = dto.CurrUserId
|
|
|
+ };
|
|
|
+
|
|
|
+ var insert = await _sqlSugar.Insertable(dataInfo).ExecuteCommandAsync();
|
|
|
+ if (insert < 1)
|
|
|
+ {
|
|
|
+ return Ok(JsonView(false, $"数据新增失败!"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 3.1 数据存在 则更新数据(覆盖原有的AI爬取详情,保留原有的其他字段不变)
|
|
|
+ dataInfo.AiCrawledDetails = finalResult;
|
|
|
+
|
|
|
+ var update = await _sqlSugar.Updateable(dataInfo).ExecuteCommandAsync();
|
|
|
+ if (update < 1)
|
|
|
+ {
|
|
|
+ return Ok(JsonView(false, $"数据更新失败!"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ return Ok(JsonView(true, $"查询成功!", new
|
|
|
+ {
|
|
|
+ dataInfo.Id,
|
|
|
+ dataInfo.InvName,
|
|
|
+ dataInfo.GroupId,
|
|
|
+ dataInfo.AiCrawledDetails,
|
|
|
+ Entry = dataInfo.Entries.FirstOrDefault(),
|
|
|
+ }));
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
#region 公务出访
|
|
|
/// <summary>
|
|
|
/// 获取团组所有信息,绑定下拉框
|