Files
Docker/CloudBuilder/CloudBuilder.Master.Service/PublishAudioService.auto.cs
T
2026-10-21 20:50:55 +08:00

622 lines
28 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using CloudBuilder.Core.DatabaseAccessor;
using CloudBuilder.Core.DatabaseAccessor.Entity;
using CloudBuilder.Core.Policy;
using CloudBuilder.Core.Service;
using CloudBuilder.Master.Data;
using CloudBuilder.Master.Entity;
using CloudBuilder.Master.Policy;
using CloudBuilder.Master.SearchCriteria;
using CloudBuilder.Security.Entity;
using CloudBuilder.Security.Policy;
using CloudBuilder.Security.Service;
using CloudBuilder.Security.Utility;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
namespace CloudBuilder.Master.Service
{
public class PublishAudioService : IPublishAudioService
{
private readonly IApplicationService service;
private IRepository<PublishAudioEntity> repository;
public PublishAudioService(IApplicationService service)
{
this.service = service;
repository = service.GetRepository<IRepository<PublishAudioEntity>>();
}
[PermissionOperate(name: MasterPermissionPolicy.PublishAudio, operate: Operate.Find)]
public PublishAudioData FindByKey(string key)
{
PublishAudioData data = new PublishAudioData();
if (string.IsNullOrEmpty(key)) return data;
PublishAudioEntity publish = repository.DetachedEntities.Where(x => x.PublishName == key).FirstOrDefault();
if (publish == null) return data;
IRepository<PublishAudioFamilyGroupEntity> repositoryPublishAudioFamilyGroup = repository.GetRepository<IRepository<PublishAudioFamilyGroupEntity>>();
IRepository<FamilyGroupEntity> repositoryFamilyGroup = repository.GetRepository<IRepository<FamilyGroupEntity>>();
IQueryable<PublishAudioFamilyGroupDisplayData>? q = from e in repositoryPublishAudioFamilyGroup.DetachedEntities
join fg in repositoryFamilyGroup.DetachedEntities on e.FamilyName equals fg.FamilyName
where e.PublishName == key
select new PublishAudioFamilyGroupDisplayData(fg)
{
CreatedBy = e.CreatedBy,
CreatedDatetime = e.CreatedDatetime,
UpdatedBy = e.UpdatedBy,
UpdatedDatetime = e.UpdatedDatetime,
PublishName = e.PublishName,
};
data.FamilyGroups = q.ToArray();
IRepository<FamilyGroupManagerEntity> repositoryFamilyGroupManager = repository.GetRepository<IRepository<FamilyGroupManagerEntity>>();
//归属与群组身份:所有者全权;群管理员可删/改信息;群成员仅可改明细
data.IsOwner = publish.OwnerName == service.UserId;
data.IsMember = data.IsOwner;
data.IsManager = data.IsOwner;
if (!data.IsOwner && data.FamilyGroups.Length > 0)
{
string[] familyNames = data.FamilyGroups.Select(x => x.FamilyName).ToArray();
FamilyGroupManagerEntity[] mine = repositoryFamilyGroupManager.DetachedEntities
.Where(x => x.UserName == service.UserId && x.ValidIndc == YesNoPolicy.YES && familyNames.Contains(x.FamilyName))
.ToArray();
data.IsMember = mine.Length > 0;
data.IsManager = mine.Any(x => x.ManagerIndc == YesNoPolicy.YES);
}
if (!data.IsOwner && !YesNoPolicy.IsYes(publish.OpenIndc) && !data.IsMember)
{
throw new ValidatedException("没有权限.");
}
data.PublishAudio = publish;
IRepository<PublishAudioDetailEntity> repositoryPublishAudioDetail = repository.GetRepository<IRepository<PublishAudioDetailEntity>>();
data.Details = repositoryPublishAudioDetail.DetachedEntities.Where(x => x.PublishName == publish.PublishName).OrderBy(x => x.OrderBy).Select(x => new PublishAudioDetailDisplayData(x)).ToArray();
if (data.Details != null && data.Details.Length > 0)
{
// 明细的文件可能由群成员(非专辑所有者)从自己的网盘里挑:目录与源文件名都要按 dms_file 的真实归属取,
// 否则拼出的地址会落到专辑所有者目录下,取不到文件(500)。
IRepository<DmsFileEntity> repositoryDmsFile = repository.GetRepository<IRepository<DmsFileEntity>>();
string[] orderKeys = data.Details
.SelectMany(x => new[] { x.Pic, x.Url, x.Lrc })
.Where(x => !string.IsNullOrEmpty(x))
.Select(x => x!)
.Distinct()
.ToArray();
Dictionary<string, DmsFileEntity> files = new Dictionary<string, DmsFileEntity>();
if (orderKeys.Length > 0)
{
DmsFileEntity[] found = repositoryDmsFile.DetachedEntities
.Where(x => x.OrderKey != null && orderKeys.Contains(x.OrderKey))
.ToArray();
foreach (DmsFileEntity file in found)
{
if (!string.IsNullOrEmpty(file.OrderKey) && !files.ContainsKey(file.OrderKey))
{
files.Add(file.OrderKey, file);
}
}
}
foreach (var item in data.Details)
{
item.PicName = DmsOrgName(files, item.Pic);
item.UrlName = DmsOrgName(files, item.Url);
item.LrcName = DmsOrgName(files, item.Lrc);
if (!string.IsNullOrEmpty(item.Pic) && FileTypePolicy.GetType(item.Pic!) == FileTypePolicy.PHOTO)
{
item.FormattedPic = FileTypePolicy.PackPicUrl(service, DmsOwner(files, item.Pic, item.CreatedBy), item.Pic);
}
if (!string.IsNullOrEmpty(item.Url) && FileTypePolicy.GetType(item.Url!) == FileTypePolicy.AUDIO)
{
item.FormattedUrl = FileTypePolicy.PackUrl(service, DmsOwner(files, item.Url, item.CreatedBy), item.Url);
}
if (!string.IsNullOrEmpty(item.Lrc) && FileTypePolicy.GetType(item.Lrc!) == FileTypePolicy.LYRIC)
{
item.FormattedLrc = FileTypePolicy.PackUrl(service, DmsOwner(files, item.Lrc, item.CreatedBy), item.Lrc);
}
}
}
return data;
}
/// <summary>明细展示用的源文件名:查不到 dms_file 时回落物理名,界面不至于空白。</summary>
private static string DmsOrgName(Dictionary<string, DmsFileEntity> files, string? orderKey)
{
if (string.IsNullOrEmpty(orderKey))
{
return string.Empty;
}
if (files.TryGetValue(orderKey, out DmsFileEntity file) && !string.IsNullOrEmpty(file.OrgFileName))
{
return file.OrgFileName;
}
return orderKey;
}
/// <summary>文件真实归属目录(网盘按人分目录):查不到就回落明细创建人,保持旧行为。</summary>
private static string DmsOwner(Dictionary<string, DmsFileEntity> files, string? orderKey, string fallback)
{
if (!string.IsNullOrEmpty(orderKey) && files.TryGetValue(orderKey, out DmsFileEntity file))
{
if (!string.IsNullOrEmpty(file.CreatedBy))
{
return file.CreatedBy;
}
if (!string.IsNullOrEmpty(file.OwerId))
{
return file.OwerId;
}
}
return fallback;
}
[PermissionOperate(name: MasterPermissionPolicy.PublishAudio, operate: Operate.Find)]
public PublishAudioResultData FindBySearchCriteria(PublishAudioSearchCriteria criteria)
{
PublishAudioResultData data = new PublishAudioResultData();
PublishAudioEntity[] list = null;
Int64 count = 0;
string sqlWhere = null;
if (criteria.Filter != null && criteria.Filter.Length > 0)
{
sqlWhere = SearchFilter.ComposeWhereClause(criteria.Filter);
}
sqlWhere = sqlWhere + string.Format(" and ({0} ='{1}' or {2} ='{3}') ",
PublishAudioEntity.DB_OWNER_NAME, service.UserId,
PublishAudioEntity.DB_OPEN_INDC, criteria.IsModal ? service.UserId : YesNoPolicy.YES);
PublishAudioEntity entity = PublishAudioEntity.GetInstance(repository.GetDbContext().Database.ProviderName);
string sql = entity.GetSelectTopFrom(criteria.limit, criteria.page, criteria.orderby.StringToDbField(), criteria.orderbytag, sqlWhere);
string err = null;
try
{
list = repository.FromSql(sql).ToArray();
}
catch (System.Exception ex)
{
err = ex.Message;
}
if (list != null && list.Length > 0)
{
sql = entity.GetSelectCountFrom(sqlWhere);
count = Convert.ToInt64(repository.ExecuteScalar(sql));
}
data.Limit = criteria.limit;
data.Page = criteria.page;
data.Datas = list;
data.Total = count;
//data.Sql = sql;
data.ErrorMessage = err;
return data;
}
[PermissionOperate(name: MasterPermissionPolicy.PublishAudio, operate: Operate.Find)]
public PublishAudioData FindPublishAudioDefault()
{
PublishAudioEntity publish = repository.DetachedEntities.Where(x => x.OwnerName == service.UserId && x.DefaultIndc == YesNoPolicy.YES).FirstOrDefault();
if (publish == null) return null;
return FindByKey(publish.PublishName);
}
[PermissionOperate(name: MasterPermissionPolicy.PublishAudio, operate: Operate.Print)]
public dynamic PrintBySearchCriteria(PublishAudioSearchCriteria criteria)
{
throw new NotImplementedException();
}
[PermissionOperate(name: MasterPermissionPolicy.PublishAudio, operate: Operate.Insert)]
[UnitOfWork]
public PublishAudioData Insert(PublishAudioData data)
{
PublishAudioEntity photoPublish = repository.DetachedEntities.Where(x => x.PublishLocalName == data.PublishAudio.PublishLocalName).FirstOrDefault();
if (photoPublish != null)
{
throw new ValidatedException($"[{photoPublish.PublishLocalName}]已发布.");
}
Validate(data.PublishAudio);
if (data.PublishAudio.DefaultIndc == YesNoPolicy.YES)
{
CancelDefaultIndc(null);
}
photoPublish = new PublishAudioEntity();
ObjectCopyHelper.Copy(data.PublishAudio, photoPublish);
ISecurityGeneratorNumberService securityGeneratorNumberService = service.ServiceProvider.GetService<ISecurityGeneratorNumberService>()!;
photoPublish.PublishName = securityGeneratorNumberService.GetNo(MasterPrefixPolicy.AUDIO_PUBLISH, MasterPrefixPolicy.AUDIO_PUBLISH_PUBLISH_NAME, "AP", YesNoPolicy.YES, "yymmdd00000");
photoPublish.OwnerName = service.UserId;
repository.InsertNow(photoPublish);
SavePublishAudioFamilyGroup(photoPublish.PublishName, data.FamilyGroups);
return FindByKey(photoPublish.PublishName);
}
[PermissionOperate(name: MasterPermissionPolicy.PublishAudio, operate: Operate.Update)]
[UnitOfWork]
public PublishAudioData Update(PublishAudioData data)
{
//归属校验:所有者可全改;公开群组管理员可改信息/群组;普通群成员只能改明细
PublishAudioEntity original = repository.DetachedEntities.Where(x => x.PublishName == data.PublishAudio.PublishName).FirstOrDefault();
if (original == null)
{
throw new ValidatedException("记录不存在.");
}
bool isOwner = original.OwnerName == service.UserId;
bool isManager = false;
bool isMember = false;
if (!isOwner)
{
IRepository<PublishAudioFamilyGroupEntity> repositoryPublishAudioFamilyGroup = repository.GetRepository<IRepository<PublishAudioFamilyGroupEntity>>();
IRepository<FamilyGroupManagerEntity> repositoryFamilyGroupManager = repository.GetRepository<IRepository<FamilyGroupManagerEntity>>();
string[] familyNames = repositoryPublishAudioFamilyGroup.DetachedEntities
.Where(x => x.PublishName == original.PublishName)
.Select(x => x.FamilyName)
.ToArray();
if (familyNames.Length == 0)
{
throw new ValidatedException("没有权限.");
}
FamilyGroupManagerEntity[] mine = repositoryFamilyGroupManager.DetachedEntities
.Where(x => x.UserName == service.UserId && x.ValidIndc == YesNoPolicy.YES && familyNames.Contains(x.FamilyName))
.ToArray();
isMember = mine.Length > 0;
isManager = mine.Any(x => x.ManagerIndc == YesNoPolicy.YES);
if (!isMember)
{
throw new ValidatedException("没有权限.");
}
}
//非所有者保留归属与建单信息,防客户端伪造
data.PublishAudio.OwnerName = original.OwnerName;
data.PublishAudio.CreatedBy = original.CreatedBy;
data.PublishAudio.CreatedDatetime = original.CreatedDatetime;
if (!isOwner && !isManager)
{
//非所有者且非管理员(普通群成员)只能改明细:专辑展示字段一律以库中为准
data.PublishAudio.PublishLocalName = original.PublishLocalName;
data.PublishAudio.Notice = original.Notice;
data.PublishAudio.OpenIndc = original.OpenIndc;
data.PublishAudio.ExpireDatetime = original.ExpireDatetime;
data.PublishAudio.OrderBy = original.OrderBy;
}
if (!isOwner)
{
//默认专辑是所有者个人偏好,群管理员也不能改
data.PublishAudio.DefaultIndc = original.DefaultIndc;
}
ValidateHelper.Validate(service, data.PublishAudio, PublishAudioEntity.DB_NAME_PUBLISH_AUDIO);
if (isOwner && data.PublishAudio.DefaultIndc == YesNoPolicy.YES)
{
CancelDefaultIndc(data.PublishAudio.PublishName);
}
PublishAudioEntity publishEntity = new PublishAudioEntity();
ObjectCopyHelper.Copy(data.PublishAudio, publishEntity);
repository.UpdateNow(publishEntity);
//只有删除和更新,没有插入
SavePublishAudioDetails(publishEntity.PublishName, data.Details);
//群组增删只对所有者/群管理员开放;普通成员提交的 FamilyGroups 不落库
if (isOwner || isManager)
{
SavePublishAudioFamilyGroup(publishEntity.PublishName, data.FamilyGroups);
}
return FindByKey(publishEntity.PublishName);
}
/// <summary>当前登录人是否为该专辑任一公开群组的管理员(正式成员且 ManagerIndc=Y)</summary>
private bool IsGroupManagerOfPublish(string publishName)
{
IRepository<PublishAudioFamilyGroupEntity> repositoryFamilyGroup = repository.GetRepository<IRepository<PublishAudioFamilyGroupEntity>>();
IRepository<FamilyGroupManagerEntity> repositoryFamilyGroupManager = repository.GetRepository<IRepository<FamilyGroupManagerEntity>>();
string[] familyNames = repositoryFamilyGroup.DetachedEntities
.Where(x => x.PublishName == publishName)
.Select(x => x.FamilyName)
.ToArray();
if (familyNames.Length == 0)
{
return false;
}
return repositoryFamilyGroupManager.DetachedEntities
.Any(x => x.UserName == service.UserId && x.ValidIndc == YesNoPolicy.YES && x.ManagerIndc == YesNoPolicy.YES && familyNames.Contains(x.FamilyName));
}
private void SavePublishAudioDetails(string publishName, PublishAudioDetailEntity[] details)
{
if (details != null && details.Length > 0)
{
int i = 1;
foreach (PublishAudioDetailEntity dtl in details)
{
dtl.OrderBy = i++;
}
}
IRepository<PublishAudioDetailEntity> repositoryPublishAudioDetail = repository.GetRepository<IRepository<PublishAudioDetailEntity>>();
PublishAudioDetailEntity[] olds = repositoryPublishAudioDetail.Entities.Where(x => x.PublishName == publishName).ToArray();
if (olds != null && olds.Length > 0)
{
//存在更新,不存在删除
foreach (PublishAudioDetailEntity old in olds)
{
if (details != null && details.Length > 0 && details.Where(x => x.DetailId == old.DetailId).Count() > 0)
{
PublishAudioDetailEntity dtl = details.Where(x => x.DetailId == old.DetailId).FirstOrDefault();
old.Title = dtl.Title;
old.Author = dtl.Author;
old.Pic = dtl.Pic;
old.Url = dtl.Url;
old.Lrc = dtl.Lrc;
old.OrderBy = dtl.OrderBy;
old.Notice = dtl.Notice;
repositoryPublishAudioDetail.UpdateNow(old);
}
else
{
repositoryPublishAudioDetail.DeleteNow(old);
}
}
}
}
private void Validate(PublishAudioEntity photoPublish)
{
List<SysTableSchemaViewEntity> sysTableSchemas = service.ServiceProvider.GetRequiredService<ISysTableSchemaViewService>().FindByTableName(PublishAudioEntity.DB_NAME_PUBLISH_AUDIO);
ValidateHelper.ValidateString(photoPublish, sysTableSchemas.ToArray());
}
private void SavePublishAudioFamilyGroup(string publishName, PublishAudioFamilyGroupEntity[] familyGroups)
{
IRepository<PublishAudioFamilyGroupEntity> repositoryPublishAudioFamilyGroup = repository.GetRepository<IRepository<PublishAudioFamilyGroupEntity>>();
PublishAudioFamilyGroupEntity[] olds = repositoryPublishAudioFamilyGroup.Entities.Where(x => x.PublishName == publishName).ToArray();
if (olds != null && olds.Length > 0)
{
//存在更新,不存在删除
foreach (PublishAudioFamilyGroupEntity old in olds)
{
if (familyGroups != null && familyGroups.Length > 0 && familyGroups.Where(x => x.FamilyName == old.FamilyName).Count() > 0)
{
//只有主键,无需更新
//repositoryPpf.UpdateNow(old);
}
else
{
repositoryPublishAudioFamilyGroup.DeleteNow(old);
}
}
}
if (familyGroups != null && familyGroups.Length > 0)
foreach (PublishAudioFamilyGroupEntity dto in familyGroups)
{
if (string.IsNullOrEmpty(dto.CreatedBy))
repositoryPublishAudioFamilyGroup.InsertNow(new PublishAudioFamilyGroupEntity()
{
PublishName = publishName,
FamilyName = dto.FamilyName,
});
}
}
public void CancelDefaultIndc(string publishLocalName)
{
PublishAudioEntity[] orgs = repository.DetachedEntities.Where(x => x.PublishName != publishLocalName && x.OwnerName == service.UserId && x.DefaultIndc == YesNoPolicy.YES).ToArray();
foreach (PublishAudioEntity org in orgs)
{
org.DefaultIndc = YesNoPolicy.NO;
}
if (orgs != null && orgs.Length > 0)
repository.UpdateNow(orgs);
}
[PermissionOperate(name: MasterPermissionPolicy.PublishAudio, operate: Operate.Delete)]
[UnitOfWork]
public void Delete(string key)
{
PublishAudioEntity publishEntity = repository.DetachedEntities.Where(x => x.PublishName == key).FirstOrDefault();
if (publishEntity == null)
{
throw new ValidatedException("记录不存在.");
}
//删除专辑:所有者或该专辑任一公开群组的管理员
if (publishEntity.OwnerName != service.UserId && !IsGroupManagerOfPublish(key))
{
throw new ValidatedException("没有权限.");
}
repository.DeleteNow(publishEntity);
IRepository<PublishAudioDetailEntity> repositoryDetail = repository.GetRepository<IRepository<PublishAudioDetailEntity>>();
PublishAudioDetailEntity[] detail = repositoryDetail.Entities.Where(x => x.PublishName == key).ToArray();
if (detail != null && detail.Length > 0)
{
foreach (PublishAudioDetailEntity dtl in detail)
{
repositoryDetail.DeleteNow(dtl);
}
}
IRepository<PublishAudioFamilyGroupEntity> repositoryFamilyGroup = repository.GetRepository<IRepository<PublishAudioFamilyGroupEntity>>();
PublishAudioFamilyGroupEntity[] groups = repositoryFamilyGroup.Entities.Where(x => x.PublishName == key).ToArray();
if (groups != null && groups.Length > 0)
{
foreach (PublishAudioFamilyGroupEntity gp in groups)
{
repositoryFamilyGroup.DeleteNow(gp);
}
}
}
[PermissionOperate(name: MasterPermissionPolicy.PublishAudio, operate: Operate.Insert)]
public void InsertPublishAudioDetail(PublishData publishData)
{
IRepository<DmsFileUploadViewEntity> repositoryDmsFile = repository.GetRepository<IRepository<DmsFileUploadViewEntity>>();
DmsFileUploadViewEntity dmsFile = repositoryDmsFile.DetachedEntities.Where(x => x.FileId == publishData.FileId).FirstOrDefault();
if (dmsFile == null) return;
if (!FileTypePolicy.IsAudio(dmsFile.OrderKey)) return;
InsertPublishAudioDetail(dmsFile, publishData.PublishName);
}
private void InsertPublishAudioDetail(DmsFileUploadViewEntity dmsFile, string publishName)
{
IRepository<PublishAudioDetailEntity> repositoryPublishAudioDetail = repository.GetRepository<IRepository<PublishAudioDetailEntity>>();
PublishAudioDetailEntity[] dtls = repositoryPublishAudioDetail.DetachedEntities.Where(x => x.PublishName == publishName).ToArray();
if (dtls != null && dtls.Where(x => x.Url == dmsFile.OrderKey).Count() > 0) return;
PublishAudioDetailEntity dtl = new PublishAudioDetailEntity();
dtl.PublishName = publishName;
string orgFileName = Path.GetFileNameWithoutExtension(dmsFile.OrgFileName);
string[] tts = orgFileName.Split('-');
dtl.Title = tts[0].Trim();
if (tts.Length > 1) dtl.Author = tts[1].Trim();
dtl.OwnerName = dmsFile.OwerId;
if (dtls != null && dtls.Length > 0)
dtl.OrderBy = dtls.Sum(x => x.OrderBy) + 1;
dtl.Url = dmsFile.OrderKey;
// 同名的歌词、封面一起存下来(优先同目录)
FillLyricAndPic(dmsFile, dtl);
repositoryPublishAudioDetail.InsertNow(dtl);
}
/// <summary>
/// 找出与音频同名的歌词和封面文件,写进明细。
///
/// 约定:音频叫 xxx.mp3,配套文件就是同目录下的 xxx.lrc(歌词)、xxx.png/jpg(封面)。
/// 匹配用 OrgFileName 的主文件名(它是用户看到的原始文件名,如 song.mp3),
/// 存的是 OrderKey——因为前端访问文件用的是 OrderKey(分片合并后的名字,如 uid-1-1.mp3)。
/// 优先取同目录(FilePath 相同)的;同目录没有时才退而求其次找其它目录同名文件。
/// </summary>
private void FillLyricAndPic(DmsFileUploadViewEntity dmsFile, PublishAudioDetailEntity dtl)
{
if (dmsFile == null || dtl == null) return;
if (string.IsNullOrEmpty(dmsFile.OrgFileName)) return;
string baseName = Path.GetFileNameWithoutExtension(dmsFile.OrgFileName);
if (string.IsNullOrEmpty(baseName)) return;
IRepository<DmsFileUploadViewEntity> repositoryDmsFile = repository.GetRepository<IRepository<DmsFileUploadViewEntity>>();
// 先取回同目录的记录,文件名比对放在内存里做:
// Path.GetFileNameWithoutExtension 翻不成 SQL,直接写进 Where 会运行时报错
DmsFileUploadViewEntity[] inFolder = repositoryDmsFile.DetachedEntities
.Where(x => x.FilePath == dmsFile.FilePath)
.ToArray();
DmsFileUploadViewEntity lyric = FindCompanion(inFolder, baseName, true);
DmsFileUploadViewEntity pic = FindCompanion(inFolder, baseName, false);
// 同目录凑不齐,再放宽到其它目录
if (lyric == null || pic == null)
{
DmsFileUploadViewEntity[] others = repositoryDmsFile.DetachedEntities
.Where(x => x.FilePath != dmsFile.FilePath)
.ToArray();
if (lyric == null) lyric = FindCompanion(others, baseName, true);
if (pic == null) pic = FindCompanion(others, baseName, false);
}
if (lyric != null) dtl.Lrc = lyric.OrderKey;
if (pic != null) dtl.Pic = pic.OrderKey;
}
/// <summary>
/// 在候选文件里找配得上 baseName 的歌词 / 图片。
/// 类型用 FileTypePolicy 判断,避免自己维护一份扩展名清单。
///
/// 匹配优先级:
/// 1) 完全同名:song.mp3 配 song.lrc,最稳妥,命中就直接用
/// 2) 音频名包含配套文件名:歌手-歌名.mp3 也能配上 歌名.lrc
/// 都用 OrdinalIgnoreCase,避免 Song.LRC / song.lrc 因大小写漏配。
/// </summary>
private DmsFileUploadViewEntity FindCompanion(DmsFileUploadViewEntity[] candidates, string baseName, bool wantLyric)
{
if (candidates == null || candidates.Length == 0) return null;
// 包含匹配的结果先存着,等确认没有完全同名的再用它
DmsFileUploadViewEntity fuzzy = null;
foreach (DmsFileUploadViewEntity item in candidates)
{
if (item == null) continue;
if (string.IsNullOrEmpty(item.OrgFileName) || string.IsNullOrEmpty(item.OrderKey)) continue;
string name = Path.GetFileNameWithoutExtension(item.OrgFileName);
if (string.IsNullOrEmpty(name)) continue;
bool typeMatched = wantLyric
? FileTypePolicy.IsLyric(item.OrgFileName)
: FileTypePolicy.IsPhoto(item.OrgFileName);
if (!typeMatched) continue;
if (string.Equals(name, baseName, StringComparison.OrdinalIgnoreCase)) return item;
if (fuzzy == null && baseName.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0)
fuzzy = item;
}
return fuzzy;
}
}
}