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