using Aliyun.OSS;
using Elasticsearch.Net;
using JiaZhiQuan.Common.AliOSS;
using JiaZhiQuan.Common.Config;
using JiaZhiQuan.Common.ElasticSearch;
using JiaZhiQuan.Common.ElasticSearch.Models;
using JiaZhiQuan.Common.SnowFlake;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Org.BouncyCastle.Utilities;
using Polly;
using Senparc.Weixin.MP.Containers;
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Dynamic;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Wicture.DbRESTFul;
using Wicture.DbRESTFul.Cache;
using Wicture.DbRESTFul.Infrastructure.Repository;
using Wicture.DbRESTFul.Rpc.Client;
namespace JiaZhiQuan.Common
{
public class PostHotValue
{
public static int GetRoleScore(int role, bool superior)
{
int basic = 100;
if (role > 0)
{
basic += 70;
}
if (superior)
{
basic += 50;
}
if (role == 2)
{
basic += 50;
}
return basic;
}
///
/// 客观事实数据(阅读量相关)
///
public float fact { get; set; } = 0;
///
/// 平台主观策略分(内容评定分)
///
public float subjectiveScore { get; set; }
///
/// 账号风险系数
///
public float cntHotCoefficient { get; set; }
public float hot { get; set; }
}
public class PostScore
{
public long id { get; set; }
public long userId { get; set; }
public double hot { get; set; }
public PostScore Clone()
{
return new PostScore
{
id = id,
userId = userId,
hot = hot
};
}
}
public static partial class RepositoryExtension
{
///
/// 通过给定的PostScoreList基础上,来计算此用户个人的推荐列表,最多返回maxPostCount个结果。如果用户未关注任何人,则无需计算
///
/// 给定的内容分列表,在此基础上添加个性推荐分
/// 账号热度系数
private static async Task> GetPersonalRecommendList(DbRESTFulRepository repository, List list, Dictionary userCntHotCoefficientDic, long userId, IRpcClient rpcClient, int maxPostCount = 1000)
{
if (list == null || list.Count == 0) return new List();
// 查出用户最新关注的500用户
var focusUserIds = userId > 0 ? (await repository.QueryAsync($"select userId from n_user_fan where fanId={userId} order by id desc")).ToList() : new List();
// 因为现在只有关注才影响热度值,所以没有关注,则无需计算
// if (focusUserIds.Count == 0) return new List();
var now = DateTime.Now;
if (userId > 0)
{
var changed = false;
list.ForEach(e =>
{
if (focusUserIds.Contains(e.userId))
{
e.hot += 200 / Math.Ceiling((now - IdWorker.DecodeId(e.id).Time).TotalDays) * userCntHotCoefficientDic[e.userId];
if (!changed) changed = true;
}
});
var list1 = changed ? list.OrderByDescending(x => x.hot).ToList() : list;
// 过滤已看过的,如果未看的内容不足最大推荐数量,则将已看内容添加至未看列表末尾
var filterRst = (await rpcClient.InvokeRpcAsync(repository.Context, "UserVisitService", "FilterNotExist", string.Join(',', list1.Select(e => $"{userId}_{e.id}")))).Data;
var filteredList = (JsonConvert.DeserializeObject(filterRst) ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries).Select(e => long.Parse(e.Split('_')[1])).ToHashSet();
if (filteredList.Count > 0)
{
var validList = new List();
var readList = new List();
foreach (var e in list1)
{
if (filteredList.Contains(e.id))
{
validList.Add(e);
if (validList.Count >= maxPostCount) break;
}
else
{
readList.Add(e);
}
}
if (validList.Count < maxPostCount && readList.Count > 0)
{
foreach (var e in readList)
{
validList.Add(e);
if (validList.Count >= maxPostCount) break;
}
}
return validList;
}
else
{
return list1.Count > maxPostCount ? list1.Take(maxPostCount).ToList() : list1;
}
}
else
{
return list.Count > maxPostCount ? list.Take(maxPostCount).ToList() : list;
}
}
///
/// 对postIds列表重新排列,将连续相同用户间隔开
///
/// 文章Id列表
/// 用户Id列表,个数与文章Id列表一致,如果传空,则会通过PostIds在数据库中查询对应的用户Id
/// 是否锁定第一个
/// 返回(文章编号列表, 是否存在顺序重排)
public static async Task<(List, bool)> RearrangePostIds(this DbRESTFulRepository repository, List postIds, List userIds, bool firstFixed = false, IDbConnection conn = null)
{
var rearranged = false;
// 找出文章id对应的用户编号, 500为一批进行查询
var size = 500;
userIds ??= new List(postIds.Count);
if (userIds.Count == 0)
{
for (var i = 0; i < postIds.Count; i += size)
{
var leftCount = postIds.Count - i;
var subList = postIds.Skip(i).Take(leftCount >= size ? size : leftCount).ToList();
// 查出对应的用户id
var map = (await repository.QueryAsync("select id, userId from n_post where id in @ids",
new { ids = subList }, conn, null, false)).ToDictionary(e => (long)e.id);
postIds.ForEach(postId =>
{
userIds.Add(map.ContainsKey(postId) ? (long)map[postId].userId : 0);
});
}
}
// 如果文章id列表与用户id列表无法一一对应,则直接返回文章id列表
if (userIds.Count != postIds.Count) return (postIds, rearranged);
var count = postIds.Count;
var hasRepetitiveItems = false;
for (var i = 1; i < count; i++)
{
if (userIds[i - 1] == userIds[i])
{
var exchanged = false;
// 如果当前文章用户与上一个文章用户相同,则向后查找一个与当前用户不同的记录
for (var j = i + 1; j < count; j++)
{
if (userIds[j] != userIds[i])
{
// 交换
postIds[i] ^= postIds[j];
postIds[j] = postIds[i] ^ postIds[j];
postIds[i] ^= postIds[j];
userIds[i] ^= userIds[j];
userIds[j] = userIds[i] ^ userIds[j];
userIds[i] ^= userIds[j];
exchanged = true;
rearranged = true;
break;
}
}
// 如果未找到可以替换的,则表示后面的全都是相同用户的内容,无需进行后续查找与替换
if (!exchanged)
{
hasRepetitiveItems = true;
break;
}
}
}
// 如果存在未能替换的,则反向执行替换一次
if (hasRepetitiveItems)
{
var reverseEndIdx = firstFixed ? 1 : 0;
for (var i = count - 2; i > reverseEndIdx; i--)
{
if (userIds[i + 1] == userIds[i])
{
var exchanged = false;
// 如果当前文章用户与上一个文章用户相同,则向后查找一个与当前用户不同的记录
for (var j = i - 1; j >= reverseEndIdx; j--)
{
if (userIds[j] != userIds[i])
{
// 交换
postIds[i] ^= postIds[j];
postIds[j] = postIds[i] ^ postIds[j];
postIds[i] ^= postIds[j];
userIds[i] ^= userIds[j];
userIds[j] = userIds[i] ^ userIds[j];
userIds[i] ^= userIds[j];
exchanged = true;
rearranged = true;
break;
}
}
// 如果未找到可以替换的,则表示后面的全都是相同用户的内容,无需进行后续查找与替换
if (!exchanged)
{
break;
}
}
}
}
return (postIds, rearranged);
}
///
/// 将推荐的内容保存到Redis中
///
/// 如果没有,则传null
/// null表示所有,1为带图片的,2为带视频的
/// 如果没有,则传null
private static async Task UpdatePersonalRecommendListToRedis(DbRESTFulRepository repository, RedisCacheProvider cacheProvider, long[] ids, long userId, int? categoryType, int? contentType, bool? hasTopic, int dayOfMonth)
{
// 重排文章列表(将连续用户分开)
ids = (await RearrangePostIds(repository, ids.ToList(), null)).Item1.ToArray();
var key = CacheKeys.PostRecommendList(dayOfMonth, userId, categoryType, contentType, hasTopic);
await cacheProvider.CSRedisClient.DelAsync(key);
if (ids.Length == 0) return;
await cacheProvider.CSRedisClient.RPushAsync(key, ids);
await cacheProvider.CSRedisClient.ExpireAsync(key, CacheKeys.PostRecommendListCacheSecs);
}
///
/// 获取满足条件的文章列表(热度>0的)
///
/// 如果没有,则传null
/// null表示所有,1为带图片的,2为带视频的
/// 如果没有,则传null
/// 如果传入的字典不为空,则在查询结果后,会将结果中用户对应的账号热度系数查出来,并存入字典
/// 最多获取多少条
private static async Task> GetHotPostList(DbRESTFulRepository repository, ConfigFromDb config, int categoryType, int? contentType, bool? hasTopic = null, Dictionary userCntHotCoefficientDic = null, int maxPostCount = 5000, IDbConnection conn = null)
{
var postList = new List();
var minId = IdWorker.GetStartIdByDate(
string.IsNullOrEmpty(config.PostStartRecommendDate)
? DateTime.Now.AddDays(-config.PostMaxRecommendDays)
: DateTime.Parse(config.PostStartRecommendDate)
);
var mustConditions = new List
{
new { term = new { state = 1 } },
new { range = new { hot = new { gt = 0 } } },
new { term = new { categoryType } },
new { range = new { id = new { gt = minId } } },
};
// 如果是带图片的
if (contentType == 1)
{
mustConditions.Add(new { term = new { type = 0 } });
mustConditions.Add(new { range = new { coverWidth = new { gt = 0 } } });
}
else if (contentType == 2)
{
mustConditions.Add(new { term = new { type = 1 } });
}
if (hasTopic != null)
{
mustConditions.Add(new { script = new { script = "doc['validTopicIds'].length>0" } });
}
var query = new
{
_source = new string[] { "id", "userId", "hot" },
size = maxPostCount,
query = new
{
@bool = new
{
filter = new
{
@bool = new
{
must = mustConditions
}
}
}
},
sort = new List
{
new
{
hot = new { order = "desc" }
},
new
{
id = new { order = "desc" }
}
}
};
var resp = await ESHelper.Client.SearchAsync(ESConstants.ESIndexName.Post, PostData.Serializable(query));
if (resp.Success)
{
var obj = JsonConvert.DeserializeObject(resp.Body);
var list = obj.Value("hits")?.Value("hits")?.Select(e =>
{
var source = e.Value("_source");
return new PostScore
{
id = source.Value("id"),
userId = source.Value("userId"),
hot = source.Value("hot"),
};
})?.ToList();
if (list != null)
{
postList = list;
}
}
else
{
LoggerManager.Logger.Error("GetHotPostList ES查询出错 " + resp?.Body);
}
if (userCntHotCoefficientDic != null)
{
var ids = new HashSet();
postList.ForEach(e =>
{
if (userCntHotCoefficientDic.ContainsKey(e.userId) || ids.Contains(e.userId)) return;
ids.Add(e.userId);
});
if (ids.Count > 0)
{
var batchSize = 500;
for (var i = 0; i < ids.Count; i += batchSize)
{
var count = i + batchSize > ids.Count ? ids.Count - i : batchSize;
var subList = i == 0 && ids.Count <= batchSize ? ids : ids.Skip(i).Take(count);
var userCntHotCoefficients = await repository.QueryAsync("select id, cntHotCoefficient from n_user where id in @ids", new { ids = subList }, conn);
userCntHotCoefficients.ForEach(e =>
{
userCntHotCoefficientDic[(long)e.id] = (float)e.cntHotCoefficient;
});
}
}
}
return postList;
}
///
/// 更新推荐缓存列表
///
/// 是否仅更新未登录用户的热门列表
public static async Task UpdateUserRecommendPostCache(this DbRESTFulRepository repository, bool calcPersonal, RedisCacheProvider cacheProvider, IRpcClient rpcClient, ConfigFromDb config)
{
// 每类型中取出最大值
var maxPostCount = 10000;
Dictionary userCntHotCoefficientDic = null;
if (calcPersonal)
{
userCntHotCoefficientDic = new Dictionary();
}
// 记录推荐缓存同步记录
var syncRecordId = await repository.QuerySingleOrDefaultAsync("insert into n_post_recommend_records(startAt) values(now()); select last_insert_id() as id;", null, null, null, false);
var syncNote = new StringBuilder();
using var conn = repository.ConnectionManager.GetConnection();
conn.Open();
var days = 10; // 10天内活跃的,且有关注记录的用户
var now = DateTime.Now;
//var standpoints = await GetHotPostList(repository, 2, null, null, userCntHotCoefficientDic, maxPostCount, conn);
// -9999表示所有一级分类
var diaryLst = new Dictionary>();
diaryLst[-9999] = await GetHotPostList(repository, config, 1, null, null, userCntHotCoefficientDic, maxPostCount, conn);
// diaryLst[-9998] = await GetHotPostList(repository, config, 1, null, true, userCntHotCoefficientDic, maxPostCount, conn);
// 带图片的图文笔记
diaryLst[1] = await GetHotPostList(repository, config, 1, 1, null, userCntHotCoefficientDic, maxPostCount, conn);
// 视频笔记
diaryLst[2] = await GetHotPostList(repository, config, 1, 2, null, userCntHotCoefficientDic, maxPostCount, conn);
var dayOfMonth = now.Day;
foreach (var di in diaryLst)
{
var nrl = await GetPersonalRecommendList(repository, diaryLst[di.Key], userCntHotCoefficientDic, 0, rpcClient, 1000);
await UpdatePersonalRecommendListToRedis(repository, cacheProvider, nrl.Select(i => i.id).ToArray(), 0, 1,
di.Key < 0 ? null : (int?)di.Key, null, dayOfMonth);
}
if (calcPersonal)
{
var startDate = now.AddDays(-days).ToString("yyyy-MM-dd");
// 查出10天内活跃用户
var userIds = (await repository.QueryAsync($"select distinct userId from n_user_online where `date`>='{startDate}'", null, conn)).ToList();
var userFetchSize = 500;
var emptyPostIds = Array.Empty();
for (var i = 0; i < userIds.Count; i += userFetchSize)
{
var count = i + userFetchSize > userIds.Count ? userIds.Count - i : userFetchSize;
var subList = i == 0 && userIds.Count <= userFetchSize ? userIds : userIds.Skip(i).Take(count).ToList();
// 查询出有关注的用户
var validSubList = (await repository.QueryAsync("select userId from n_user_statistic where focusUser>0 and userId in @subList", new { subList }, conn)).ToHashSet();
foreach (var userId in subList)
{
if (CommonUtils.IsMockUser(userId)) continue;
// 如果是无关注的用户,则推荐设置为[]
if (!validSubList.Contains(userId))
{
foreach (var di in diaryLst)
{
await UpdatePersonalRecommendListToRedis(repository, cacheProvider, emptyPostIds, userId, 1,
di.Key < 0 ? null : (int?)di.Key, null, dayOfMonth);
}
continue;
}
foreach (var di in diaryLst)
{
// 热度会被计算,所以得Clone出副本
var rl = await GetPersonalRecommendList(repository,
diaryLst[di.Key].Select(e => e.Clone()).ToList(), userCntHotCoefficientDic, userId,
rpcClient, 1000);
await UpdatePersonalRecommendListToRedis(repository, cacheProvider, rl.Select(i => i.id).ToArray(),
userId, 1, di.Key < 0 ? null : (int?)di.Key, null,
dayOfMonth);
}
}
}
syncNote.Append($"含用户个性化推荐【{userIds.Count}】");
}
if (syncRecordId > 0)
{
var endAt = DateTime.Now;
await repository.QueryAsync("update n_post_recommend_records set endAt=@endAt, totalSecs=@totalSecs, note=@note where id=@id", new
{
id = syncRecordId,
endAt,
totalSecs = (float)(endAt - now).TotalSeconds,
note = syncNote.ToString()
}, null, null, false);
}
}
///
/// 生成新文章推荐缓存,如果对应的缓存已存在,则不生成
///
public static async Task GenerateNewPostRecommend(this DbRESTFulRepository repository, DateTime time,
int categoryType, int? contentType, bool? hasTopic, RedisCacheProvider cacheProvider, ConfigFromDb config)
{
var minute = (time.Minute / 5) * 5;
DateTime startTime;
// 查询上次推荐缓存同步时间,如果有,则取同步时间,如果此时间与当前时间相差超过一天,则取24小时前的时间
var syncTime =
await repository.QuerySingleOrDefaultAsync(
"select startAt from n_post_recommend_records order by id desc limit 1");
if (syncTime != null)
{
if ((syncTime.Value - time).TotalDays > 1)
{
startTime = time.AddHours(-config.PostIgnoreHotRecentHours - 24);
}
else
{
startTime = syncTime.Value.AddHours(-config.PostIgnoreHotRecentHours);
}
}
else
{
startTime = time.AddHours(-config.PostIgnoreHotRecentHours);
}
var startPostId = IdWorker.GetStartIdByDate(startTime);
var key = CacheKeys.PostNewRecommendCache($"{time.ToString("MMddHH")}{minute.ToString("00")}",
categoryType, contentType, hasTopic);
if (await cacheProvider.KeyExist(key)) return;
var mustConditions = new List
{
new { range = new { id = new { gt = startPostId } } },
new { term = new { state = 1 } },
new { range = new { subjectiveScore = new { gt = 0 } } },
new { term = new { categoryType } },
};
if (contentType == 1)
{
mustConditions.Add(new { term = new { type = 0 } });
mustConditions.Add(new { range = new { coverWidth = new { gt = 0 } } });
}
else if (contentType == 2)
{
mustConditions.Add(new { term = new { type = 1 } });
}
var postList = new List<(double, object)>();
var query = new
{
_source = new string[] { "id", "subjectiveScore" },
size = 10000,
query = new
{
@bool = new
{
filter = new
{
@bool = new
{
must = mustConditions
}
}
}
}
};
var resp = await ESHelper.Client.SearchAsync(ESConstants.ESIndexName.Post,
PostData.Serializable(query));
if (resp.Success)
{
var obj = JsonConvert.DeserializeObject(resp.Body);
int curScore = 0;
obj.Value("hits")?.Value("hits")?.ForEach(e =>
{
var source = e.Value("_source");
curScore += (int)Math.Ceiling(source.Value("subjectiveScore"));
postList.Add((curScore, source.Value("id")));
});
}
else
{
LoggerManager.Logger.Error("GetHotPostList ES查询出错 " + resp?.Body);
}
if (postList.Count == 0)
{
postList.Add((0, 0));
}
await cacheProvider.CSRedisClient.ZAddAsync(key, postList.ToArray());
// 缓存10分钟
await cacheProvider.CSRedisClient.ExpireAsync(key, 60 * 10);
}
///
/// 批量更新文章的冷却值
///
public static async Task RecalcPostCoolingFactorAll(this DbRESTFulRepository repository, long startPostId, long? endPostId, ConfigFromDb config)
{
var batchSize = 1000;
using var conn = repository.ConnectionManager.GetConnection(false);
conn.Open();
while (true)
{
var extQuery = endPostId == null ? "" : $" and id<{endPostId}";
var infos = (await repository.QueryAsync($"select id, userId, coolingFactor from n_post where id>{startPostId} {extQuery} and state=1 and categoryType=1 order by id asc limit {batchSize}", null, conn)).ToList();
if (infos.Count == 0) break;
var firstPostTime = IdWorker.DecodeId(infos[0].id).Time;
var localMinId = IdWorker.GetStartIdByDate(firstPostTime.AddHours(config.PostCoolingPeriodHours)); // 满足条件可在本地计算冷却系数的最小文章编号
var localStartIndex = infos.FindIndex(e => e.id > localMinId); // 此这里开始就可以在本地算冷却系数了
var dic = new Dictionary();
if (localStartIndex > -1)
{
for (var j = localStartIndex; j < infos.Count; j++)
{
var item = infos[j];
var id = (long)item.id;
var minId = IdWorker.GetStartIdByDate(IdWorker.DecodeId(id).Time.AddHours(-config.PostCoolingPeriodHours));
// 向前找X小时内同用户的帖子数量
var count = 1;
for (var i = j - 1; i >= 0; i--)
{
var item1 = infos[i];
if (item1.userId == item.userId && item1.id > minId)
{
count++;
}
}
float val;
if (count <= config.PostIgnoreCoolingMaxCountPerPeriod)
{
val = 1;
}
else
{
val = (float)(1 / Math.Pow(count, 0.5));
}
if (item.coolingFactor != val)
{
dic[id] = val;
}
}
}
else
{
localStartIndex = infos.Count;
}
var cacheUserPostIds = new Dictionary>();
for (var j = 0; j < localStartIndex; j++)
{
var item = infos[j];
var id = (long)item.id;
var minId = IdWorker.GetStartIdByDate(IdWorker.DecodeId((long)item.id).Time.AddHours(-config.PostCoolingPeriodHours));
var userId = (long)item.userId;
List cachedList;
if (cacheUserPostIds.ContainsKey(userId))
{
cachedList = cacheUserPostIds[userId];
}
else
{
cachedList = (await repository.QueryAsync($"select id from n_post where id>{minId} and userId={userId} and state=1 order by id asc")).ToList();
cacheUserPostIds[userId] = cachedList;
}
var count = cachedList.Count(e => e > minId);
float val;
if (count <= config.PostIgnoreCoolingMaxCountPerPeriod)
{
val = 1;
}
else
{
val = (float)(1 / Math.Pow(count, 0.5));
}
if (item.coolingFactor != val)
{
dic[id] = val;
}
cachedList.Add(item.id);
}
if (dic.Count > 0)
{
// 更新冷却值
var list = dic.Select(e => new
{
id = e.Key,
coolingFactor = e.Value
});
await repository.QueryAsync("update n_post set coolingFactor=@coolingFactor, updateAt=now() where id=@id", list, conn, null, false);
}
if (infos.Count < batchSize) break;
startPostId = infos.Last().id;
}
}
///
/// 更新单一某个文章的冷却值,同时会更新其后面的N个
///
/// 不传则从数据库中查询
public static async Task RecalcPostCoolingFactor(this DbRESTFulRepository repository, long postId, long authorId, ConfigFromDb config)
{
// 计算冷却系数
var postCoolingPeriodHours = config.PostCoolingPeriodHours; // 冷却小时数,即在多久内计算冷却系数
var postIgnoreCoolingMaxCountPerPeriod = config.PostIgnoreCoolingMaxCountPerPeriod; // 在冷却周期内,小于等于此值时,冷却系数为1
// 找出此文章冷却向前向后小时已发布的文章,计算当前的以及更新向后的
var createAt = IdWorker.DecodeId(postId).Time;
var startId = IdWorker.GetStartIdByDate(createAt.AddHours(-postCoolingPeriodHours));
var endId = IdWorker.GetStartIdByDate(createAt.AddHours(postCoolingPeriodHours));
if (authorId == 0)
{
authorId = await repository.QuerySingleOrDefaultAsync($"select userId from n_post where id={postId}");
}
// 未找到记录,则无法计算
if (authorId == 0) return;
var postInfos = (await repository.QueryAsync($"select id, coolingFactor, state from n_post where id>{startId} and id<{endId} and userId={authorId} order by id asc")).ToList();
var beforeCount = 1;
var isBefore = true;
var dic = new Dictionary();
for (var i = 0; i < postInfos.Count; i++)
{
var id = (long)postInfos[i].id;
var state = (int)postInfos[i].state;
if (id != postId)
{
if (isBefore)
{
if (state == 1) beforeCount++;
}
else
{
// 如果状态不是通过,则不计算
if (state == 1)
{
var _createAt = IdWorker.DecodeId(id).Time;
var _startId = IdWorker.GetStartIdByDate(_createAt);
var tmp = 1;
for (var j = i; j >= 0; j--)
{
if ((long)postInfos[j].id > _startId && postInfos[j].state == 1)
{
tmp++;
}
}
float val = 1;
if (tmp > postIgnoreCoolingMaxCountPerPeriod)
{
val = (float)(1 / Math.Pow(tmp, 0.5));
}
if (val != (float)postInfos[i].coolingFactor)
{
dic[id] = val;
}
}
}
}
else
{
isBefore = false;
if (beforeCount >= 1 && state == 1)
{
float val = 1;
// 计算当前的coolingfact
if (beforeCount > postIgnoreCoolingMaxCountPerPeriod)
{
val = (float)(1 / Math.Pow(beforeCount, 0.5));
}
if (val != (float)postInfos[i].coolingFactor)
{
dic[id] = val;
}
}
}
}
if (dic.Count > 0)
{
// 更新冷却值
var list = dic.Select(e => new
{
id = e.Key,
coolingFactor = e.Value
});
await repository.QueryAsync("update n_post set coolingFactor=@coolingFactor, updateAt=now() where id=@id", list, null, null, false);
}
}
///
/// 更新所有文章的平台主观策略分、计算客观事实分 以及 热度值(此处不计算文章的冷却系数)
///
public static async Task UpdateAllPostContentScore(this DbRESTFulRepository repository, ConfigFromDb config)
{
var size = 1000;
var now = DateTime.Now;
var validEndTime = now.AddHours(-config.PostIgnoreHotRecentHours);
var endId = IdWorker.GetStartIdByDate(validEndTime);
var sql =
$"select p.id, p.userId, p.type, p.categoryType, p.checkCntScore, p.manualExtraScore, p.coolingFactor, u.mpRole, u.superior, u.cntHotCoefficient from n_post as p inner join n_user as u on p.userId=u.id where p.id>@startId and p.id<{endId} and categoryType=1 and p.state=1 and u.state=0 order by p.id asc limit {size}";
using var conn = repository.ConnectionManager.GetConnection();
conn.Open();
var calcTaskId = await repository.QuerySingleOrDefaultAsync(
"insert into n_post_hot_all_calc_records(startAt) values(@startAt); select last_insert_id() as id;",
new { startAt = now }, null, null, false);
var totalCount = 0;
var startId = IdWorker.GetStartIdByDate(
string.IsNullOrEmpty(config.PostStartRecommendDate)
? DateTime.Now.AddDays(-config.PostMaxRecommendDays)
: DateTime.Parse(config.PostStartRecommendDate)
);
var originalStartId = startId;
while (true)
{
var items = (await repository.QueryAsync(sql, new { startId }, conn)).ToList();
if (items.Count == 0)
{
break;
}
totalCount += items.Count;
var itemsCount = items.Count;
var lastId = items.Last().id;
// 用来存文章热度值中间值
var dic = new Dictionary();
items.ForEach(e => dic[(long)e.id] = new PostHotValue());
var itemsInBlackList = new List();
// 查出在小黑屋的,如果在小黑屋,则热度为0
var postIdInBlacklist =
(await repository.QueryAsync(
$"select b.postId from n_stat_post_blacklist as b left join n_post as p on b.postId=p.id where b.postId>{startId} and b.postId<={lastId} and b.ctrlType>0"))
.ToList();
if (postIdInBlacklist.Count > 0)
{
var validItems = new List();
items.ForEach(e =>
{
if (!postIdInBlacklist.Contains(e.id))
{
validItems.Add(e);
}
else
{
itemsInBlackList.Add(e);
}
});
items = validItems;
}
// 计算当前文章列表的有效阅读量
// 判断是否是今天的内容,如果是,则需要查出今天的有效阅读量
var todayStartTime = now.AddHours(-now.Hour).AddMinutes(-now.Minute).AddSeconds(-now.Second)
.AddMilliseconds(-now.Millisecond);
var todayStartId = IdWorker.GetStartIdByDate(todayStartTime);
var todayItems = items.Where(e => e.id > todayStartId && e.id < endId && e.checkCntScore > 0).ToList();
if (todayItems.Count > 0)
{
var todayItemIds = todayItems.Select(e => (long)e.id).ToList();
#region 统计有效阅读量
// 统计阅读量
var resp = await ESHelper.Client.SearchAsync("postreadrecord",
PostData.Serializable(new
{
query = new
{
@bool = new
{
must = new List
{
new
{
range = new
{
createAt = new
{
gte = todayStartTime.ToString("yyyy-MM-dd HH:mm:ss")
}
}
},
new
{
terms = new
{
postId = todayItemIds
}
}
},
must_not = new List
{
new
{
term = new { notValidRecord = true }
}
}
}
},
size = 0,
aggs = new
{
post = new
{
terms = new { field = "postId", size = 50000 }
}
}
}));
if (resp.Success)
{
var rst = JsonConvert.DeserializeObject(resp.Body);
var buckets = rst.Value("aggregations").Value("post")
.Value("buckets");
if (buckets.Count > 0)
{
buckets.ForEach(e =>
{
var postId = e.Value("key");
var count = e.Value("doc_count") * 3;
dic[postId].fact += count;
});
}
}
// 统计分享量
resp = await ESHelper.Client.SearchAsync("postsharerecord", PostData.Serializable(
new
{
query = new
{
@bool = new
{
must = new List
{
new
{
range = new
{
createAt = new
{
gte = todayStartTime.ToString("yyyy-MM-dd HH:mm:ss")
}
}
},
new
{
terms = new
{
postId = todayItemIds
}
}
},
must_not = new List
{
new
{
term = new { notValidRecord = true }
}
}
}
},
size = 0,
aggs = new
{
post = new
{
terms = new { field = "postId", size = 50000 }
}
}
}));
if (resp.Success)
{
var rst = JsonConvert.DeserializeObject(resp.Body);
var buckets = rst.Value("aggregations").Value("post")
.Value("buckets");
if (buckets.Count > 0)
{
buckets.ForEach(e =>
{
var postId = e.Value("key");
var count = e.Value("doc_count") * 6;
dic[postId].fact += count;
});
}
}
var todayStartTimeStr = todayStartTime.ToString("yyyy-MM-dd HH:mm:ss");
// 统计有效点赞
var list = (await repository.QueryAsync(@$"
SELECT tg.targetId as postId, count(1) as count FROM `n_user_thumbsup_gain` as tg left join n_stat_thumbsup_history as th on tg.targetId=th.postId and tg.userId=th.userId where tg.targetId>{startId} and tg.targetId<{endId} and
tg.userId<>tg.authorId and tg.type=0 and tg.categoryType=1 and tg.createAt>='{todayStartTimeStr}' and
th.userId is null
group by tg.targetId
")).ToList();
if (list.Count > 0)
{
list.ForEach(e =>
{
if (dic.ContainsKey(e.postId))
{
dic[(long)e.postId].fact += e.count * 6;
}
});
}
// 统计有效收藏
list = (await repository.QueryAsync(@$"
SELECT tg.postId, count(1) as count FROM `n_user_collect_post` as tg left join n_stat_collection_history as th on tg.postId=th.postId and tg.userId=th.userId where tg.postId>{startId} and tg.postId<{endId} and
tg.userId<>tg.authorId and tg.categoryType=1 and tg.createAt>='{todayStartTimeStr}' and
th.userId is null
group by tg.postId
")).ToList();
if (list.Count > 0)
{
list.ForEach(e =>
{
if (dic.ContainsKey(e.postId))
{
dic[(long)e.postId].fact += e.count * 6;
}
});
}
// 统计有效评论
list = (await repository.QueryAsync(@$"
select t1.postId, count(1) as count from (
select distinct t.userId, t.postId from (
select distinct c.userId, c.postId from n_post_comment as c
where c.state=0 and c.isAuthor=0 and c.createAt>='{todayStartTimeStr}' and c.postId>{startId} and c.postId<{endId}
UNION
select distinct r.userId, r.postId from n_post_reply as r left join n_post_comment as c1 on r.commentId=c1.id
where r.state=0 and r.isAuthor=0 and r.createAt>='{todayStartTimeStr}' and c1.state=0 and r.postId>{startId} and r.postId<{endId}
) as t
) as t1 left join n_stat_comment_history as ch on t1.postId=ch.postId and t1.userId=ch.userId
where ch.postId is null
group by t1.postId
")).ToList();
if (list.Count > 0)
{
list.ForEach(e =>
{
if (dic.ContainsKey(e.postId))
{
dic[(long)e.postId].fact += e.count * 6;
}
});
}
#endregion 统计有效阅读量
}
// 看是否存在非今日的,如果存在,则需要在每日统计表中查询数据
var notTodayItems = items.Where(e => e.id < todayStartId && e.id < endId && e.checkCntScore > 0)
.ToList();
if (notTodayItems.Count > 0)
{
var notTodayItemIds = notTodayItems.Select(e => (long)e.id).ToList();
// 各表分别统计
var readStat = await repository.QueryAsync(
"select postId, sum(normalReadCount+fanReadCount) as count from n_stat_read where postId in @ids group by postId",
new { ids = notTodayItemIds });
readStat.ForEach(e => { dic[(long)e.postId].fact += (float)e.count * 3; });
var commentStat = await repository.QueryAsync(
"select postId, sum(commentCount) as count from n_stat_comment where postId in @ids group by postId",
new { ids = notTodayItemIds });
commentStat.ForEach(e => { dic[(long)e.postId].fact += (float)e.count * 6; });
var thumbsupStat = await repository.QueryAsync(
"select postId, sum(thumbsupCount) as count from n_stat_thumbsup where postId in @ids group by postId",
new { ids = notTodayItemIds });
thumbsupStat.ForEach(e => { dic[(long)e.postId].fact += (float)e.count * 6; });
var collectionStat = await repository.QueryAsync(
"select postId, sum(collectionCount) as count from n_stat_collection where postId in @ids group by postId",
new { ids = notTodayItemIds });
collectionStat.ForEach(e => { dic[(long)e.postId].fact += (float)e.count * 6; });
var shareStat = await repository.QueryAsync(
"select postId, sum(shareCount) as count from n_stat_share where postId in @ids group by postId",
new { ids = notTodayItemIds });
shareStat.ForEach(e => { dic[(long)e.postId].fact += (float)e.count * 6; });
}
// 如果客观事实数据最大为3500
dic.ForEach(e =>
{
if (e.Value.fact > 3500) dic[e.Key].fact = 3500;
});
var esRecords = new List();
items.ForEach(e =>
{
var id = (long)e.id;
var roleScore = PostHotValue.GetRoleScore(e.mpRole, e.superior > 0);
var checkCntScore = (float)e.checkCntScore;
var cntTypeFactor = e.type > 0 ? 1.3f : 1;
var coolingFactor = (float)e.coolingFactor;
var manualExtraScore = (float)e.manualExtraScore;
var cntHotCoefficient = (float)e.cntHotCoefficient;
var subjectiveScore = checkCntScore > 0
? (roleScore + checkCntScore) * cntTypeFactor * coolingFactor + manualExtraScore
: 0;
var days = Math.Ceiling((now - IdWorker.DecodeId(id).Time).TotalDays);
if (days == 0) days = 1;
var objectiveScore = dic.ContainsKey(id) ? dic[id].fact : 0;
var cntHot = checkCntScore > 0
? (float)((subjectiveScore + objectiveScore) / Math.Pow(days, 1.6) * cntHotCoefficient)
: 0;
esRecords.Add(new
{
update = new
{
_id = id
}
});
esRecords.Add(new
{
doc = new
{
subjectiveScore,
hot = cntHot
}
});
});
if (esRecords.Count > 0)
{
var resp = await ESHelper.Client.BulkAsync(ESConstants.ESIndexName.Post,
PostData.MultiJson(esRecords));
if (!resp.Success)
{
LoggerManager.Logger.Error("ES更新笔记热度值失败\r\n" + resp.Body);
}
}
startId = lastId;
if (itemsCount < size) break;
}
if (originalStartId > 0)
{
// 将设置过期的文章热度值为0
var rsp = await ESHelper.Client.UpdateByQueryAsync(ESConstants.ESIndexName.Post,
PostData.Serializable(new
{
script = new { source = $"ctx._source['hot']=0" },
query = new
{
@bool = new
{
must = new List
{
new { term = new { categoryType = 1 } },
new { range = new { hot = new { gt = 0 } } },
new { range = new { id = new { lt = originalStartId } } },
}
}
}
}));
if (!rsp.Success)
{
LoggerManager.Logger.Error("ES文章热度归零失败:\r\n" + rsp.Body);
}
}
if (calcTaskId > 0)
{
var endAt = DateTime.Now;
await repository.QueryAsync(
"update n_post_hot_all_calc_records set endAt=@endAt, totalSecs=@totalSecs, note=@note where id=@id",
new
{
id = calcTaskId,
endAt,
totalSecs = (float)(endAt - now).TotalSeconds,
note = $"处理总计{totalCount}条文章记录"
}, null, null, false);
}
}
///
/// 计算某些文章的平台主观策略分、客观事实分 以及 热度值(此处不计算文章的冷却系数)
///
/// 是否计算客观事件(有效阅读)及综合热度值
/// 额外附加的人工干预分数,用来测算合适的人工干预分
public static async Task> CalcPostsHot(this DbRESTFulRepository repository, List postIds, ConfigFromDb config, bool calcReadScore = true, float? extraManualExtraScore = null, DateTime? specifyCalcTime = null)
{
postIds = postIds.OrderBy(e => e).ToList();
// 用来存文章热度值中间值
var dic = new Dictionary();
postIds.ForEach(e =>
{
dic[e] = new PostHotValue();
});
if (postIds.Count == 0)
{
return dic;
}
var sql = $"select p.id, p.userId, p.type, p.categoryType, p.checkCntScore, p.manualExtraScore, p.coolingFactor, u.mpRole, u.superior, u.cntHotCoefficient from n_post as p left join n_user as u on p.userId=u.id where p.id in @postIds and categoryType=1 and p.state=1 and p.checkCntScore>0 order by p.id asc";
using var conn = repository.ConnectionManager.GetConnection();
conn.Open();
var items = (await repository.QueryAsync(sql, new { postIds }, conn)).ToList();
if (items.Count == 0)
{
return dic;
}
var calcTime = specifyCalcTime ?? DateTime.Now;
var itemsInBlackList = new List();
var validPostIds = items.Select(e => (long)e.id).ToList();
if (validPostIds.Count > 0)
{
// 查出在小黑屋的,如果在小黑屋,则热度为0
var postIdInBlacklist = (await repository.QueryAsync($"select postId from n_stat_post_blacklist where postId in @postIds and ctrlType>0", new { postIds = validPostIds }, conn)).ToList();
if (postIdInBlacklist.Count > 0)
{
var validItems = new List();
items.ForEach(e =>
{
if (postIdInBlacklist.Contains(e.id))
{
validItems.Add(e);
}
else
{
itemsInBlackList.Add(e);
}
});
items = validItems;
}
}
// 综合热度及客观事实分
// 不计算最近6小时内的
var validEndTime = calcTime.AddHours(-config.PostIgnoreHotRecentHours);
var validEndId = IdWorker.GetStartIdByDate(validEndTime);
var expiredMinId = IdWorker.GetStartIdByDate(
string.IsNullOrEmpty(config.PostStartRecommendDate)
? DateTime.Now.AddDays(-config.PostMaxRecommendDays)
: DateTime.Parse(config.PostStartRecommendDate)
);
if (calcReadScore)
{
// 计算当前文章列表的有效阅读量
// 判断是否是今天的内容,如果是,则需要查出今天的有效阅读量
var todayStartTime = calcTime.AddHours(-calcTime.Hour).AddMinutes(-calcTime.Minute).AddSeconds(-calcTime.Second).AddMilliseconds(-calcTime.Millisecond);
var todayStartId = IdWorker.GetStartIdByDate(todayStartTime);
var todayItems = items.Where(e => e.id > todayStartId && e.id < validEndId).ToList();
if (todayItems.Count > 0)
{
var todayItemIds = todayItems.Select(e => (long)e.id).ToList();
#region 统计有效阅读量
// 统计阅读量
var resp = await ESHelper.Client.SearchAsync("postreadrecord", PostData.Serializable(new
{
query = new
{
@bool = new
{
must = new List
{
new
{
range = new
{
createAt = new
{
gte = todayStartTime.ToString("yyyy-MM-dd HH:mm:ss")
}
}
},
new
{
terms = new
{
postId = todayItemIds
}
}
},
must_not = new List
{
new
{
term = new { notValidRecord = true }
}
}
}
},
size = 0,
aggs = new
{
post = new
{
terms = new { field = "postId", size = 50000 }
}
}
}));
if (resp.Success)
{
var rst = JsonConvert.DeserializeObject(resp.Body);
var buckets = rst.Value("aggregations").Value("post").Value("buckets");
if (buckets.Count > 0)
{
buckets.ForEach(e =>
{
var postId = e.Value("key");
var count = e.Value("doc_count") * 3;
dic[postId].fact += count;
});
}
}
// 统计分享量
resp = await ESHelper.Client.SearchAsync("postsharerecord", PostData.Serializable(new
{
query = new
{
@bool = new
{
must = new List
{
new
{
range = new
{
createAt = new
{
gte = todayStartTime.ToString("yyyy-MM-dd HH:mm:ss")
}
}
},
new
{
terms = new
{
postId = todayItemIds
}
}
},
must_not = new List
{
new
{
term = new { notValidRecord = true }
}
}
}
},
size = 0,
aggs = new
{
post = new
{
terms = new { field = "postId", size = 50000 }
}
}
}));
if (resp.Success)
{
var rst = JsonConvert.DeserializeObject(resp.Body);
var buckets = rst.Value("aggregations").Value("post").Value("buckets");
if (buckets.Count > 0)
{
buckets.ForEach(e =>
{
var postId = e.Value("key");
var count = e.Value("doc_count") * 6;
dic[postId].fact += count;
});
}
}
var todayStartTimeStr = todayStartTime.ToString("yyyy-MM-dd HH:mm:ss");
// 统计有效点赞
var list = (await repository.QueryAsync(@$"
SELECT tg.targetId as postId, count(1) as count FROM `n_user_thumbsup_gain` as tg left join n_stat_thumbsup_history as th on tg.targetId=th.postId and tg.userId=th.userId where tg.targetId in @todayItemIds and
tg.userId<>tg.authorId and tg.type=0 and tg.categoryType=1 and tg.createAt>='{todayStartTimeStr}' and
th.userId is null
group by tg.targetId
", new { todayItemIds }, conn)).ToList();
if (list.Count > 0)
{
list.ForEach(e =>
{
dic[(long)e.postId].fact += e.count * 6;
});
}
// 统计有效收藏
list = (await repository.QueryAsync(@$"
SELECT tg.postId, count(1) as count FROM `n_user_collect_post` as tg left join n_stat_collection_history as th on tg.postId=th.postId and tg.userId=th.userId where tg.postId in @todayItemIds and
tg.userId<>tg.authorId and tg.categoryType=1 and tg.createAt>='{todayStartTimeStr}' and
th.userId is null
group by tg.postId
", new { todayItemIds }, conn)).ToList();
if (list.Count > 0)
{
list.ForEach(e =>
{
if (dic.ContainsKey(e.postId))
{
dic[(long)e.postId].fact += e.count * 6;
}
});
}
// 统计有效评论
list = (await repository.QueryAsync(@$"
select t1.postId, count(1) as count from (
select distinct t.userId, t.postId from (
select distinct c.userId, c.postId from n_post_comment as c
where c.state=0 and c.isAuthor=0 and c.createAt>='{todayStartTimeStr}' and c.postId in @todayItemIds
UNION
select distinct r.userId, r.postId from n_post_reply as r left join n_post_comment as c1 on r.commentId=c1.id
where r.state=0 and r.isAuthor=0 and r.createAt>='{todayStartTimeStr}' and c1.state=0 and r.postId in @todayItemIds
) as t
) as t1 left join n_stat_comment_history as ch on t1.postId=ch.postId and t1.userId=ch.userId
where ch.postId is null
group by t1.postId
", new { todayItemIds }, conn)).ToList();
if (list.Count > 0)
{
list.ForEach(e =>
{
dic[(long)e.postId].fact += e.count * 6;
});
}
#endregion 统计有效阅读量
}
// 看是否存在非今日的,如果存在,则需要在每日统计表中查询数据
var notTodayItems = items.Where(e => e.id < todayStartId && e.id > expiredMinId && e.id < validEndId).ToList();
if (notTodayItems.Count > 0)
{
var notTodayItemIds = notTodayItems.Select(e => (long)e.id).ToList();
// 各表分别统计
var readStat = await repository.QueryAsync("select postId, sum(normalReadCount+fanReadCount) as count from n_stat_read where postId in @ids group by postId", new { ids = notTodayItemIds }, conn);
readStat.ForEach(e =>
{
dic[(long)e.postId].fact += (float)e.count * 3;
});
var commentStat = await repository.QueryAsync("select postId, sum(commentCount) as count from n_stat_comment where postId in @ids group by postId", new { ids = notTodayItemIds }, conn);
commentStat.ForEach(e =>
{
dic[(long)e.postId].fact += (float)e.count * 6;
});
var thumbsupStat = await repository.QueryAsync("select postId, sum(thumbsupCount) as count from n_stat_thumbsup where postId in @ids group by postId", new { ids = notTodayItemIds }, conn);
thumbsupStat.ForEach(e =>
{
dic[(long)e.postId].fact += (float)e.count * 6;
});
var collectionStat = await repository.QueryAsync("select postId, sum(collectionCount) as count from n_stat_collection where postId in @ids group by postId", new { ids = notTodayItemIds });
collectionStat.ForEach(e =>
{
dic[(long)e.postId].fact += (float)e.count * 6;
});
var shareStat = await repository.QueryAsync("select postId, sum(shareCount) as count from n_stat_share where postId in @ids group by postId", new { ids = notTodayItemIds }, conn);
shareStat.ForEach(e =>
{
dic[(long)e.postId].fact += (float)e.count * 6;
});
}
// 如果客观事实数据最大为3500
dic.ForEach(e =>
{
if (e.Value.fact > 3500) dic[e.Key].fact = 3500;
});
}
items.ForEach(e =>
{
var id = (long)e.id;
var roleScore = PostHotValue.GetRoleScore(e.mpRole, e.superior > 0);
var checkCntScore = (float)e.checkCntScore;
var cntTypeFactor = e.type > 0 ? 1.3f : 1;
var coolingFactor = (float)e.coolingFactor;
var manualExtraScore = ((float)e.manualExtraScore + (extraManualExtraScore ?? 0));
var cntHotCoefficient = (float)e.cntHotCoefficient;
var subjectiveScore = checkCntScore > 0 ? (roleScore + checkCntScore) * cntTypeFactor * coolingFactor + manualExtraScore : 0;
var expired = e.id < expiredMinId;
dic[id].subjectiveScore = subjectiveScore;
dic[id].cntHotCoefficient = cntHotCoefficient;
if (calcReadScore && !expired && id < validEndId)
{
var days = (float)Math.Ceiling((calcTime - IdWorker.DecodeId(id).Time).TotalDays);
if (days == 0) days = 1;
var objectiveScore = dic.ContainsKey(id) ? dic[id].fact : 0;
dic[id].hot = (float)((subjectiveScore + objectiveScore) / Math.Pow(days, 1.6) * cntHotCoefficient);
}
});
return dic;
}
///
/// 同步单篇内容
///
public static async Task SyncPostSingle(this DbRESTFulRepository repository, long id, OSSConfig ossConfig, OssClient ossClient, ConfigFromDb config, bool calcReadScore = true)
{
var postInfo = await repository.QuerySingleOrDefaultAsync("select id, state, title, thumbnails, type, originalTopicNames, tags, summary, contentState, videoId, content, createAt, userId, categoryType, onlyInTopic, noMoreCont, resourceCode, primaryType, otherPrimaryTypes, isBest, coverWidth, coverHeight, previewable, reprintSource, reprintLink, checkCntScore from n_post where id=@id", new { id });
if (postInfo == null)
{
await ESHelper.Client.DeleteAsync(ESConstants.ESIndexName.Post, id.ToString());
}
else
{
await repository.RecalcPostCoolingFactor(id, (long)postInfo.userId, config);
await repository.SyncPostList2ES(DateTime.Now, new List { postInfo }, ossConfig, ossClient, config, calcReadScore);
}
}
///
/// 返回最后一条同步的动态Id,如果没有,则返回null。(id, state, title, thumbnails, type, originalTopicNames, tags, summary, contentState, videoId, content, createAt, userId, categoryType, onlyInTopic, noMoreCont, resourceCode, primaryType, otherPrimaryTypes, isBest, coverWidth, coverHeight, previewable, reprintSource, reprintLink, checkCntScore)
///
public static async Task SyncPostList2ES(this DbRESTFulRepository repository, DateTime now, List posts, OSSConfig ossConfig, OssClient ossClient, ConfigFromDb config, bool calcReadScore = true)
{
if (posts.Count == 0) return null;
long? lastId = null;
var userIds = posts.Select(e => e.userId).Distinct().ToList();
Dictionary userDic = null;
if (userIds.Count > 0)
{
var users = (await repository.QueryAsync("select id, alias from n_user where id in @ids", new { ids = userIds })).ToList();
userDic = new Dictionary(users.Count);
foreach (var item in users)
{
userDic[item.id] = item.alias;
}
}
var postTopicNamesDic = new Dictionary>();
var allTopicNames = new HashSet();
posts.Where(e => !string.IsNullOrEmpty((string)e.originalTopicNames)).ForEach(e =>
{
var names = ((string)e.originalTopicNames).Split('\n', StringSplitOptions.RemoveEmptyEntries).Distinct().ToList();
postTopicNamesDic[(long)e.id] = names;
names.ForEach(n => allTopicNames.Add(n));
});
Dictionary topicNameInfoDic = new Dictionary();
if (allTopicNames.Count > 0)
{
var topics = (await repository.QueryAsync("select id, title, disabled from n_topic where title in @names", new { names = allTopicNames })).ToList();
topics.ForEach(e =>
{
topicNameInfoDic[(string)e.title] = e;
});
}
var postIds = new List();
var statistic = await repository.QueryAsync("select postId, hot, thumbsup, collected, `comment`, `read` from n_post_statistic where postId in @ids", new { ids = posts.Select(e => e.id) });
var statisticDic = new Dictionary();
foreach (var item in statistic)
{
statisticDic[(long)item.postId] = item;
}
var resourceDic = new Dictionary();
var resourceCodes = new HashSet();
posts.ForEach(e =>
{
postIds.Add((long)e.id);
if (!string.IsNullOrEmpty(e.resourceCode))
{
((string)e.resourceCode).Split(',', StringSplitOptions.RemoveEmptyEntries).ForEach(i => resourceCodes.Add(i));
}
});
if (resourceCodes.Count > 0)
{
var resourceInfos = await repository.QueryAsync("select code, name from n_resource where code in @codes", new { codes = resourceCodes.ToList() });
resourceInfos.ForEach(e =>
{
resourceDic.Add(e.code, e.name);
});
}
var hotDic = await repository.CalcPostsHot(postIds, config, calcReadScore);
var records = new List();
foreach (var post in posts)
{
var postId = (long)post.id;
records.Add(new
{
delete = new { _id = postId }
});
// ES只存储审核通过且内容完整(即视频是上传处理完成的)的动态
if (post.state != 1 || post.contentState != 1) continue;
records.Add(new
{
create = new { _id = postId }
});
string tagsStr = post.tags;
List tags = tagsStr.Split(',', StringSplitOptions.RemoveEmptyEntries).Distinct().ToList();
var statisticItem = statisticDic.ContainsKey(post.id) ? statisticDic[post.id] : null;
string content = string.Empty;
try
{
if (post.noMoreCont > 0)
{
content = post.content ?? string.Empty;
}
else
{
content = await FileOperator.GetFileContent("jzq_" + post.id.ToString(), ossConfig.OSSBucket, ossClient);
}
}
catch
{
// ignored
}
var primaryTypes = new List();
primaryTypes.Add(post.primaryType.ToString());
if (!string.IsNullOrEmpty(post.otherPrimaryTypes))
{
primaryTypes.AddRange((post.otherPrimaryTypes as string).Split(',', StringSplitOptions.RemoveEmptyEntries));
}
var codes = ((string)post.resourceCode).Split(',', StringSplitOptions.RemoveEmptyEntries);
var validCodes = new List();
var validNames = new List();
codes.ForEach(e =>
{
if (!resourceDic.TryGetValue(e, out var value)) return;
validCodes.Add(e);
validNames.Add(value);
});
var topicInfoList = postTopicNamesDic.ContainsKey(postId) && postTopicNamesDic[postId].Count > 0 ? postTopicNamesDic[postId].Where(e => topicNameInfoDic.ContainsKey(e)).Select(e => topicNameInfoDic[e]).ToList() : new List();
var topicId = 0;
var topicName = string.Empty;
int[] topicIds = null;
int[] validTopicIds = null;
string[] topicNames = null;
string[] validTopicNames = null;
if (topicInfoList.Count > 0)
{
topicIds = topicInfoList.Select(e => (int)e.id).ToArray();
topicNames = topicInfoList.Select(e => (string)e.title).ToArray();
validTopicIds = topicInfoList.Where(e => (int)e.disabled == 0).Select(e => (int)e.id).ToArray();
validTopicNames = topicInfoList.Where(e => (int)e.disabled == 0).Select(e => (string)e.title).ToArray();
topicId = topicInfoList.First().id;
topicName = topicInfoList.First().name;
}
else
{
topicIds = Array.Empty();
topicNames = Array.Empty();
validTopicIds = Array.Empty();
validTopicNames = Array.Empty();
}
records.Add(new ESPostModel()
{
id = postId,
title = post.title,
tags = tags.ToArray(),
thumbnails = post.thumbnails,
type = (short)post.type,
userId = post.userId,
userAlias = userDic != null && userDic.ContainsKey(post.userId) ? userDic[post.userId] : "",
summary = post.summary,
contentState = post.contentState,
videoId = post.videoId,
content = content,
createAt = post.createAt.ToString("yyyy-MM-dd HH:mm:ss"),
subjectiveScore = post.checkCntScore > 0 && hotDic.ContainsKey(postId) ? hotDic[postId].subjectiveScore : 0,
hot = post.checkCntScore > 0 && hotDic.ContainsKey(postId) ? hotDic[postId].hot : 0,
topicId = topicId,
topicName = topicName,
topicIds = topicIds,
topicNames = topicNames,
validTopicIds = validTopicIds,
validTopicNames = validTopicNames,
noMoreCont = post.noMoreCont,
state = (short)post.state,
categoryType = (short)post.categoryType,
onlyInTopic = (short)post.onlyInTopic,
resourceCodes = validCodes.ToArray(),
resourceNames = validNames.ToArray(),
primaryType = post.primaryType,
primaryTypes = primaryTypes.Distinct().ToArray(),
isBest = post.isBest,
previewable = post.previewable > 0,
coverHeight = post.coverHeight,
coverWidth = post.coverWidth,
reprintSource = post.reprintSource,
reprintLink = post.reprintLink,
});
lastId = post.id;
}
await ESHelper.Client.BulkAsync(ESConstants.ESIndexName.Post, PostData.MultiJson(records));
return lastId;
}
///
/// 单独给某一篇文章添加有效阅读量
///
public static async Task AddSingleReadRecord(this DbRESTFulRepository repository, long postId, long authorId, int categoryType, int count = 1)
{
if (count == 0) return;
var esRecrods = new List();
for (int i = 0; i < count; i++)
{
esRecrods.Add(new { create = new { } });
esRecrods.Add(new ESPostReadRecordModel
{
postId = postId,
authorId = authorId,
categoryType = categoryType,
userId = 0,
isAuthor = false,
isFan = false,
isMock = true,
clientId = "",
ip = "",
createAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
});
}
// 存入ES
await ESHelper.Client.BulkAsync("postreadrecord", PostData.MultiJson(esRecrods));
// 更新数据库中统计数据
await repository.QueryAsync($"update n_post_statistic set `read`=`read`+{count}, `normalRead`=`normalRead`+{count} where postId={postId};", null, null, null, false);
}
///
/// 尝试删除某文章对应的热门讨论配置
///
public static async Task HotDiscussTryToRemove(this DbRESTFulRepository repository, long postId, int? topicId = null)
{
var query = topicId > 0 ? $"postId={postId} and topicId={topicId}" : $"postId={postId}";
using var conn = repository.ConnectionManager.GetConnection(false);
conn.Open();
await repository.QueryAsync($"delete from n_post_topic_hot where {query}", null,
conn);
await repository.QueryAsync(@$"update n_post_topic_hot_records set removeAt=now()
where {query} and removeAt is null", null, conn);
}
}
}