From bff07b1f020dda8518e3f1d38ad8b038f3fa053d Mon Sep 17 00:00:00 2001
From: “wangming” <“wangming@antissoft.com”>
Date: Fri, 19 Sep 2025 18:15:14 +0800
Subject: [PATCH] Refactor FileVariable and LqEventService to include UserSignatureFilePath; enhance LqEventUser DTO with StoreId; implement Excel import functionality for LqEvent users; add UploadBase64Image method in FileService for user signature uploads.
---
netcore/src/Modularity/Common/NCC.Common/Configuration/FileVariable.cs | 9 +++++++--
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqEvent/LqEventImportResult.cs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqEventUser/LqEventUserCrInput.cs | 5 +++++
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdPxmx/LqKdPxmxInfoOutput.cs | 29 +++++++++++++++++------------
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxCrInput.cs | 2 +-
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_eventuser/LqEventUserEntity.cs | 6 ++++++
netcore/src/Modularity/Extend/NCC.Extend/LqEventService.cs | 237 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs | 1 +
netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs | 14 ++++++++++++++
netcore/src/Modularity/Extend/NCC.Extend/NCC.Extend.csproj | 4 ++++
netcore/src/Modularity/System/NCC.System/Entitys/Dto/Base64ImageUploadInput.cs | 30 ++++++++++++++++++++++++++++++
netcore/src/Modularity/System/NCC.System/Service/Common/FileService.cs | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
拓客活动用户导入模板.csv | 5 +++++
13 files changed, 610 insertions(+), 38 deletions(-)
create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqEvent/LqEventImportResult.cs
create mode 100644 netcore/src/Modularity/System/NCC.System/Entitys/Dto/Base64ImageUploadInput.cs
create mode 100644 拓客活动用户导入模板.csv
diff --git a/netcore/src/Modularity/Common/NCC.Common/Configuration/FileVariable.cs b/netcore/src/Modularity/Common/NCC.Common/Configuration/FileVariable.cs
index 4e906d8..d8bbf28 100644
--- a/netcore/src/Modularity/Common/NCC.Common/Configuration/FileVariable.cs
+++ b/netcore/src/Modularity/Common/NCC.Common/Configuration/FileVariable.cs
@@ -1,5 +1,5 @@
-using NCC.Dependency;
-using System.IO;
+using System.IO;
+using NCC.Dependency;
namespace NCC.Common.Configuration
{
@@ -73,5 +73,10 @@ namespace NCC.Common.Configuration
/// 模板路径
///
public static string TemplateFilePath = SystemPath + "TemplateFile/";
+
+ ///
+ /// 用户签名存储路径
+ ///
+ public static string UserSignatureFilePath = SystemPath + "UserSignature/";
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqEvent/LqEventImportResult.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqEvent/LqEventImportResult.cs
new file mode 100644
index 0000000..b79d05e
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqEvent/LqEventImportResult.cs
@@ -0,0 +1,77 @@
+using System.Collections.Generic;
+using NCC.Extend.Entitys.Dto.LqEventUser;
+
+namespace NCC.Extend.Entitys.Dto.LqEvent
+{
+ ///
+ /// 拓客活动Excel导入结果
+ ///
+ public class LqEventImportResult
+ {
+ ///
+ /// 成功数量
+ ///
+ public int SuccessCount { get; set; }
+
+ ///
+ /// 失败数量
+ ///
+ public int FailCount { get; set; }
+
+ ///
+ /// 总数量
+ ///
+ public int TotalCount { get; set; }
+
+ ///
+ /// 成功的数据列表
+ ///
+ public List SuccessData { get; set; } = new List();
+
+ ///
+ /// 失败的数据列表
+ ///
+ public List FailData { get; set; } = new List();
+ }
+
+ ///
+ /// 拓客活动导入错误信息
+ ///
+ public class LqEventImportError
+ {
+ ///
+ /// 行号
+ ///
+ public int RowNumber { get; set; }
+
+ ///
+ /// 员工手机号
+ ///
+ public string MobilePhone { get; set; }
+
+ ///
+ /// 姓名
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// 战队
+ ///
+ public string TeamName { get; set; }
+
+ ///
+ /// 门店
+ ///
+ public string StoreName { get; set; }
+
+ ///
+ /// 目标张数
+ ///
+ public int? TargetCount { get; set; }
+
+ ///
+ /// 错误信息
+ ///
+ public string ErrorMessage { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqEventUser/LqEventUserCrInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqEventUser/LqEventUserCrInput.cs
index 00e2fb4..373e018 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqEventUser/LqEventUserCrInput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqEventUser/LqEventUserCrInput.cs
@@ -33,5 +33,10 @@ namespace NCC.Extend.Entitys.Dto.LqEventUser
/// 战队名称
///
public string TeamName { get; set; }
+
+ ///
+ /// 门店id
+ ///
+ public string StoreId { get; set; }
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdPxmx/LqKdPxmxInfoOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdPxmx/LqKdPxmxInfoOutput.cs
index c02844f..7e580d0 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdPxmx/LqKdPxmxInfoOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdPxmx/LqKdPxmxInfoOutput.cs
@@ -12,62 +12,67 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
/// 明细编号
///
public string id { get; set; }
-
+
///
/// 关联开单编号
///
public string glkdbh { get; set; }
-
+
///
/// 品项编号
///
public string px { get; set; }
-
+
///
/// 品项名称
///
public string pxmc { get; set; }
-
+
///
/// 品项价格
///
public decimal pxjg { get; set; }
-
+
///
/// 项目次数
///
public int? projectNumber { get; set; }
-
+
///
/// 是否有效
///
public int? isEnabled { get; set; }
-
+
///
/// 来源类型(开卡/赠送/其他)
///
public string sourceType { get; set; }
-
+
///
/// 会员ID
///
public string memberId { get; set; }
-
+
///
/// 创建时间
///
public DateTime? createTime { get; set; }
-
+
///
/// 总价格(品项价格 × 项目次数)
///
public decimal totalPrice { get; set; }
-
+
+ ///
+ /// 实际价格
+ ///
+ public decimal actualPrice { get; set; }
+
///
/// 健康师业绩列表
///
public List lqKdJksyjList { get; set; }
-
+
///
/// 科技部老师业绩列表
///
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxCrInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxCrInput.cs
index bd0f156..74336e2 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxCrInput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxCrInput.cs
@@ -81,7 +81,7 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
///
/// 客户消费
///
- public string khxf { get; set; }
+ public List khxf { get; set; }
///
/// 消费频次
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_eventuser/LqEventUserEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_eventuser/LqEventUserEntity.cs
index 8ce9f42..5d00e21 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_eventuser/LqEventUserEntity.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_eventuser/LqEventUserEntity.cs
@@ -56,5 +56,11 @@ namespace NCC.Extend.Entitys.lq_eventuser
///
[SugarColumn(ColumnName = "F_EventTarget")]
public int EventTarget { get; set; }
+
+ ///
+ /// 门店id
+ ///
+ [SugarColumn(ColumnName = "F_StoreId")]
+ public string StoreId { get; set; }
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqEventService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqEventService.cs
index 5f301b3..8b9ad64 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqEventService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqEventService.cs
@@ -1,19 +1,24 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Mapster;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NCC.Common.Core.Manager;
using NCC.Common.Filter;
+using NCC.Common.Helper;
using NCC.Dependency;
using NCC.DynamicApiController;
using NCC.Extend.Entitys.Dto.LqEvent;
using NCC.Extend.Entitys.Dto.LqEventUser;
using NCC.Extend.Entitys.lq_event;
using NCC.Extend.Entitys.lq_eventuser;
+using NCC.Extend.Entitys.lq_mdxx;
using NCC.Extend.Interfaces.LqEvent;
using NCC.FriendlyException;
+using NCC.System.Entitys.Permission;
using SqlSugar;
using Yitter.IdGenerator;
@@ -219,6 +224,7 @@ namespace NCC.Extend.LqEvent
TeamName = member.TeamName,
CreationTime = DateTime.Now,
CreationUser = userInfo?.userName,
+ StoreId = member.StoreId,
}
);
}
@@ -306,6 +312,7 @@ namespace NCC.Extend.LqEvent
TeamName = member.TeamName,
CreationTime = DateTime.Now,
CreationUser = userInfo?.userName,
+ StoreId = member.StoreId,
}
);
}
@@ -482,5 +489,235 @@ namespace NCC.Extend.LqEvent
}
}
#endregion
+
+ #region Excel导入拓客活动用户
+
+ ///
+ /// Excel导入拓客活动用户
+ ///
+ ///
+ /// 通过Excel文件导入拓客活动用户数据,支持批量导入。
+ /// Excel列名:员工手机号、姓名、战队、门店、目标张数
+ ///
+ /// 导入流程:
+ /// 1. 通过员工手机号查找用户信息,获取用户ID和组织ID
+ /// 2. 通过门店中文名称查找门店信息,获取门店ID
+ /// 3. 验证数据完整性
+ /// 4. 返回成功和失败的数据列表
+ ///
+ /// 示例请求:
+ /// POST /api/Extend/LqEvent/import-users
+ /// Content-Type: multipart/form-data
+ ///
+ /// 参数:
+ /// - file: Excel文件
+ ///
+ /// 返回数据:
+ /// - SuccessCount: 成功数量
+ /// - FailCount: 失败数量
+ /// - SuccessData: 成功的数据列表
+ /// - FailData: 失败的数据列表(包含错误信息)
+ ///
+ /// Excel文件
+ /// 拓客活动ID
+ /// 导入结果
+ /// 导入完成,返回成功和失败的数据统计
+ /// 请求参数错误或文件格式不正确
+ /// 服务器内部错误
+ [HttpPost("import-users")]
+ public async Task ImportUsers(IFormFile file)
+ {
+ try
+ {
+ if (file == null || file.Length == 0)
+ {
+ throw NCCException.Oh("请选择要导入的Excel文件");
+ }
+ var result = new LqEventImportResult();
+ // 读取Excel文件
+ var excelData = await ReadExcelFile(file);
+ result.TotalCount = excelData.Count;
+
+ foreach (var row in excelData)
+ {
+ try
+ {
+ var importError = new LqEventImportError
+ {
+ RowNumber = row.RowNumber,
+ MobilePhone = row.MobilePhone,
+ Name = row.Name,
+ TeamName = row.TeamName,
+ StoreName = row.StoreName,
+ TargetCount = row.TargetCount,
+ };
+
+ // 验证必填字段
+ if (string.IsNullOrEmpty(row.MobilePhone))
+ {
+ importError.ErrorMessage = "员工手机号不能为空";
+ result.FailData.Add(importError);
+ result.FailCount++;
+ continue;
+ }
+
+ // 通过手机号查找用户
+ var user = await _db.Queryable().Where(u => u.MobilePhone == row.MobilePhone).FirstAsync();
+
+ if (user == null)
+ {
+ importError.ErrorMessage = $"未找到手机号为 {row.MobilePhone} 的用户";
+ result.FailData.Add(importError);
+ result.FailCount++;
+ continue;
+ }
+
+ // 获取用户组织ID(部门ID)
+ if (string.IsNullOrEmpty(user.OrganizeId))
+ {
+ importError.ErrorMessage = $"用户 {user.RealName} 没有关联的组织信息";
+ result.FailData.Add(importError);
+ result.FailCount++;
+ continue;
+ }
+
+ // 查找门店信息
+ string storeId = null;
+ if (!string.IsNullOrEmpty(row.StoreName))
+ {
+ var store = await _db.Queryable().Where(s => s.Dm == row.StoreName).FirstAsync();
+
+ if (store == null)
+ {
+ importError.ErrorMessage = $"未找到名称为 {row.StoreName} 的门店";
+ result.FailData.Add(importError);
+ result.FailCount++;
+ continue;
+ }
+ storeId = store.Id;
+ }
+
+ // 创建成功的数据
+ var successData = new NCC.Extend.Entitys.Dto.LqEventUser.LqEventUserCrInput
+ {
+ Id = YitIdHelper.NextId().ToString(),
+ EventId = eventId,
+ UserId = user.Id,
+ DepId = user.OrganizeId,
+ TeamName = row.TeamName,
+ StoreId = storeId,
+ };
+
+ result.SuccessData.Add(successData);
+ result.SuccessCount++;
+ }
+ catch (Exception ex)
+ {
+ var importError = new LqEventImportError
+ {
+ RowNumber = row.RowNumber,
+ MobilePhone = row.MobilePhone,
+ Name = row.Name,
+ TeamName = row.TeamName,
+ StoreName = row.StoreName,
+ TargetCount = row.TargetCount,
+ ErrorMessage = $"处理数据时发生错误: {ex.Message}",
+ };
+ result.FailData.Add(importError);
+ result.FailCount++;
+ }
+ }
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ throw NCCException.Oh($"导入拓客活动用户失败: {ex.Message}", ex);
+ }
+ }
+
+ ///
+ /// 读取Excel文件数据
+ ///
+ /// Excel文件
+ /// Excel数据列表
+ [NonAction]
+ private async Task> ReadExcelFile(IFormFile file)
+ {
+ var result = new List();
+
+ // 保存临时文件
+ var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName));
+ try
+ {
+ using (var stream = new FileStream(tempFilePath, FileMode.Create))
+ {
+ await file.CopyToAsync(stream);
+ }
+
+ // 使用ExcelImportHelper读取Excel文件
+ var dataTable = ExcelImportHelper.ToDataTable(tempFilePath, 0, 0);
+
+ // 从第2行开始读取数据(第1行是标题)
+ for (int i = 1; i < dataTable.Rows.Count; i++)
+ {
+ var row = dataTable.Rows[i];
+ var mobilePhone = row[0]?.ToString()?.Trim();
+ var name = row[1]?.ToString()?.Trim();
+ var teamName = row[2]?.ToString()?.Trim();
+ var storeName = row[3]?.ToString()?.Trim();
+ var targetCountStr = row[4]?.ToString()?.Trim();
+
+ int? targetCount = null;
+ if (!string.IsNullOrEmpty(targetCountStr) && int.TryParse(targetCountStr, out int target))
+ {
+ targetCount = target;
+ }
+
+ // 跳过空行
+ if (string.IsNullOrEmpty(mobilePhone) && string.IsNullOrEmpty(name))
+ {
+ continue;
+ }
+
+ result.Add(
+ new ExcelImportRow
+ {
+ RowNumber = i + 1, // Excel行号从1开始
+ MobilePhone = mobilePhone,
+ Name = name,
+ TeamName = teamName,
+ StoreName = storeName,
+ TargetCount = targetCount,
+ }
+ );
+ }
+ }
+ finally
+ {
+ // 清理临时文件
+ if (File.Exists(tempFilePath))
+ {
+ File.Delete(tempFilePath);
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Excel导入行数据
+ ///
+ private class ExcelImportRow
+ {
+ public int RowNumber { get; set; }
+ public string MobilePhone { get; set; }
+ public string Name { get; set; }
+ public string TeamName { get; set; }
+ public string StoreName { get; set; }
+ public int? TargetCount { get; set; }
+ }
+
+ #endregion
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
index 3873e22..448898c 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
@@ -131,6 +131,7 @@ namespace NCC.Extend.LqKdKdjlb
memberId = pxmx.MemberId,
createTime = pxmx.CreateTIme,
totalPrice = pxmx.TotalPrice,
+ actualPrice = pxmx.ActualPrice,
};
// 关联该品项的健康师业绩
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
index fea575a..53cca4c 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
@@ -157,6 +157,13 @@ namespace NCC.Extend.LqKhxx
}
var entity = input.Adapt();
entity.Id = YitIdHelper.NextId().ToString();
+
+ // 处理客户消费字段:将数组转换为逗号分隔的字符串
+ if (input.khxf != null && input.khxf.Count > 0)
+ {
+ entity.Khxf = string.Join(",", input.khxf);
+ }
+
var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync();
if (!(isOk > 0))
throw NCCException.Oh(ErrorCode.COM1000);
@@ -464,6 +471,13 @@ namespace NCC.Extend.LqKhxx
public async Task Update(string id, [FromBody] LqKhxxUpInput input)
{
var entity = input.Adapt();
+
+ // 处理客户消费字段:将数组转换为逗号分隔的字符串
+ if (input.khxf != null && input.khxf.Count > 0)
+ {
+ entity.Khxf = string.Join(",", input.khxf);
+ }
+
var isOk = await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
if (!(isOk > 0))
throw NCCException.Oh(ErrorCode.COM1001);
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/NCC.Extend.csproj b/netcore/src/Modularity/Extend/NCC.Extend/NCC.Extend.csproj
index aa97535..a89e7d9 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/NCC.Extend.csproj
+++ b/netcore/src/Modularity/Extend/NCC.Extend/NCC.Extend.csproj
@@ -17,4 +17,8 @@
+
+
+
+
diff --git a/netcore/src/Modularity/System/NCC.System/Entitys/Dto/Base64ImageUploadInput.cs b/netcore/src/Modularity/System/NCC.System/Entitys/Dto/Base64ImageUploadInput.cs
new file mode 100644
index 0000000..11acf68
--- /dev/null
+++ b/netcore/src/Modularity/System/NCC.System/Entitys/Dto/Base64ImageUploadInput.cs
@@ -0,0 +1,30 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace NCC.System.Entitys.Dto
+{
+ ///
+ /// Base64图片上传输入参数
+ ///
+ public class Base64ImageUploadInput
+ {
+ ///
+ /// Base64编码的图片数据
+ /// 支持格式:data:image/xxx;base64,xxxxx 或纯base64字符串
+ ///
+ [Required(ErrorMessage = "Base64数据不能为空")]
+ public string Base64Data { get; set; }
+
+ ///
+ /// 图片类型(可选)
+ /// 支持的类型:userAvatar, document, temporary, weixin, workFlow, annex, annexpic, diskdocument, preview, screenShot, banner, bg, border, source, template, codeGenerator
+ /// 默认为 temporary
+ ///
+ public string ImageType { get; set; }
+
+ ///
+ /// 文件名(可选,不包含扩展名)
+ /// 如果不提供,系统会自动生成
+ ///
+ public string FileName { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/System/NCC.System/Service/Common/FileService.cs b/netcore/src/Modularity/System/NCC.System/Service/Common/FileService.cs
index a9dcf4d..550c219 100644
--- a/netcore/src/Modularity/System/NCC.System/Service/Common/FileService.cs
+++ b/netcore/src/Modularity/System/NCC.System/Service/Common/FileService.cs
@@ -1,4 +1,15 @@
-using NCC.Common.Configuration;
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Web;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using NCC.Common.Configuration;
using NCC.Common.Core.Captcha.General;
using NCC.Common.Core.Manager;
using NCC.Common.Enum;
@@ -10,18 +21,9 @@ using NCC.DynamicApiController;
using NCC.FriendlyException;
using NCC.JsonSerialization;
using NCC.RemoteRequest.Extensions;
+using NCC.System.Entitys.Dto;
using NCC.System.Interfaces.Common;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
using OnceMi.AspNetCore.OSS;
-using System;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Web;
using Yitter.IdGenerator;
namespace NCC.System.Service.Common
@@ -33,7 +35,7 @@ namespace NCC.System.Service.Common
[Route("api/[controller]")]
public class FileService : IFileService, IDynamicApiController, ITransient
{
- private readonly IGeneralCaptcha _captchaHandle;// 验证码服务
+ private readonly IGeneralCaptcha _captchaHandle; // 验证码服务
private readonly IConfiguration _configuration;
private readonly IUserManager _userManager;
private readonly IOSSServiceFactory _oSSServiceFactory;
@@ -177,7 +179,8 @@ namespace NCC.System.Service.Common
var fileDownloadName = exname;
if (fileDownloadName.IsNullOrWhiteSpace() || fileDownloadName.Split('.').Length < 2)
fileDownloadName = Path.GetFileName(filePath);
- if (fileDownloadName.IsNullOrWhiteSpace()) fileDownloadName = fileName.Replace(GetPathByType(type), "");
+ if (fileDownloadName.IsNullOrWhiteSpace())
+ fileDownloadName = fileName.Replace(GetPathByType(type), "");
return await DownloadFileByType(filePath, fileDownloadName);
}
else
@@ -282,6 +285,8 @@ namespace NCC.System.Service.Common
{
case "userAvatar":
return FileVariable.UserAvatarFilePath;
+ case "userSignature":
+ return FileVariable.UserSignatureFilePath;
case "mail":
return FileVariable.EmailFilePath;
case "IM":
@@ -352,10 +357,6 @@ namespace NCC.System.Service.Common
return false;
}
-
-
-
-
#region 导入导出
///
@@ -374,11 +375,7 @@ namespace NCC.System.Service.Common
var byteList = new UTF8Encoding(true).GetBytes(jsonStr.ToCharArray());
FileHelper.CreateFile(_filePath + _fileName, byteList);
var fileName = _userManager.UserId + "|" + _filePath + _fileName + "|json";
- var output = new
- {
- name = _fileName,
- url = "/api/file/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC")
- };
+ var output = new { name = _fileName, url = "/api/file/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC") };
return output;
}
@@ -422,7 +419,6 @@ namespace NCC.System.Service.Common
}
}
-
///
/// 上传文件
///
@@ -446,5 +442,192 @@ namespace NCC.System.Service.Common
}
}
#endregion
+
+ #region Base64图片上传
+
+ ///
+ /// 上传Base64格式图片(主要用于用户签名信息)
+ ///
+ ///
+ /// 接收前端传入的base64格式图片数据,解码后保存到服务器并返回访问路径。
+ /// 支持常见的图片格式(JPG、PNG、GIF、BMP等),主要用于保存用户签名信息。
+ ///
+ /// 示例请求:
+ /// POST /api/File/UploadBase64Image
+ /// Content-Type: application/json
+ ///
+ /// ```json
+ /// {
+ /// "base64Data": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD...",
+ /// "imageType": "userSignature",
+ /// "fileName": "signature"
+ /// }
+ /// ```
+ ///
+ /// 参数说明:
+ /// - base64Data: 必填,base64编码的图片数据(支持data:image/xxx;base64,格式)
+ /// - imageType: 可选,图片类型(userSignature用于用户签名,默认为temporary)
+ /// - fileName: 可选,文件名(不包含扩展名,系统会自动添加)
+ ///
+ /// 支持的图片类型:
+ /// - userSignature: 用户签名(推荐)
+ /// - userAvatar: 用户头像
+ /// - document: 文档
+ /// - temporary: 临时文件(默认)
+ /// - 其他系统支持的图片类型
+ ///
+ /// 返回数据:
+ /// - name: 保存的文件名
+ /// - url: 图片访问路径
+ /// - fileSize: 文件大小(字节)
+ /// - imageFormat: 图片格式
+ /// - imageType: 图片类型
+ ///
+ /// Base64图片上传参数
+ /// 图片上传结果,包含文件名和访问路径
+ /// 图片上传成功
+ /// 请求参数错误或图片格式不支持
+ /// 服务器内部错误
+ [HttpPost("UploadBase64Image")]
+ [AllowAnonymous]
+ public async Task UploadBase64Image([FromBody] Base64ImageUploadInput input)
+ {
+ try
+ {
+ // 验证输入参数
+ if (string.IsNullOrEmpty(input.Base64Data))
+ {
+ throw NCCException.Oh("Base64数据不能为空");
+ }
+
+ // 解析Base64数据
+ var imageData = ParseBase64Data(input.Base64Data, out string imageFormat);
+
+ // 验证图片格式
+ if (!IsValidImageFormat(imageFormat))
+ {
+ throw NCCException.Oh($"不支持的图片格式: {imageFormat}");
+ }
+
+ // 生成文件名
+ var fileName = GenerateImageFileName(input.FileName, imageFormat);
+
+ // 获取存储路径
+ var imageType = string.IsNullOrEmpty(input.ImageType) ? "temporary" : input.ImageType;
+ var filePath = GetPathByType(imageType);
+
+ // 确保目录存在
+ if (!Directory.Exists(filePath))
+ {
+ Directory.CreateDirectory(filePath);
+ }
+
+ // 保存图片文件
+ var fullPath = Path.Combine(filePath, fileName);
+ await File.WriteAllBytesAsync(fullPath, imageData);
+
+ // 生成访问URL
+ var accessUrl = $"/api/File/Image/{imageType}/{fileName}";
+
+ return new
+ {
+ name = fileName,
+ url = accessUrl,
+ fileSize = imageData.Length,
+ imageFormat = imageFormat.ToUpper(),
+ imageType = imageType,
+ };
+ }
+ catch (Exception ex)
+ {
+ throw NCCException.Oh($"Base64图片上传失败: {ex.Message}", ex);
+ }
+ }
+
+ ///
+ /// 解析Base64数据并提取图片格式
+ ///
+ /// Base64数据
+ /// 输出图片格式
+ /// 解码后的图片字节数组
+ [NonAction]
+ private byte[] ParseBase64Data(string base64Data, out string imageFormat)
+ {
+ try
+ {
+ // 处理data:image/xxx;base64,格式
+ if (base64Data.StartsWith("data:image/"))
+ {
+ var parts = base64Data.Split(',');
+ if (parts.Length != 2)
+ {
+ throw new ArgumentException("Base64数据格式不正确");
+ }
+
+ // 提取图片格式
+ var header = parts[0];
+ var formatMatch = Regex.Match(header, @"data:image/([^;]+)");
+ if (formatMatch.Success)
+ {
+ imageFormat = formatMatch.Groups[1].Value.ToLower();
+ }
+ else
+ {
+ imageFormat = "jpeg"; // 默认格式
+ }
+
+ // 解码Base64数据
+ return Convert.FromBase64String(parts[1]);
+ }
+ else
+ {
+ // 纯Base64数据,默认为JPEG格式
+ imageFormat = "jpeg";
+ return Convert.FromBase64String(base64Data);
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new ArgumentException($"Base64数据解析失败: {ex.Message}", ex);
+ }
+ }
+
+ ///
+ /// 验证图片格式是否支持
+ ///
+ /// 图片格式
+ /// 是否支持
+ [NonAction]
+ private bool IsValidImageFormat(string imageFormat)
+ {
+ var allowedFormats = new[] { "jpg", "jpeg", "png", "gif", "bmp", "tiff", "webp" };
+ return allowedFormats.Contains(imageFormat.ToLower());
+ }
+
+ ///
+ /// 生成图片文件名
+ ///
+ /// 输入的文件名
+ /// 图片格式
+ /// 生成的文件名
+ [NonAction]
+ private string GenerateImageFileName(string inputFileName, string imageFormat)
+ {
+ var timestamp = DateTime.Now.ToString("yyyyMMddHHmmss");
+ var id = YitIdHelper.NextId().ToString();
+
+ if (!string.IsNullOrEmpty(inputFileName))
+ {
+ // 移除可能存在的扩展名
+ var nameWithoutExt = Path.GetFileNameWithoutExtension(inputFileName);
+ return $"{nameWithoutExt}_{timestamp}_{id}.{imageFormat}";
+ }
+ else
+ {
+ return $"image_{timestamp}_{id}.{imageFormat}";
+ }
+ }
+
+ #endregion
}
}
diff --git a/拓客活动用户导入模板.csv b/拓客活动用户导入模板.csv
new file mode 100644
index 0000000..57932e7
--- /dev/null
+++ b/拓客活动用户导入模板.csv
@@ -0,0 +1,5 @@
+员工手机号,姓名,战队,门店,目标张数
+13800138001,张三,第一战队,北京旗舰店,50
+13800138002,李四,第二战队,上海分店,30
+13800138003,王五,,深圳分店,40
+13800138004,赵六,第三战队,,25
--
libgit2 0.21.4