Compare commits

..

77 Commits
perf-ai ... abp

Author SHA1 Message Date
橙子
a50c45f7a3 !102 修正删除部门无效问题
Merge pull request !102 from Po/cherry-pick-1765609320
2025-12-14 04:37:59 +00:00
Po
2bc8837155 修正删除部门无效问题 2025-12-13 07:02:19 +00:00
ccnetcore
8a6e5abf48 fix: 修复token鉴权 2025-12-11 23:32:57 +08:00
ccnetcore
8b191330b8 Revert "fix: 仅从 Query 获取 access_token/refresh_token,简化 OnMessageReceived 逻辑"
This reverts commit 0d2f2cb826.
2025-12-11 23:31:29 +08:00
chenchun
0d2f2cb826 fix: 仅从 Query 获取 access_token/refresh_token,简化 OnMessageReceived 逻辑
- 修改文件:Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs
- 将 JwtBearerEvents.OnMessageReceived 的上下文参数名改为 messageContext,统一变量名。
- 简化 Token 获取逻辑:只从 request.Query 中读取 access_token 与 refresh_token,移除从 Cookies(Token)和请求头(refresh_token)读取的分支。
2025-12-11 17:41:38 +08:00
chenchun
571df74c43 chore: 在 common.props 添加 SatelliteResourceLanguages=en;zh-CN
在 Yi.Abp.Net8/common.props 中新增 SatelliteResourceLanguages 属性,指定生成卫星资源语言为 en 和 zh-CN,以便打包对应的本地化资源。
2025-12-10 15:53:18 +08:00
chenchun
cefde6848d perf: 去除35MB又臭又大的腾讯云sdk 2025-12-10 15:10:54 +08:00
ccnetcore
551597765c perf: 优化sqlsguar分页查询 2025-12-07 18:50:02 +08:00
chenchun
5eaffe2ec2 feat: 新增更新并发乐观锁配置与支持
- 在 DbConnOptions 新增 EnabledConcurrencyException(bool,默认 false) 配置项。
- 在 SqlSugarRepository 引入 IAbpLazyServiceProvider,通过 IOptions<DbConnOptions> 延迟获取配置。
- UpdateAsync 改为仅当 EnabledConcurrencyException 为 true 且实体实现 IHasConcurrencyStamp 时,使用 ExecuteCommandWithOptLockAsync 并捕获 VersionExceptions 抛出 AbpDbConcurrencyException;否则回退到原有的 UpdateAsync 实现。
- 清理/调整部分 using 引用,新增 Microsoft.Extensions.Options 与 Volo.Abp.DependencyInjection 引用。

注意:默认值为 false,需在配置中显式开启 EnabledConcurrencyException 才会启用乐观并发校验,开启后会改变之前对带版本实体自动使用乐观锁的行为。
2025-11-17 11:19:15 +08:00
chenchun
2ec7b5f4fd fix: 修复软删除时空引用异常
在 SqlSugarRepository.cs 中对 ISoftDelete 分支增加 GetByIdAsync 返回 null 的判断,避免在实体为 null 时继续反射赋值导致 NullReferenceException。若实体不存在,直接返回 false。
2025-11-13 16:49:44 +08:00
chenchun
4521212a90 feat: 新增文件缓存功能
- 在 Yi.Framework.Rbac.Application.Services.FileService 中注入 IMemoryCache,用于缓存文件元数据,减少对仓储的重复读取。
  - 在 Get 方法中通过 key "File:{code}" 缓存 FileCacheItem,设置绝对过期时间为 1 天。
  - 缓存项使用 Mapster 适配为 FileCacheItem,再适配回 FileAggregateRoot(保留现有逻辑判断和路径获取)。
- 新增缓存模型 Yi.Framework.Rbac.Domain.Shared.Caches.FileCacheItem(包含 Id、FileSize、FileName、FilePath、创建/修改信息等)。
- 增加并调整相关 using 引用(Microsoft.Extensions.Caching.Memory、Volo.Abp.Caching、Domain.Shared.Caches)。
- 同时修复了保存多文件时的缩进/空格格式(不影响功能)。
2025-11-06 11:29:21 +08:00
chenchun
94834f45c3 perf: 使用 FileStreamResult 流式返回文件,避免一次性读取到内存
改为 FileStream 并返回 FileStreamResult,减小内存占用并支持大型文件;修正变量名拼写并添加 null-forgiving 标记。
2025-11-06 11:13:50 +08:00
chenchun
22ac150acd fix: 修正 FileAggregateRoot.FilePath 的赋值,保存目录路径而非包含文件名的完整路径 2025-11-06 10:58:33 +08:00
ccnetcore
1cc5f2a14f refactor: 注释掉 Furion 统一结果 API 注册,保留 ABP 默认处理方式 2025-09-27 17:26:13 +08:00
橙子
d9997eeb28 !100 update Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/UnifyResult/Fiters/FriendlyExceptionFilter.cs.
Merge pull request !100 from Gary/N/A
2025-09-22 07:14:02 +00:00
Gary
06e77aa8fd update Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/UnifyResult/Fiters/FriendlyExceptionFilter.cs.
判断是否为模型验证错误,如果是,将errors传回,并打印日志

Signed-off-by: Gary <1511313969@qq.com>
2025-09-22 07:13:46 +00:00
橙子
e9e2228f6e !97 岗位状态修改
Merge pull request !97 from 嗳摸嫫/jun
2025-09-22 07:13:45 +00:00
橙子
d516a381d0 !98 update Yi.Abp.Net8/framework/Yi.Framework.Mapster/YiFrameworkMapsterModule.cs.
Merge pull request !98 from Gary/N/A
2025-09-22 07:13:26 +00:00
Gary
4e792ba976 update Yi.Abp.Net8/framework/Yi.Framework.Mapster/YiFrameworkMapsterModule.cs.
自动扫描所有继承IRegister 的Mapster 配置

Signed-off-by: Gary <1511313969@qq.com>
2025-09-22 02:58:59 +00:00
chenchun
f90d3871fa feat: 启用 Furion 统一返回结果并优化过滤器配置
- 在 `YiAbpWebModule` 中启用 `AddFurionUnifyResultApi` 以支持 Furion 风格的统一 API 返回格式
- 调整 `UnifyResultExtensions`,移除 `AbpExceptionFilter` 和 `AbpNoContentActionFilter`,确保统一结果过滤器优先执行
2025-09-16 11:48:36 +08:00
HW-July
6005b9329d 岗位状态修改 2025-08-25 17:12:18 +08:00
橙子
9d4b3e7d0c update README.md.
Signed-off-by: 橙子 <454313500@qq.com>
2025-07-05 07:50:10 +00:00
chenchun
72795382a1 style: 调整样式 2025-07-02 15:03:16 +08:00
橙子
35cdff2afa update README.md.
Signed-off-by: 橙子 <454313500@qq.com>
2025-07-02 06:04:18 +00:00
橙子
dcf547f513 style: 新增赞助
Signed-off-by: 橙子 <454313500@qq.com>
2025-07-02 06:03:28 +00:00
橙子
8660d45f36 update README.md.
Signed-off-by: 橙子 <454313500@qq.com>
2025-07-02 06:01:51 +00:00
橙子
63dd55e7a4 !95 修正分页导致部门结构显示异常。取消后台分页功能,同菜单结构无需分页。
Merge pull request !95 from Po/N/A
2025-07-02 03:49:56 +00:00
Po
40cd89f90c 修正分页导致部门结构显示异常。取消后台分页功能,同菜单结构无需分页。
Signed-off-by: Po <448443959@qq.com>
2025-07-02 02:50:06 +00:00
ccnetcore
901ccc7314 feat: 处理短信升级问题 2025-06-02 02:40:22 +08:00
ccnetcore
629add1e8a feat: 支持用户限制 2025-06-02 02:12:38 +08:00
chenchun
8b92cd6bed perf: 支持ai转义 2025-05-01 15:58:43 +08:00
chenchun
e63fb71ef6 fix: 支持ai转义功能 2025-05-01 15:58:03 +08:00
chenchun
aa122d2d82 Merge remote-tracking branch 'origin/abp' into abp 2025-05-01 14:55:45 +08:00
chenchun
5d6bfe36d0 logs: 日志调整 2025-05-01 14:55:32 +08:00
橙子
26dadd7dae !92 优化动态下拉框宽度样式,和延迟获取数据
Merge pull request !92 from JiangCY/abp
2025-04-17 07:09:43 +00:00
橙子
520dca6953 !93 update Yi.RuoYi.Vue3/src/utils/index.js.
Merge pull request !93 from fenngmr/fix-utils-parseTime
2025-04-17 07:09:23 +00:00
fenngmr
699f7febe4 update Yi.RuoYi.Vue3/src/utils/index.js.
Signed-off-by: fenngmr <guo_fengxian@163.com>
2025-04-14 08:11:32 +00:00
橙子
87a14ebac1 feat: 社区新增有偿悬赏功能 2025-04-12 23:18:06 +08:00
JiangCYkk
29573342b5 优化动态下拉框宽度样式,和延迟获取数据 2025-04-08 17:27:26 +08:00
橙子
91b216c06e !91 1.处理删除菜单传参错误的问题;
Merge pull request !91 from JiangCY/abp
2025-03-30 03:29:35 +00:00
JiangCYkk
83a6ec1b98 1.处理删除菜单传参错误的问题;
2.处理字典数据新增修改窗口没有提交按钮的问题;
2025-03-28 16:46:05 +08:00
JiangCYkk
c5d636d697 添加动态数据下拉框,可以通过下拉框筛选获取后台数据;
未实现滚动分页;
2025-03-28 16:45:36 +08:00
JiangCYkk
3d704220f3 添加数据库上下文改为 services.Add() 方法,TryAdd() 让后面的 YiRbacDbContext 无法注入,导致数据权限过滤失效了 2025-03-28 16:45:26 +08:00
橙子
b830317608 Merge remote-tracking branch 'origin/abp' into abp 2025-03-23 17:16:37 +08:00
橙子
21ff599a4e fix: 修复更新事件 2025-03-23 17:15:17 +08:00
chenchun
224c2b96e4 feat: 新增模型 2025-03-21 18:25:22 +08:00
chenchun
cbb3510d94 feat: 重构聊天室语义内核 2025-03-21 18:24:59 +08:00
chenchun
0b111852ec fix: 修复点数问题 2025-03-21 15:51:44 +08:00
chenchun
ff8038a616 fix: 修复股市问题 2025-03-21 15:36:22 +08:00
橙子
710ad95eda feat: 支持默认启用redis 2025-03-18 23:13:16 +08:00
橙子
f7d9effa07 feat: 上线ai股市模块 2025-03-15 00:58:10 +08:00
橙子
cec28faaf7 perf: 优化提示词 2025-03-14 00:14:44 +08:00
橙子
bcdcca82eb fix: 修复排序问题 2025-03-14 00:10:31 +08:00
chenchun
4e0cc9a24a feat: 完善前端界面 2025-03-13 20:45:23 +08:00
chenchun
53a402a656 feat: 优化提示词 2025-03-13 15:30:24 +08:00
橙子
85ed4df1e4 fix: 修复记录时间 2025-03-11 22:00:11 +08:00
chenchun
8ef91ebd03 feat: 完成股票价格生成job 2025-03-11 13:43:26 +08:00
chenchun
ccaebb8ec2 feat: 完善提示词 2025-03-11 13:40:15 +08:00
橙子
4afc1cc492 feat: 新增job db选择 2025-03-10 22:27:54 +08:00
橙子
ddba0f9aa1 style: 修改默认redisdb 2025-03-10 22:08:38 +08:00
橙子
c782246a1d feat: 完成提示词工程 2025-03-09 23:51:30 +08:00
橙子
afa5fad8c6 Merge remote-tracking branch 'origin/stock' into stock 2025-03-09 16:21:52 +08:00
橙子
d605809932 feat: 完善对接接口 2025-03-09 16:21:47 +08:00
橙子
30250db0fb feat: 完善对接接口 2025-03-09 16:21:39 +08:00
橙子
b48f584db8 style: 修改定时任务种子数据 2025-03-08 23:36:03 +08:00
橙子
56d850d74b chorm: 构建 2025-03-08 23:22:01 +08:00
橙子
3ef1323f05 stly: 更换样式 2025-03-08 22:49:08 +08:00
橙子
d9ed547a20 stly: 更换样式 2025-03-08 22:47:30 +08:00
橙子
82865631fc feat: ai完成stock模块搭建 2025-03-08 22:14:26 +08:00
橙子
337088c908 feat: 补充部分功能 2025-03-07 00:35:32 +08:00
橙子
c092ee46e9 feat: 完成ai生成 2025-03-05 23:08:58 +08:00
橙子
287634cf99 feat: 新增ai-stock模块 2025-03-02 01:54:12 +08:00
橙子
c1535fd116 perf: 优化错误提示 2025-03-01 23:43:19 +08:00
橙子
d7d4fd8a48 perf: 优化排行榜页面 2025-03-01 14:58:21 +08:00
橙子
1552b00516 perf: 优化首页 2025-03-01 02:41:33 +08:00
橙子
a40d9b79b4 feat:新增stock页面 2025-03-01 01:53:55 +08:00
橙子
0bc5b1a940 feat:新增stock页面 2025-03-01 01:53:42 +08:00
131 changed files with 5543 additions and 673 deletions

View File

@@ -7,6 +7,12 @@
[![fork](https://gitee.com/ccnetcore/yi/badge/fork.svg?theme=dark)](https://gitee.com/ccnetcore/Yi)
[![license](https://img.shields.io/badge/license-MIT-yellow)](https://gitee.com/ccnetcore/Yi)
本项目 CDN 加速及安全防护由 Tencent EdgeOne 赞助
[亚洲最佳CDN、边缘和安全解决方案 - Tencent EdgeOne](https://edgeone.ai/zh?from=github)
<img src="readme/edgeone.png"/>
[English](README-en.md) | 简体中文
****
## 🍍 简介:
@@ -60,9 +66,9 @@ bbs前端`docker run -d --name yi.bbs -p 18001:18001 -v /home/Yi/Yi.Bbs.Vue3/
Yi社区官网网址Bbs社区正式[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
Rbac后台演示地址https://ccnetcore.com:1000 用户cc、密码123456
Rbac后台演示地址https://data.ccnetcore.com:1000 用户cc、密码123456
Pure后台演示地址https://ccnetcore.com:1001 用户cc、密码123456
Pure后台演示地址https://data.ccnetcore.com:1001 用户cc、密码123456
## 🍏 支持:

View File

@@ -174,17 +174,17 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.WeChat.MiniPro
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.BackgroundWorkers.Hangfire", "framework\Yi.Framework.BackgroundWorkers.Hangfire\Yi.Framework.BackgroundWorkers.Hangfire.csproj", "{862CA181-BEE6-4870-82D2-B662E527ED8C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "stock", "stock", "{DB46873F-981A-43D8-91B0-D464CCB65943}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ai-stock", "ai-stock", "{DB46873F-981A-43D8-91B0-D464CCB65943}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Application", "module\stock\Yi.Framework.Stock.Application\Yi.Framework.Stock.Application.csproj", "{B79CE23C-10F8-48A5-A039-5940A188CF5A}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Application", "module\ai-stock\Yi.Framework.Stock.Application\Yi.Framework.Stock.Application.csproj", "{B79CE23C-10F8-48A5-A039-5940A188CF5A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Application.Contracts", "module\stock\Yi.Framework.Stock.Application.Contracts\Yi.Framework.Stock.Application.Contracts.csproj", "{846B781A-B77E-4F86-A31F-0B5B57AB0775}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Application.Contracts", "module\ai-stock\Yi.Framework.Stock.Application.Contracts\Yi.Framework.Stock.Application.Contracts.csproj", "{846B781A-B77E-4F86-A31F-0B5B57AB0775}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Domain", "module\stock\Yi.Framework.Stock.Domain\Yi.Framework.Stock.Domain.csproj", "{162821E4-8FE0-4A68-B3C0-49BD6596446F}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Domain", "module\ai-stock\Yi.Framework.Stock.Domain\Yi.Framework.Stock.Domain.csproj", "{162821E4-8FE0-4A68-B3C0-49BD6596446F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Domain.Shared", "module\stock\Yi.Framework.Stock.Domain.Shared\Yi.Framework.Stock.Domain.Shared.csproj", "{10273544-715D-4BB3-893C-6F010D947BDD}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Domain.Shared", "module\ai-stock\Yi.Framework.Stock.Domain.Shared\Yi.Framework.Stock.Domain.Shared.csproj", "{10273544-715D-4BB3-893C-6F010D947BDD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.SqlSugarCore", "module\stock\Yi.Framework.Stock.SqlSugarCore\Yi.Framework.Stock.SqlSugarCore.csproj", "{5F49318F-E6C7-4194-BAE0-83D4FB8D1983}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.SqlSugarCore", "module\ai-stock\Yi.Framework.Stock.SqlSugarCore\Yi.Framework.Stock.SqlSugarCore.csproj", "{5F49318F-E6C7-4194-BAE0-83D4FB8D1983}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@@ -12,6 +12,7 @@
</PropertyGroup>
<PropertyGroup>
<SatelliteResourceLanguages>en;zh-CN</SatelliteResourceLanguages>
<LangVersion>latest</LangVersion>
<Version>1.0.0</Version>
<NoWarn>$(NoWarn);CS1591;CS8618;CS1998;CS8604;CS8620;CS8600;CS8602</NoWarn>

View File

@@ -1,4 +1,4 @@
// MIT 许可证
// MIT 许可证
//
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
//
@@ -17,25 +17,25 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Validation;
using Yi.Framework.Core.Extensions;
namespace Yi.Framework.AspNetCore.UnifyResult.Fiters;
/// <summary>
/// 友好异常拦截器
/// 友好异常拦截器
/// </summary>
public sealed class FriendlyExceptionFilter : IAsyncExceptionFilter
{
/// <summary>
/// 异常拦截
/// 异常拦截
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task OnExceptionAsync(ExceptionContext context)
{
// 排除 WebSocket 请求处理
if (context.HttpContext.IsWebSocketRequest()) return;
@@ -44,20 +44,23 @@ public sealed class FriendlyExceptionFilter : IAsyncExceptionFilter
// 解析异常信息
var exceptionMetadata = GetExceptionMetadata(context);
IUnifyResultProvider unifyResult = context.GetRequiredService<IUnifyResultProvider>();
var unifyResult = context.GetRequiredService<IUnifyResultProvider>();
// 执行规范化异常处理
context.Result = unifyResult.OnException(context, exceptionMetadata);
// 创建日志记录器
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<FriendlyExceptionFilter>>();
var errorMsg = "";
if (exceptionMetadata.Errors != null) errorMsg = "\n" + JsonConvert.SerializeObject(exceptionMetadata.Errors);
// 记录拦截日常
logger.LogError(context.Exception, context.Exception.Message);
logger.LogError(context.Exception, context.Exception.Message + errorMsg);
}
/// <summary>
/// 获取异常元数据
/// 获取异常元数据
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
@@ -74,22 +77,25 @@ public sealed class FriendlyExceptionFilter : IAsyncExceptionFilter
// 判断是否是 ExceptionContext 或者 ActionExecutedContext
var exception = context is ExceptionContext exContext
? exContext.Exception
: (
context is ActionExecutedContext edContext
? edContext.Exception
: default
);
: context is ActionExecutedContext edContext
? edContext.Exception
: default;
if (exception is AbpValidationException validationException)
{
errors = validationException.ValidationErrors;
isValidationException = true;
}
// 判断是否是友好异常
if (exception is UserFriendlyException friendlyException)
{
int statusCode2 = 500;
var statusCode2 = 500;
int.TryParse(friendlyException.Code, out statusCode2);
isFriendlyException = true;
errorCode = friendlyException.Code;
originErrorCode = friendlyException.Code;
statusCode = statusCode2==0?403:statusCode2;
isValidationException = false;
statusCode = statusCode2 == 0 ? 403 : statusCode2;
errors = friendlyException.Message;
data = friendlyException.Data;
}

View File

@@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
using Volo.Abp.AspNetCore.Mvc.Response;
using Yi.Framework.AspNetCore.UnifyResult.Fiters;
namespace Yi.Framework.AspNetCore.UnifyResult;
@@ -20,9 +21,10 @@ public static class UnifyResultExtensions
services.AddTransient<FriendlyExceptionFilter>();
services.AddMvc(options =>
{
options.Filters.RemoveAll(x => (x as ServiceFilterAttribute)?.ServiceType == typeof(AbpExceptionFilter));
options.Filters.RemoveAll(x => (x as ServiceFilterAttribute)?.ServiceType == typeof(AbpNoContentActionFilter));
options.Filters.AddService<SucceededUnifyResultFilter>(99);
options.Filters.AddService<FriendlyExceptionFilter>(100);
options.Filters.RemoveAll(x => (x as ServiceFilterAttribute)?.ServiceType == typeof(AbpExceptionFilter));
});
return services;
}

View File

@@ -0,0 +1,8 @@
namespace Yi.Framework.Core.Options;
public class SemanticKernelOptions
{
public List<string> ModelIds { get; set; }
public string Endpoint { get; set; }
public string ApiKey { get; set; }
}

View File

@@ -11,12 +11,12 @@ namespace Yi.Framework.Ddd.Application
/// <summary>
/// CRUD应用服务基类 - 基础版本
/// </summary>
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey>
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey>
: YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
@@ -30,7 +30,7 @@ namespace Yi.Framework.Ddd.Application
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
@@ -44,7 +44,7 @@ namespace Yi.Framework.Ddd.Application
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
@@ -58,7 +58,7 @@ namespace Yi.Framework.Ddd.Application
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
@@ -78,7 +78,7 @@ namespace Yi.Framework.Ddd.Application
/// </summary>
private const string TempFilePath = "/wwwroot/temp";
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
@@ -96,7 +96,7 @@ namespace Yi.Framework.Ddd.Application
// 获取并验证实体
var entity = await GetEntityByIdAsync(id);
// 检查更新输入
await CheckUpdateInputDtoAsync(entity, input);
@@ -124,10 +124,10 @@ namespace Yi.Framework.Ddd.Application
{
// 检查创建权限
await CheckCreatePolicyAsync();
// 检查创建输入
await CheckCreateInputDtoAsync(input);
// 映射到实体
var entity = await MapToEntityAsync(input);
@@ -156,13 +156,13 @@ namespace Yi.Framework.Ddd.Application
public override async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
{
List<TEntity> entities;
// 根据输入类型决定查询方式
if (input is IPagedResultRequest pagedInput)
{
// 分页查询
entities = await Repository.GetPagedListAsync(
pagedInput.SkipCount,
pagedInput.SkipCount,
pagedInput.MaxResultCount,
string.Empty
);
@@ -176,7 +176,23 @@ namespace Yi.Framework.Ddd.Application
// 获取总数并映射结果
var totalCount = await Repository.GetCountAsync();
var dtos = await MapToGetListOutputDtosAsync(entities);
return new PagedResultDto<TGetListOutputDto>(totalCount, dtos);
}
/// <summary>
/// 获取实体动态下拉框列表,子类重写该方法,通过 keywords 进行筛选
/// </summary>
/// <param name="keywords">查询关键字</param>
/// <returns></returns>
public virtual async Task<PagedResultDto<TGetListOutputDto>> GetSelectDataListAsync(string? keywords = null)
{
List<TEntity> entities = await Repository.GetListAsync();
// 获取总数并映射结果
var totalCount = entities.Count;
var dtos = await MapToGetListOutputDtosAsync(entities);
return new PagedResultDto<TGetListOutputDto>(totalCount, dtos);
}

View File

@@ -1,7 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectMapping;
using Yi.Framework.Core;
using Mapster;
namespace Yi.Framework.Mapster
{
@@ -22,7 +23,8 @@ namespace Yi.Framework.Mapster
public override void ConfigureServices(ServiceConfigurationContext context)
{
var services = context.Services;
// 扫描并注册所有映射配置
TypeAdapterConfig.GlobalSettings.Scan(AppDomain.CurrentDomain.GetAssemblies());
// 注册Mapster相关服务
services.AddTransient<IAutoObjectMappingProvider, MapsterAutoObjectMappingProvider>();
services.AddTransient<IObjectMapper, MapsterObjectMapper>();

View File

@@ -58,5 +58,10 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
/// 是否启用SaaS多租户
/// </summary>
public bool EnabledSaasMultiTenancy { get; set; } = false;
/// <summary>
/// 是否开启更新并发乐观锁
/// </summary>
public bool EnabledConcurrencyException { get;set; } = false;
}
}

View File

@@ -213,6 +213,10 @@ public class DefaultSqlSugarDbContext : SqlSugarDbContext
{
EntityChangeEventHelperService.PublishEntityDeletedEvent(entityInfo.EntityValue);
}
else
{
EntityChangeEventHelperService.PublishEntityUpdatedEvent(entityInfo.EntityValue);
}
}
else
{

View File

@@ -1,12 +1,9 @@
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Microsoft.Extensions.Logging;
using System.Linq.Expressions;
using Microsoft.Extensions.Options;
using Nito.AsyncEx;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Linq;
@@ -17,12 +14,17 @@ namespace Yi.Framework.SqlSugarCore.Repositories
{
public class SqlSugarRepository<TEntity> : ISqlSugarRepository<TEntity>, IRepository<TEntity> where TEntity : class, IEntity, new()
{
[Obsolete("使用GetDbContextAsync()")]
public ISqlSugarClient _Db => AsyncContext.Run(async () => await GetDbContextAsync());
[Obsolete("使用AsQueryable()")]
public ISugarQueryable<TEntity> _DbQueryable => _Db.Queryable<TEntity>();
private readonly ISugarDbContextProvider<ISqlSugarDbContext> _dbContextProvider;
public IAbpLazyServiceProvider LazyServiceProvider { get; set; }
protected DbConnOptions? Options => LazyServiceProvider?.LazyGetService<IOptions<DbConnOptions>>().Value;
/// <summary>
/// 异步查询执行器
/// </summary>
@@ -264,6 +266,7 @@ namespace Yi.Framework.SqlSugarCore.Repositories
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
var entity = await GetByIdAsync(id);
if (entity == null) return false;
//反射赋值
ReflexHelper.SetModelValue(nameof(ISoftDelete.IsDeleted), true, entity);
return await UpdateAsync(entity);
@@ -319,12 +322,12 @@ namespace Yi.Framework.SqlSugarCore.Repositories
public virtual async Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize)
{
return await (await GetDbSimpleClientAsync()).GetPageListAsync(whereExpression, new PageModel() { PageIndex = pageNum, PageSize = pageSize });
return await (await AsQueryable()).Where(whereExpression).ToPageListAsync(pageNum, pageSize);
}
public virtual async Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize, Expression<Func<TEntity, object>>? orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
{
return await (await GetDbSimpleClientAsync()).GetPageListAsync(whereExpression, new PageModel { PageIndex = pageNum, PageSize = pageSize }, orderByExpression, orderByType);
return await (await AsQueryable()).Where(whereExpression) .OrderBy( orderByExpression,orderByType).ToPageListAsync(pageNum, pageSize);
}
public virtual async Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> whereExpression)
@@ -379,20 +382,24 @@ namespace Yi.Framework.SqlSugarCore.Repositories
public virtual async Task<bool> UpdateAsync(TEntity updateObj)
{
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>())//带版本号乐观锁更新
if (Options is not null && Options.EnabledConcurrencyException)
{
try
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>()) //带版本号乐观锁更新
{
int num = await (await GetDbSimpleClientAsync())
.Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true);
return num>0;
}
catch (VersionExceptions ex)
{
throw new AbpDbConcurrencyException($"{ex.Message}[更新失败ConcurrencyStamp不是最新版本],entityInfo{updateObj}", ex);
try
{
int num = await (await GetDbSimpleClientAsync())
.Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true);
return num > 0;
}
catch (VersionExceptions ex)
{
throw new AbpDbConcurrencyException(
$"{ex.Message}[更新失败ConcurrencyStamp不是最新版本],entityInfo{updateObj}", ex);
}
}
}
return await (await GetDbSimpleClientAsync()).UpdateAsync(updateObj);
}

View File

@@ -26,7 +26,7 @@ public static class SqlSugarCoreExtensions
ServiceLifetime serviceLifetime = ServiceLifetime.Transient)
where TDbContext : class, ISqlSugarDbContextDependencies
{
services.TryAdd(new ServiceDescriptor(
services.Add(new ServiceDescriptor(
typeof(ISqlSugarDbContextDependencies),
typeof(TDbContext),
serviceLifetime));

View File

@@ -0,0 +1,66 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockHolding
{
/// <summary>
/// 用户股票持仓DTO
/// </summary>
public class StockHoldingDto : EntityDto<Guid>
{
/// <summary>
/// 用户ID
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// 股票ID
/// </summary>
public Guid StockId { get; set; }
/// <summary>
/// 股票代码
/// </summary>
public string StockCode { get; set; }
/// <summary>
/// 股票名称
/// </summary>
public string StockName { get; set; }
/// <summary>
/// 持有数量
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// 持仓成本
/// </summary>
public decimal CostPrice { get; set; }
/// <summary>
/// 当前价格
/// </summary>
public decimal CurrentPrice { get; set; }
/// <summary>
/// 持仓市值
/// </summary>
public decimal MarketValue { get; set; }
/// <summary>
/// 盈亏金额
/// </summary>
public decimal ProfitLoss { get; set; }
/// <summary>
/// 盈亏百分比
/// </summary>
public decimal ProfitLossPercentage { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreationTime { get; set; }
}
}

View File

@@ -0,0 +1,21 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockHolding
{
/// <summary>
/// 获取用户持仓列表的输入DTO
/// </summary>
public class StockHoldingGetListInputDto : PagedAndSortedResultRequestDto
{
/// <summary>
/// 股票代码
/// </summary>
public string? StockCode { get; set; }
/// <summary>
/// 股票名称
/// </summary>
public string? StockName { get; set; }
}
}

View File

@@ -0,0 +1,20 @@
using System;
namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockMarket
{
/// <summary>
/// 买入股票输入DTO
/// </summary>
public class BuyStockInputDto
{
/// <summary>
/// 股票ID
/// </summary>
public Guid StockId { get; set; }
/// <summary>
/// 买入数量
/// </summary>
public int Quantity { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
using System;
namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockMarket;
/// <summary>
/// 创建股市输入DTO
/// </summary>
public class CreateStockMarketInputDto
{
/// <summary>
/// 股市代码
/// </summary>
public string MarketCode { get; set; }
/// <summary>
/// 股市名称
/// </summary>
public string MarketName { get; set; }
/// <summary>
/// 股市描述
/// </summary>
public string Description { get; set; }
}

View File

@@ -0,0 +1,20 @@
using System;
namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockMarket
{
/// <summary>
/// 卖出股票输入DTO
/// </summary>
public class SellStockInputDto
{
/// <summary>
/// 股票ID
/// </summary>
public Guid StockId { get; set; }
/// <summary>
/// 卖出数量
/// </summary>
public int Quantity { get; set; }
}
}

View File

@@ -0,0 +1,36 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockMarket
{
/// <summary>
/// 股市信息DTO
/// </summary>
public class StockMarketDto : EntityDto<Guid>
{
/// <summary>
/// 股市代码
/// </summary>
public string MarketCode { get; set; }
/// <summary>
/// 股市名称
/// </summary>
public string MarketName { get; set; }
/// <summary>
/// 股市描述
/// </summary>
public string Description { get; set; }
/// <summary>
/// 状态
/// </summary>
public bool State { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreationTime { get; set; }
}
}

View File

@@ -0,0 +1,26 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockMarket
{
/// <summary>
/// 获取股市列表的输入DTO
/// </summary>
public class StockMarketGetListInputDto : PagedAndSortedResultRequestDto
{
/// <summary>
/// 股市代码
/// </summary>
public string? MarketCode { get; set; }
/// <summary>
/// 股市名称
/// </summary>
public string? MarketName { get; set; }
/// <summary>
/// 状态
/// </summary>
public bool? State { get; set; }
}
}

View File

@@ -0,0 +1,41 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockNews
{
/// <summary>
/// 股市新闻DTO
/// </summary>
public class StockNewsDto : EntityDto<Guid>
{
/// <summary>
/// 新闻标题
/// </summary>
public string Title { get; set; }
/// <summary>
/// 新闻内容
/// </summary>
public string Content { get; set; }
/// <summary>
/// 发布时间
/// </summary>
public DateTime PublishTime { get; set; }
/// <summary>
/// 新闻来源
/// </summary>
public string Source { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreationTime { get; set; }
/// <summary>
/// 排序号
/// </summary>
public int OrderNum { get; set; }
}
}

View File

@@ -0,0 +1,37 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockNews
{
/// <summary>
/// 获取股市新闻列表的输入DTO
/// </summary>
public class StockNewsGetListInputDto : PagedAndSortedResultRequestDto
{
/// <summary>
/// 新闻标题关键词
/// </summary>
public string? Title { get; set; }
/// <summary>
/// 新闻来源
/// </summary>
public string? Source { get; set; }
/// <summary>
/// 开始时间
/// </summary>
public DateTime? StartTime { get; set; }
/// <summary>
/// 结束时间
/// </summary>
public DateTime? EndTime { get; set; }
/// <summary>
/// 是否只显示最近10天的新闻
/// </summary>
/// <remarks>默认为true查询最近10天的新闻</remarks>
public bool IsRecent { get; set; } = true;
}
}

View File

@@ -0,0 +1,47 @@
using System;
using Volo.Abp.Application.Dtos;
using Yi.Framework.Stock.Domain.Shared;
namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockPrice
{
/// <summary>
/// 股市价格记录DTO
/// </summary>
public class StockPriceRecordDto : EntityDto<Guid>
{
/// <summary>
/// 股票ID
/// </summary>
public Guid StockId { get; set; }
/// <summary>
/// 记录时间
/// </summary>
public DateTime CreationTime { get; set; }
/// <summary>
/// 当前价
/// </summary>
public decimal CurrentPrice { get; set; }
/// <summary>
/// 交易量
/// </summary>
public long Volume { get; set; }
/// <summary>
/// 交易额
/// </summary>
public decimal Turnover { get; set; }
/// <summary>
/// 时间周期类型
/// </summary>
public PeriodTypeEnum PeriodType { get; set; }
/// <summary>
/// 记录时间
/// </summary>
public DateTime RecordTime { get; set; }
}
}

View File

@@ -0,0 +1,32 @@
using System;
using Volo.Abp.Application.Dtos;
using Yi.Framework.Stock.Domain.Shared;
namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockPrice
{
/// <summary>
/// 获取股市价格记录的输入DTO
/// </summary>
public class StockPriceRecordGetListInputDto : PagedAndSortedResultRequestDto
{
/// <summary>
/// 股票ID
/// </summary>
public Guid? StockId { get; set; }
/// <summary>
/// 开始时间
/// </summary>
public DateTime? StartTime { get; set; }
/// <summary>
/// 结束时间
/// </summary>
public DateTime? EndTime { get; set; }
/// <summary>
/// 时间周期类型
/// </summary>
public PeriodTypeEnum? PeriodType { get; set; }
}
}

View File

@@ -0,0 +1,62 @@
using System;
using Volo.Abp.Application.Dtos;
using Yi.Framework.Stock.Domain.Shared;
namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockTransaction
{
/// <summary>
/// 股票交易记录DTO
/// </summary>
public class StockTransactionDto : EntityDto<Guid>
{
/// <summary>
/// 用户ID
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// 股票ID
/// </summary>
public Guid StockId { get; set; }
/// <summary>
/// 股票代码
/// </summary>
public string StockCode { get; set; }
/// <summary>
/// 股票名称
/// </summary>
public string StockName { get; set; }
/// <summary>
/// 交易类型
/// </summary>
public TransactionTypeEnum TransactionType { get; set; }
/// <summary>
/// 交易价格
/// </summary>
public decimal Price { get; set; }
/// <summary>
/// 交易数量
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// 交易总额
/// </summary>
public decimal TotalAmount { get; set; }
/// <summary>
/// 交易费用
/// </summary>
public decimal Fee { get; set; }
/// <summary>
/// 交易时间
/// </summary>
public DateTime CreationTime { get; set; }
}
}

View File

@@ -0,0 +1,37 @@
using System;
using Volo.Abp.Application.Dtos;
using Yi.Framework.Stock.Domain.Shared;
namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockTransaction
{
/// <summary>
/// 获取交易记录的输入DTO
/// </summary>
public class StockTransactionGetListInputDto : PagedAndSortedResultRequestDto
{
/// <summary>
/// 股票代码
/// </summary>
public string? StockCode { get; set; }
/// <summary>
/// 股票名称
/// </summary>
public string? StockName { get; set; }
/// <summary>
/// 交易类型
/// </summary>
public TransactionTypeEnum? TransactionType { get; set; }
/// <summary>
/// 开始时间
/// </summary>
public DateTime? StartTime { get; set; }
/// <summary>
/// 结束时间
/// </summary>
public DateTime? EndTime { get; set; }
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Yi.Framework.Stock.Application.Contracts.Dtos.StockHolding;
using Yi.Framework.Stock.Application.Contracts.Dtos.StockTransaction;
namespace Yi.Framework.Stock.Application.Contracts.IServices
{
/// <summary>
/// 用户持仓服务接口
/// </summary>
public interface IStockHoldingService : IApplicationService
{
/// <summary>
/// 获取当前用户的持仓列表
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>持仓列表</returns>
Task<PagedResultDto<StockHoldingDto>> GetUserHoldingsAsync(StockHoldingGetListInputDto input);
/// <summary>
/// 获取当前用户的交易记录
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>交易记录列表</returns>
Task<PagedResultDto<StockTransactionDto>> GetUserTransactionsAsync(StockTransactionGetListInputDto input);
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Yi.Framework.Stock.Application.Contracts.Dtos.StockMarket;
using Yi.Framework.Stock.Application.Contracts.Dtos.StockPrice;
namespace Yi.Framework.Stock.Application.Contracts.IServices
{
/// <summary>
/// 股市服务接口
/// </summary>
public interface IStockMarketService : IApplicationService
{
/// <summary>
/// 获取股市列表
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>股市列表</returns>
Task<PagedResultDto<StockMarketDto>> GetStockMarketListAsync(StockMarketGetListInputDto input);
/// <summary>
/// 获取股市价格记录看板
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>股价记录列表</returns>
Task<PagedResultDto<StockPriceRecordDto>> GetStockPriceRecordListAsync(StockPriceRecordGetListInputDto input);
/// <summary>
/// 买入股票
/// </summary>
/// <param name="input">买入股票参数</param>
/// <returns>操作结果</returns>
Task BuyStockAsync(BuyStockInputDto input);
/// <summary>
/// 卖出股票
/// </summary>
/// <param name="input">卖出股票参数</param>
/// <returns>操作结果</returns>
Task SellStockAsync(SellStockInputDto input);
/// <summary>
/// 生成最新股票记录
/// </summary>
/// <returns>操作结果</returns>
Task GenerateStocksAsync();
}
}

View File

@@ -0,0 +1,20 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Yi.Framework.Stock.Application.Contracts.Dtos.StockNews;
namespace Yi.Framework.Stock.Application.Contracts.IServices
{
/// <summary>
/// 股市新闻服务接口
/// </summary>
public interface IStockNewsService : IApplicationService
{
/// <summary>
/// 获取股市新闻列表
/// </summary>
/// <param name="input">查询条件</param>
/// <returns>新闻列表</returns>
Task<PagedResultDto<StockNewsDto>> GetStockNewsListAsync(StockNewsGetListInputDto input);
}
}

View File

@@ -9,7 +9,6 @@
<PackageReference Include="Volo.Abp.SettingManagement.Application.Contracts" Version="$(AbpVersion)" />
</ItemGroup>
<ItemGroup>
<Folder Include="Dtos\" />
<Folder Include="IServices\" />
</ItemGroup>

View File

@@ -0,0 +1,107 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Yi.Framework.Stock.Application.Contracts.Dtos.StockHolding;
using Yi.Framework.Stock.Application.Contracts.Dtos.StockTransaction;
using Yi.Framework.Stock.Application.Contracts.IServices;
using Yi.Framework.Stock.Domain.Entities;
using Yi.Framework.SqlSugarCore.Abstractions;
using Volo.Abp.Users;
namespace Yi.Framework.Stock.Application.Services
{
/// <summary>
/// 用户持仓服务实现
/// </summary>
[Authorize]
public class StockHoldingService : ApplicationService, IStockHoldingService
{
private readonly ISqlSugarRepository<StockHoldingAggregateRoot> _stockHoldingRepository;
private readonly ISqlSugarRepository<StockTransactionEntity> _stockTransactionRepository;
public StockHoldingService(
ISqlSugarRepository<StockHoldingAggregateRoot> stockHoldingRepository,
ISqlSugarRepository<StockTransactionEntity> stockTransactionRepository)
{
_stockHoldingRepository = stockHoldingRepository;
_stockTransactionRepository = stockTransactionRepository;
}
/// <summary>
/// 获取当前用户的持仓列表
/// </summary>
[Authorize]
[HttpGet("stock/user-holdings")]
public async Task<PagedResultDto<StockHoldingDto>> GetUserHoldingsAsync(StockHoldingGetListInputDto input)
{
Guid userId = CurrentUser.GetId();
RefAsync<int> total = 0;
var query = _stockHoldingRepository._DbQueryable
.Where(h => h.UserId == userId)
.WhereIF(!string.IsNullOrEmpty(input.StockCode), h => h.StockCode.Contains(input.StockCode))
.WhereIF(!string.IsNullOrEmpty(input.StockName), h => h.StockName.Contains(input.StockName))
.OrderByIF(!string.IsNullOrEmpty(input.Sorting),input.Sorting)
.OrderByIF(string.IsNullOrEmpty(input.Sorting),t=>t.CreationTime,OrderByType.Desc);
var list = await query
.Select(h => new StockHoldingDto
{
Id = h.Id,
UserId = h.UserId,
StockId = h.StockId,
StockCode = h.StockCode,
StockName = h.StockName,
Quantity = h.Quantity,
CreationTime = h.CreationTime
})
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<StockHoldingDto>(total, list);
}
/// <summary>
/// 获取当前用户的交易记录
/// </summary>
[Authorize]
[HttpGet("stock/user-transactions")]
public async Task<PagedResultDto<StockTransactionDto>> GetUserTransactionsAsync(StockTransactionGetListInputDto input)
{
Guid userId = CurrentUser.GetId();
RefAsync<int> total = 0;
var query = _stockTransactionRepository._DbQueryable
.Where(t => t.UserId == userId)
.WhereIF(!string.IsNullOrEmpty(input.StockCode), t => t.StockCode.Contains(input.StockCode))
.WhereIF(!string.IsNullOrEmpty(input.StockName), t => t.StockName.Contains(input.StockName))
.WhereIF(input.TransactionType.HasValue, t => t.TransactionType == input.TransactionType.Value)
.WhereIF(input.StartTime.HasValue, t => t.CreationTime >= input.StartTime.Value)
.WhereIF(input.EndTime.HasValue, t => t.CreationTime <= input.EndTime.Value)
.OrderByIF(!string.IsNullOrEmpty(input.Sorting),input.Sorting)
.OrderByIF(string.IsNullOrEmpty(input.Sorting),t=>t.CreationTime,OrderByType.Desc);
var list = await query
.Select(t => new StockTransactionDto
{
Id = t.Id,
UserId = t.UserId,
StockId = t.StockId,
StockCode = t.StockCode,
StockName = t.StockName,
TransactionType = t.TransactionType,
Price = t.Price,
Quantity = t.Quantity,
TotalAmount = t.TotalAmount,
Fee = t.Fee,
CreationTime = t.CreationTime
})
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<StockTransactionDto>(total, list);
}
}
}

View File

@@ -0,0 +1,167 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
using Yi.Framework.Stock.Application.Contracts.Dtos.StockMarket;
using Yi.Framework.Stock.Application.Contracts.Dtos.StockPrice;
using Yi.Framework.Stock.Application.Contracts.IServices;
using Yi.Framework.Stock.Domain.Entities;
using Yi.Framework.SqlSugarCore.Abstractions;
using Yi.Framework.Stock.Domain.Managers;
using Mapster;
namespace Yi.Framework.Stock.Application.Services
{
/// <summary>
/// 股市服务实现
/// </summary>
public class StockMarketService : ApplicationService, IStockMarketService
{
private readonly ISqlSugarRepository<StockMarketAggregateRoot> _stockMarketRepository;
private readonly ISqlSugarRepository<StockPriceRecordEntity> _stockPriceRecordRepository;
private readonly StockMarketManager _stockMarketManager;
public StockMarketService(
ISqlSugarRepository<StockMarketAggregateRoot> stockMarketRepository,
ISqlSugarRepository<StockPriceRecordEntity> stockPriceRecordRepository,
StockMarketManager stockMarketManager)
{
_stockMarketRepository = stockMarketRepository;
_stockPriceRecordRepository = stockPriceRecordRepository;
_stockMarketManager = stockMarketManager;
}
/// <summary>
/// 创建股市
/// </summary>
[HttpPost("stock/markets")]
[Authorize]
public async Task<StockMarketDto> CreateStockMarketAsync(CreateStockMarketInputDto input)
{
// 使用映射将输入DTO转换为实体
var stockMarket = input.Adapt<StockMarketAggregateRoot>();
// 保存到数据库
var result = await _stockMarketRepository.InsertReturnEntityAsync(stockMarket);
// 使用映射将实体转换为返回DTO
return result.Adapt<StockMarketDto>();
}
/// <summary>
/// 获取股市列表
/// </summary>
[HttpGet("stock/markets")]
public async Task<PagedResultDto<StockMarketDto>> GetStockMarketListAsync(StockMarketGetListInputDto input)
{
RefAsync<int> total = 0;
var query = _stockMarketRepository._DbQueryable
.WhereIF(!string.IsNullOrEmpty(input.MarketCode), m => m.MarketCode.Contains(input.MarketCode))
.WhereIF(!string.IsNullOrEmpty(input.MarketName), m => m.MarketName.Contains(input.MarketName))
.WhereIF(input.State.HasValue, m => m.State == input.State.Value)
.OrderByIF(!string.IsNullOrEmpty(input.Sorting),input.Sorting)
.OrderByIF(string.IsNullOrEmpty(input.Sorting),m=>m.OrderNum,OrderByType.Asc)
.OrderByIF(string.IsNullOrEmpty(input.Sorting),m=>m.CreationTime,OrderByType.Desc);
var list = await query
.Select(m => new StockMarketDto
{
Id = m.Id,
MarketCode = m.MarketCode,
MarketName = m.MarketName,
Description = m.Description,
State = m.State,
CreationTime = m.CreationTime
})
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<StockMarketDto>(total, list);
}
/// <summary>
/// 获取股市价格记录看板
/// </summary>
[HttpGet("stock/price-records")]
public async Task<PagedResultDto<StockPriceRecordDto>> GetStockPriceRecordListAsync(StockPriceRecordGetListInputDto input)
{
RefAsync<int> total = 0;
var query = _stockPriceRecordRepository._DbQueryable
.WhereIF(input.StockId.HasValue, p => p.StockId == input.StockId.Value)
.WhereIF(input.StartTime.HasValue, p => p.RecordTime >= input.StartTime.Value)
.WhereIF(input.EndTime.HasValue, p => p.RecordTime <= input.EndTime.Value)
.WhereIF(input.PeriodType.HasValue, p => p.PeriodType == input.PeriodType.Value)
.Where(x=>x.RecordTime<=DateTime.Now)
.OrderByIF(!string.IsNullOrEmpty(input.Sorting),input.Sorting)
.OrderByIF(string.IsNullOrEmpty(input.Sorting),p=>p.RecordTime);
var list = await query
.Select(p => new StockPriceRecordDto
{
Id = p.Id,
StockId = p.StockId,
CreationTime = p.CreationTime,
RecordTime = p.RecordTime,
CurrentPrice = p.CurrentPrice,
Volume = p.Volume,
Turnover = p.Turnover,
PeriodType = p.PeriodType
})
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<StockPriceRecordDto>(total, list);
}
/// <summary>
/// 买入股票
/// </summary>
[HttpPost("stock/buy")]
[Authorize]
public async Task BuyStockAsync(BuyStockInputDto input)
{
// 获取当前登录用户ID
var userId = CurrentUser.GetId();
// 调用领域服务进行股票购买
await _stockMarketManager.BuyStockAsync(
userId,
input.StockId,
input.Quantity
);
}
/// <summary>
/// 卖出股票
/// </summary>
[HttpDelete("stock/sell")]
[Authorize]
public async Task SellStockAsync(SellStockInputDto input)
{
// 获取当前登录用户ID
var userId = CurrentUser.GetId();
// 调用领域服务进行股票卖出
await _stockMarketManager.SellStockAsync(
userId,
input.StockId,
input.Quantity
);
}
/// <summary>
/// 生成最新股票记录
/// </summary>
[HttpPost("stock/generate")]
[Authorize]
public async Task GenerateStocksAsync()
{
await _stockMarketManager.GenerateStocksAsync();
}
}
}

View File

@@ -0,0 +1,80 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Yi.Framework.Stock.Application.Contracts.Dtos.StockNews;
using Yi.Framework.Stock.Application.Contracts.IServices;
using Yi.Framework.Stock.Domain.Entities;
using Yi.Framework.SqlSugarCore.Abstractions;
using Yi.Framework.Stock.Domain.Managers;
namespace Yi.Framework.Stock.Application.Services
{
/// <summary>
/// 股市新闻服务实现
/// </summary>
public class StockNewsService : ApplicationService, IStockNewsService
{
private readonly ISqlSugarRepository<StockNewsAggregateRoot> _stockNewsRepository;
private readonly NewsManager _newsManager;
public StockNewsService(
ISqlSugarRepository<StockNewsAggregateRoot> stockNewsRepository,
NewsManager newsManager)
{
_stockNewsRepository = stockNewsRepository;
_newsManager = newsManager;
}
/// <summary>
/// 获取股市新闻列表
/// </summary>
[HttpGet("/api/app/stock/news")]
public async Task<PagedResultDto<StockNewsDto>> GetStockNewsListAsync(StockNewsGetListInputDto input)
{
RefAsync<int> total = 0;
// 计算10天前的日期
DateTime tenDaysAgo = DateTime.Now.AddDays(-10);
var query = _stockNewsRepository._DbQueryable
.WhereIF(!string.IsNullOrEmpty(input.Title), n => n.Title.Contains(input.Title))
.WhereIF(!string.IsNullOrEmpty(input.Source), n => n.Source.Contains(input.Source))
.WhereIF(input.StartTime.HasValue, n => n.PublishTime >= input.StartTime.Value)
.WhereIF(input.EndTime.HasValue, n => n.PublishTime <= input.EndTime.Value)
// 如果IsRecent为true则只查询最近10天的新闻
.WhereIF(input.IsRecent, n => n.PublishTime >= tenDaysAgo)
.OrderByIF(!string.IsNullOrEmpty(input.Sorting),input.Sorting)
.OrderByIF(string.IsNullOrEmpty(input.Sorting),n=>n.OrderNum,OrderByType.Asc)
.OrderByIF(string.IsNullOrEmpty(input.Sorting),n=>n.PublishTime,OrderByType.Desc) ;
var list = await query
.Select(n => new StockNewsDto
{
Id = n.Id,
Title = n.Title,
Content = n.Content,
PublishTime = n.PublishTime,
Source = n.Source,
CreationTime = n.CreationTime,
OrderNum = n.OrderNum
})
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<StockNewsDto>(total, list);
}
/// <summary>
/// 生成股市新闻
/// </summary>
/// <returns>生成结果</returns>
[HttpPost("/api/app/stock/news/generate")]
public async Task GenerateNewsAsync()
{
await _newsManager.GenerateNewsAsync();
}
}
}

View File

@@ -0,0 +1,41 @@
namespace Yi.Framework.Stock.Domain.Shared
{
/// <summary>
/// 时间周期类型枚举
/// </summary>
/// <remarks>
/// 用于定义股票价格记录的时间周期类型
/// </remarks>
public enum PeriodTypeEnum
{
/// <summary>
/// 分钟
/// </summary>
Minute = 0,
/// <summary>
/// 小时
/// </summary>
Hour = 1,
/// <summary>
/// 天
/// </summary>
Day = 2,
/// <summary>
/// 周
/// </summary>
Week = 3,
/// <summary>
/// 月
/// </summary>
Month = 4,
/// <summary>
/// 年
/// </summary>
Year = 5
}
}

View File

@@ -0,0 +1,18 @@
namespace Yi.Framework.Stock.Domain.Shared
{
/// <summary>
/// 交易类型枚举
/// </summary>
public enum TransactionTypeEnum
{
/// <summary>
/// 买入
/// </summary>
Buy = 0,
/// <summary>
/// 卖出
/// </summary>
Sell = 1
}
}

View File

@@ -0,0 +1,56 @@
using System;
using Yi.Framework.Stock.Domain.Shared;
namespace Yi.Framework.Stock.Domain.Shared.Etos
{
/// <summary>
/// 股票交易事件数据传输对象
/// </summary>
public class StockTransactionEto
{
/// <summary>
/// 用户ID
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// 股票ID
/// </summary>
public Guid StockId { get; set; }
/// <summary>
/// 股票代码
/// </summary>
public string StockCode { get; set; }
/// <summary>
/// 股票名称
/// </summary>
public string StockName { get; set; }
/// <summary>
/// 交易类型
/// </summary>
public TransactionTypeEnum TransactionType { get; set; }
/// <summary>
/// 交易价格
/// </summary>
public decimal Price { get; set; }
/// <summary>
/// 交易数量
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// 交易总额
/// </summary>
public decimal TotalAmount { get; set; }
/// <summary>
/// 交易费用
/// </summary>
public decimal Fee { get; set; }
}
}

View File

@@ -14,5 +14,8 @@
<Folder Include="Enums\" />
<Folder Include="Etos\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\bbs\Yi.Framework.Bbs.Domain.Shared\Yi.Framework.Bbs.Domain.Shared.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,141 @@
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Yi.Framework.Core.Data;
namespace Yi.Framework.Stock.Domain.Entities
{
/// <summary>
/// 用户股票持仓聚合根
/// </summary>
/// <remarks>
/// 记录用户持有的股票数量和相关信息
/// </remarks>
[SugarTable("Stock_Holding")]
public class StockHoldingAggregateRoot : AggregateRoot<Guid>, ISoftDelete, IAuditedObject
{
/// <summary>
/// 逻辑删除
/// </summary>
public bool IsDeleted { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreationTime { get; set; } = DateTime.Now;
/// <summary>
/// 创建者
/// </summary>
public Guid? CreatorId { get; set; }
/// <summary>
/// 最后修改者
/// </summary>
public Guid? LastModifierId { get; set; }
/// <summary>
/// 最后修改时间
/// </summary>
public DateTime? LastModificationTime { get; set; }
/// <summary>
/// 用户ID
/// </summary>
/// <remarks>关联到持有股票的用户</remarks>
public Guid UserId { get; set; }
/// <summary>
/// 股票ID
/// </summary>
/// <remarks>关联到具体的股票</remarks>
public Guid StockId { get; set; }
/// <summary>
/// 股票代码
/// </summary>
/// <remarks>冗余字段,方便查询</remarks>
public string StockCode { get; set; } = string.Empty;
/// <summary>
/// 股票名称
/// </summary>
/// <remarks>冗余字段,方便查询</remarks>
public string StockName { get; set; } = string.Empty;
/// <summary>
/// 持有数量
/// </summary>
/// <remarks>用户持有的股票数量</remarks>
public int Quantity { get; set; }
/// <summary>
/// 平均成本价
/// </summary>
/// <remarks>用户购买这些股票的平均成本价</remarks>
public decimal AverageCostPrice { get; set; }
/// <summary>
/// 持仓成本
/// </summary>
/// <remarks>总投入成本 = 平均成本价 * 持有数量</remarks>
[SugarColumn(IsIgnore = true)]
public decimal TotalCost => AverageCostPrice * Quantity;
public StockHoldingAggregateRoot() { }
public StockHoldingAggregateRoot(
Guid userId,
Guid stockId,
string stockCode,
string stockName,
int quantity,
decimal averageCostPrice)
{
UserId = userId;
StockId = stockId;
StockCode = stockCode;
StockName = stockName;
Quantity = quantity;
AverageCostPrice = averageCostPrice;
}
/// <summary>
/// 增加持仓数量
/// </summary>
/// <param name="quantity">增加的数量</param>
/// <param name="price">本次购买价格</param>
public void AddQuantity(int quantity, decimal price)
{
if (quantity <= 0)
throw new ArgumentException("增加的数量必须大于0");
// 计算新的平均成本价
decimal totalCost = AverageCostPrice * Quantity + price * quantity;
Quantity += quantity;
AverageCostPrice = totalCost / Quantity;
}
/// <summary>
/// 减少持仓数量
/// </summary>
/// <param name="quantity">减少的数量</param>
public void ReduceQuantity(int quantity)
{
if (quantity <= 0)
throw new ArgumentException("减少的数量必须大于0");
if (quantity > Quantity)
throw new ArgumentException("减少的数量不能大于持有数量");
Quantity -= quantity;
// 如果数量为0标记为删除
if (Quantity == 0)
{
IsDeleted = true;
}
}
}
}

View File

@@ -0,0 +1,79 @@
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Yi.Framework.Core.Data;
namespace Yi.Framework.Stock.Domain.Entities
{
/// <summary>
/// 股市聚合根实体
/// </summary>
/// <remarks>
/// 用于定义有哪些公司上架的股市
/// </remarks>
[SugarTable("Stock_Market")]
public class StockMarketAggregateRoot : AggregateRoot<Guid>, ISoftDelete, IAuditedObject, IOrderNum, IState
{
/// <summary>
/// 逻辑删除
/// </summary>
public bool IsDeleted { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreationTime { get; set; }
/// <summary>
/// 创建者
/// </summary>
public Guid? CreatorId { get; set; }
/// <summary>
/// 最后修改者
/// </summary>
public Guid? LastModifierId { get; set; }
/// <summary>
/// 最后修改时间
/// </summary>
public DateTime? LastModificationTime { get; set; }
/// <summary>
/// 排序
/// </summary>
public int OrderNum { get; set; } = 0;
/// <summary>
/// 状态
/// </summary>
public bool State { get; set; } = true;
/// <summary>
/// 股市代码
/// </summary>
public string MarketCode { get; set; } = string.Empty;
/// <summary>
/// 股市名称
/// </summary>
public string MarketName { get; set; } = string.Empty;
/// <summary>
/// 股市描述
/// </summary>
public string Description { get; set; } = string.Empty;
public StockMarketAggregateRoot() { }
public StockMarketAggregateRoot(
string marketCode,
string marketName,
string description = "")
{
MarketCode = marketCode;
MarketName = marketName;
Description = description;
}
}
}

View File

@@ -0,0 +1,87 @@
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Yi.Framework.Core.Data;
namespace Yi.Framework.Stock.Domain.Entities
{
/// <summary>
/// 股市新闻聚合根实体
/// </summary>
/// <remarks>
/// 用于记录影响股市波动的新闻事件
/// </remarks>
[SugarTable("Stock_News")]
public class StockNewsAggregateRoot : AggregateRoot<Guid>, ISoftDelete, IAuditedObject, IOrderNum
{
/// <summary>
/// 逻辑删除
/// </summary>
public bool IsDeleted { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreationTime { get; set; }
/// <summary>
/// 创建者ID
/// </summary>
public Guid? CreatorId { get; set; }
/// <summary>
/// 最后修改时间
/// </summary>
public DateTime? LastModificationTime { get; set; }
/// <summary>
/// 最后修改者ID
/// </summary>
public Guid? LastModifierId { get; set; }
/// <summary>
/// 排序号
/// </summary>
public int OrderNum { get; set; } = 0;
/// <summary>
/// 新闻标题
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// 新闻内容
/// </summary>
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string Content { get; set; } = string.Empty;
/// <summary>
/// 发布时间
/// </summary>
public DateTime PublishTime { get; set; }
/// <summary>
/// 新闻来源
/// </summary>
public string Source { get; set; } = string.Empty;
/// <summary>
/// 新闻摘要
/// </summary>
public string Summary { get; set; } = string.Empty;
public StockNewsAggregateRoot() { }
public StockNewsAggregateRoot(
string title,
string content,
string source = "")
{
Title = title;
Content = content;
Source = source;
PublishTime = DateTime.Now;
}
}
}

View File

@@ -0,0 +1,78 @@
using SqlSugar;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Auditing;
using Yi.Framework.Stock.Domain.Shared;
namespace Yi.Framework.Stock.Domain.Entities
{
/// <summary>
/// 股票价格记录实体
/// </summary>
/// <remarks>
/// 用于记录每支股票在不同时间点的价格数据,支持趋势分析和图表展示
/// </remarks>
[SugarTable("Stock_PriceRecord")]
public class StockPriceRecordEntity : Entity<Guid>, IHasCreationTime
{
/// <summary>
/// 股票ID
/// </summary>
/// <remarks>关联到具体的股票</remarks>
public Guid StockId { get; set; }
/// <summary>
/// 创建时间(审计日志)
/// </summary>
public DateTime CreationTime { get; set; }
/// <summary>
/// 记录时间
/// </summary>
/// <remarks>价格记录的实际时间点</remarks>
public DateTime RecordTime { get; set; }
/// <summary>
/// 当前价
/// </summary>
public decimal CurrentPrice { get; set; }
/// <summary>
/// 交易量
/// </summary>
/// <remarks>该时间段内的交易股数</remarks>
public long Volume { get; set; }
/// <summary>
/// 交易额
/// </summary>
/// <remarks>该时间段内的交易金额</remarks>
public decimal Turnover { get; set; }
/// <summary>
/// 时间周期类型
/// </summary>
/// <remarks>
/// 记录的时间周期类型:分钟、小时、日、周、月等
/// </remarks>
public PeriodTypeEnum PeriodType { get; set; }
public StockPriceRecordEntity() { }
public StockPriceRecordEntity(
Guid stockId,
decimal currentPrice,
long volume = 0,
decimal turnover = 0,
PeriodTypeEnum periodType = PeriodTypeEnum.Day)
{
StockId = stockId;
CreationTime = DateTime.Now;
RecordTime = DateTime.Now;
CurrentPrice = currentPrice;
Volume = volume;
Turnover = turnover;
PeriodType = periodType;
}
}
}

View File

@@ -0,0 +1,121 @@
using SqlSugar;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Auditing;
using Yi.Framework.Stock.Domain.Shared;
namespace Yi.Framework.Stock.Domain.Entities
{
/// <summary>
/// 股票交易记录实体
/// </summary>
/// <remarks>
/// 用于记录用户买入或卖出股票的交易历史
/// </remarks>
[SugarTable("Stock_Transaction")]
public class StockTransactionEntity : Entity<Guid>, IAuditedObject
{
/// <summary>
/// 用户ID
/// </summary>
/// <remarks>进行交易的用户</remarks>
public Guid UserId { get; set; }
/// <summary>
/// 股票ID
/// </summary>
/// <remarks>交易的股票</remarks>
public Guid StockId { get; set; }
/// <summary>
/// 股票代码
/// </summary>
/// <remarks>冗余字段,方便查询</remarks>
public string StockCode { get; set; } = string.Empty;
/// <summary>
/// 股票名称
/// </summary>
/// <remarks>冗余字段,方便查询</remarks>
public string StockName { get; set; } = string.Empty;
/// <summary>
/// 交易类型
/// </summary>
public TransactionTypeEnum TransactionType { get; set; }
/// <summary>
/// 交易价格
/// </summary>
/// <remarks>股票的单价</remarks>
public decimal Price { get; set; }
/// <summary>
/// 交易数量
/// </summary>
/// <remarks>买入或卖出的股票数量</remarks>
public int Quantity { get; set; }
/// <summary>
/// 交易总额
/// </summary>
/// <remarks>价格 × 数量</remarks>
public decimal TotalAmount { get; set; }
/// <summary>
/// 交易费用
/// </summary>
/// <remarks>手续费、佣金等</remarks>
public decimal Fee { get; set; }
/// <summary>
/// 创建时间
/// </summary>
/// <remarks>交易发生时间</remarks>
public DateTime CreationTime { get; set; }
/// <summary>
/// 创建者ID
/// </summary>
public Guid? CreatorId { get; set; }
/// <summary>
/// 最后修改时间
/// </summary>
public DateTime? LastModificationTime { get; set; }
/// <summary>
/// 最后修改者ID
/// </summary>
public Guid? LastModifierId { get; set; }
/// <summary>
/// 备注
/// </summary>
public string Remark { get; set; } = string.Empty;
public StockTransactionEntity() { }
public StockTransactionEntity(
Guid userId,
Guid stockId,
string stockCode,
string stockName,
TransactionTypeEnum transactionType,
decimal price,
int quantity,
decimal fee = 0)
{
Id = Guid.NewGuid();
UserId = userId;
StockId = stockId;
StockCode = stockCode;
StockName = stockName;
TransactionType = transactionType;
Price = price;
Quantity = quantity;
TotalAmount = price * quantity;
Fee = fee;
CreationTime = DateTime.Now;
}
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
using Yi.Framework.Stock.Domain.Entities;
using Yi.Framework.Stock.Domain.Shared.Etos;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Stock.Domain.EventHandlers
{
/// <summary>
/// 股票交易事件处理器
/// </summary>
public class StockTransactionEventHandler : ILocalEventHandler<StockTransactionEto>, ITransientDependency
{
private readonly ISqlSugarRepository<StockTransactionEntity> _transactionRepository;
public StockTransactionEventHandler(
ISqlSugarRepository<StockTransactionEntity> transactionRepository)
{
_transactionRepository = transactionRepository;
}
public async Task HandleEventAsync(StockTransactionEto eventData)
{
// 创建交易记录实体
var transaction = new StockTransactionEntity(
eventData.UserId,
eventData.StockId,
eventData.StockCode,
eventData.StockName,
eventData.TransactionType,
eventData.Price,
eventData.Quantity,
eventData.Fee
);
// 保存交易记录
await _transactionRepository.InsertAsync(transaction);
}
}
}

View File

@@ -0,0 +1,91 @@
using Volo.Abp.Domain.Services;
using Yi.Framework.SqlSugarCore.Abstractions;
using Yi.Framework.Stock.Domain.Entities;
using Yi.Framework.Stock.Domain.Managers.SemanticKernel;
using Yi.Framework.Stock.Domain.Managers.SemanticKernel.Plugins;
using System.Text;
using System.IO;
namespace Yi.Framework.Stock.Domain.Managers;
public class NewsManager:DomainService
{
private SemanticKernelClient _skClient;
private ISqlSugarRepository<StockNewsAggregateRoot> _newsRepository;
public NewsManager(SemanticKernelClient skClient,ISqlSugarRepository<StockNewsAggregateRoot> newsRepository)
{
_skClient = skClient;
_newsRepository = newsRepository;
}
/// <summary>
/// 获取最近的新闻
/// </summary>
/// <param name="count">获取数量</param>
/// <returns>最近的新闻列表</returns>
public async Task<List<StockNewsAggregateRoot>> GetRecentNewsAsync(int count = 10)
{
return await _newsRepository._DbQueryable
.OrderByDescending(n => n.CreationTime)
.Take(count)
.Select(n => new StockNewsAggregateRoot
{
Title = n.Title,
Summary = n.Summary,
Source = n.Source,
CreationTime = n.CreationTime
})
.ToListAsync();
}
/// <summary>
/// 生成一个新闻
/// </summary>
/// <returns></returns>
public async Task GenerateNewsAsync()
{
// 获取最近10条新闻
var recentNews = await GetRecentNewsAsync(10);
// 构建新闻背景上下文
var newsContext = new StringBuilder();
if (recentNews.Any())
{
newsContext.AppendLine("以下是最近的新闻简介:");
foreach (var news in recentNews)
{
newsContext.AppendLine($"- {news.CreationTime:yyyy-MM-dd} 来源:{news.Source}");
newsContext.AppendLine($" 标题:{news.Title}");
newsContext.AppendLine($" 简介:{news.Summary}");
newsContext.AppendLine();
}
}
else
{
newsContext.AppendLine("目前没有最近的新闻记录。");
}
var promptPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", "stock", "newsPrompt.txt");
var question = await File.ReadAllTextAsync(promptPath);
question = question.Replace("{{newsContext}}", newsContext.ToString());
await _skClient.ChatCompletionAsync(question, ("NewsPlugins","save_news"));
}
public async Task SaveNewsAsync(NewsModel news)
{
var newsEntity = new StockNewsAggregateRoot(
title: news.Title,
content: news.Content,
source: news.Source
)
{
Summary = news.Summary,
CreationTime = DateTime.Now,
IsDeleted = false,
OrderNum = 0
};
await _newsRepository.InsertAsync(newsEntity);
}
}

View File

@@ -0,0 +1,42 @@
using System.ComponentModel;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
namespace Yi.Framework.Stock.Domain.Managers.SemanticKernel.Plugins;
public class NewsPlugins
{
private readonly IServiceProvider _serviceProvider;
public NewsPlugins(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
[KernelFunction("save_news"), Description("生成并保存一个新闻")]
public async Task SaveAsync(NewsModel news)
{
var newsManager = _serviceProvider.GetRequiredService<NewsManager>();
await newsManager.SaveNewsAsync(news);
}
}
public class NewsModel
{
[JsonPropertyName("title")]
[DisplayName("新闻标题")]
public string Title { get; set; }
[JsonPropertyName("content")]
[DisplayName("新闻内容")]
public string Content { get; set; }
[JsonPropertyName("summary")]
[DisplayName("新闻简介")]
public string Summary { get; set; }
[JsonPropertyName("source")]
[DisplayName("新闻来源")]
public string Source { get; set; }
}

View File

@@ -0,0 +1,34 @@
using System.ComponentModel;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
namespace Yi.Framework.Stock.Domain.Managers.SemanticKernel.Plugins;
public class StockPlugins
{
private readonly IServiceProvider _serviceProvider;
public StockPlugins(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
[KernelFunction("save_stocks"), Description("生成并且保存多个股票记录")]
public async Task SaveAsync(List<StockModel> stockModels)
{
var stockMarketManager= _serviceProvider.GetRequiredService<StockMarketManager>();
await stockMarketManager.SaveStockAsync(stockModels);
}
}
public class StockModel
{
[JsonPropertyName("id")]
[DisplayName("股票id")]
public Guid Id { get; set; }
[JsonPropertyName("values")]
[DisplayName("股票未来24小时价格")]
public decimal[] Values { get; set; }
}

View File

@@ -0,0 +1,55 @@
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Volo.Abp.DependencyInjection;
namespace Yi.Framework.Stock.Domain.Managers.SemanticKernel;
public class SemanticKernelClient:ITransientDependency
{
public Kernel Kernel { get;}
public SemanticKernelClient(Kernel kernel)
{
this.Kernel = kernel;
}
/// <summary>
/// 执行插件
/// </summary>
/// <param name="input"></param>
/// <param name="pluginName"></param>
/// <param name="functionName"></param>
/// <returns></returns>
public async Task<string> InovkerFunctionAsync(string input, string pluginName, string functionName)
{
KernelFunction jsonFun = this.Kernel.Plugins.GetFunction(pluginName, functionName);
var result = await this.Kernel.InvokeAsync(function: jsonFun, new KernelArguments() { ["input"] = input });
return result.GetValue<string>();
}
/// <summary>
/// 聊天对话,调用方法
/// </summary>
/// <returns></returns>
public async Task<IReadOnlyList<ChatMessageContent>> ChatCompletionAsync(string question,params (string,string)[] functions)
{
if (functions is null)
{
throw new Exception("请选择插件");
}
var openSettings = new OpenAIPromptExecutionSettings()
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(functions.Select(x=>this.Kernel.Plugins.GetFunction(x.Item1, x.Item2)).ToList(),true),
// ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
MaxTokens =1000
};
var chatCompletionService = this.Kernel.GetRequiredService<IChatCompletionService>();
var results =await chatCompletionService.GetChatMessageContentsAsync(
question,
executionSettings: openSettings,
kernel: Kernel);
return results;
}
}

View File

@@ -0,0 +1,9 @@
namespace Yi.Framework.Stock.Domain.Managers.SemanticKernel
{
public class SemanticKernelOptions
{
public List<string> ModelIds { get; set; }
public string Endpoint { get; set; }
public string ApiKey { get; set; }
}
}

View File

@@ -0,0 +1,446 @@
using System;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Domain.Services;
using Volo.Abp.EventBus.Local;
using Yi.Framework.Bbs.Domain.Shared.Etos;
using Yi.Framework.Stock.Domain.Entities;
using Yi.Framework.Stock.Domain.Shared;
using Yi.Framework.Stock.Domain.Shared.Etos;
using Yi.Framework.SqlSugarCore.Abstractions;
using Yi.Framework.Stock.Domain.Managers.SemanticKernel;
using Yi.Framework.Stock.Domain.Managers.SemanticKernel.Plugins;
using Microsoft.Extensions.Hosting;
using System.Text;
using System.IO;
namespace Yi.Framework.Stock.Domain.Managers
{
/// <summary>
/// 股市领域服务
/// </summary>
/// <remarks>
/// 处理股票交易相关业务,例如买入、卖出等
/// </remarks>
public class StockMarketManager : DomainService
{
private readonly ISqlSugarRepository<StockHoldingAggregateRoot> _stockHoldingRepository;
private readonly ISqlSugarRepository<StockTransactionEntity> _stockTransactionRepository;
private readonly ISqlSugarRepository<StockPriceRecordEntity> _stockPriceRecordRepository;
private readonly ISqlSugarRepository<StockMarketAggregateRoot> _stockMarketRepository;
private readonly ILocalEventBus _localEventBus;
private readonly SemanticKernelClient _skClient;
private readonly IHostEnvironment _hostEnvironment;
private readonly NewsManager _newsManager;
public StockMarketManager(
ISqlSugarRepository<StockHoldingAggregateRoot> stockHoldingRepository,
ISqlSugarRepository<StockTransactionEntity> stockTransactionRepository,
ISqlSugarRepository<StockPriceRecordEntity> stockPriceRecordRepository,
ISqlSugarRepository<StockMarketAggregateRoot> stockMarketRepository,
ILocalEventBus localEventBus,
SemanticKernelClient skClient,
IHostEnvironment hostEnvironment,
NewsManager newsManager)
{
_stockHoldingRepository = stockHoldingRepository;
_stockTransactionRepository = stockTransactionRepository;
_stockPriceRecordRepository = stockPriceRecordRepository;
_stockMarketRepository = stockMarketRepository;
_localEventBus = localEventBus;
_skClient = skClient;
_hostEnvironment = hostEnvironment;
_newsManager = newsManager;
}
/// <summary>
/// 购买股票
/// </summary>
/// <param name="userId">用户ID</param>
/// <param name="stockId">股票ID</param>
/// <param name="quantity">购买数量</param>
/// <returns></returns>
public async Task BuyStockAsync(Guid userId, Guid stockId, int quantity)
{
if (quantity <= 0)
{
throw new UserFriendlyException("购买数量必须大于0");
}
// 通过stockId查询获取股票信息
var stockInfo = await _stockMarketRepository.GetFirstAsync(s => s.Id == stockId);
if (stockInfo == null)
{
throw new UserFriendlyException("找不到指定的股票");
}
string stockCode = stockInfo.MarketCode; // 根据实际字段调整
string stockName = stockInfo.MarketName; // 根据实际字段调整
// 获取当前股票价格
decimal currentPrice = await GetCurrentStockPriceAsync(stockId);
// 计算总金额和手续费
decimal totalAmount = currentPrice * quantity;
decimal fee = CalculateTradingFee(totalAmount, TransactionTypeEnum.Buy);
decimal totalCost = totalAmount + fee;
// 扣减用户资金
await _localEventBus.PublishAsync(
new MoneyChangeEventArgs { UserId = userId, Number = -totalCost }, false);
// 更新或创建用户持仓
var holding = await _stockHoldingRepository.GetFirstAsync(h =>
h.UserId == userId &&
h.StockId == stockId &&
!h.IsDeleted);
if (holding == null)
{
// 创建新持仓
holding = new StockHoldingAggregateRoot(
userId,
stockId,
stockCode,
stockName,
quantity,
currentPrice);
await _stockHoldingRepository.InsertAsync(holding);
}
else
{
// 更新现有持仓
holding.AddQuantity(quantity, currentPrice);
await _stockHoldingRepository.UpdateAsync(holding);
}
// 发布交易事件
await _localEventBus.PublishAsync(new StockTransactionEto
{
UserId = userId,
StockId = stockId,
StockCode = stockCode,
StockName = stockName,
TransactionType = TransactionTypeEnum.Buy,
Price = currentPrice,
Quantity = quantity,
TotalAmount = totalAmount,
Fee = fee
}, false);
}
/// <summary>
/// 卖出股票
/// </summary>
/// <param name="userId">用户ID</param>
/// <param name="stockId">股票ID</param>
/// <param name="quantity">卖出数量</param>
/// <returns></returns>
public async Task SellStockAsync(Guid userId, Guid stockId, int quantity)
{
// 验证卖出时间
VerifySellTime();
if (quantity <= 0)
{
throw new UserFriendlyException("卖出数量必须大于0");
}
// 获取用户持仓
var holding = await _stockHoldingRepository.GetFirstAsync(h =>
h.UserId == userId &&
h.StockId == stockId &&
!h.IsDeleted);
if (holding == null)
{
throw new UserFriendlyException("您没有持有该股票");
}
if (holding.Quantity < quantity)
{
throw new UserFriendlyException("持仓数量不足");
}
// 获取当前股票价格
decimal currentPrice = await GetCurrentStockPriceAsync(stockId);
// 计算总金额和手续费
decimal totalAmount = currentPrice * quantity;
decimal fee = CalculateTradingFee(totalAmount, TransactionTypeEnum.Sell);
decimal actualIncome = totalAmount - fee;
// 增加用户资金
await _localEventBus.PublishAsync(
new MoneyChangeEventArgs { UserId = userId, Number = actualIncome }, false);
// 更新用户持仓
holding.ReduceQuantity(quantity);
if (holding.Quantity > 0)
{
await _stockHoldingRepository.UpdateAsync(holding);
}
else
{
await _stockHoldingRepository.DeleteAsync(holding);
}
// 发布交易事件
await _localEventBus.PublishAsync(new StockTransactionEto
{
UserId = userId,
StockId = stockId,
StockCode = holding.StockCode,
StockName = holding.StockName,
TransactionType = TransactionTypeEnum.Sell,
Price = currentPrice,
Quantity = quantity,
TotalAmount = totalAmount,
Fee = fee
}, false);
}
/// <summary>
/// 获取股票当前价格
/// </summary>
/// <param name="stockId">股票ID</param>
/// <returns>当前价格</returns>
public async Task<decimal> GetCurrentStockPriceAsync(Guid stockId)
{
// 获取最新的价格记录
var latestPriceRecord = await _stockPriceRecordRepository._DbQueryable
.Where(p => p.StockId == stockId)
.Where(x=>x.RecordTime<=DateTime.Now)
.OrderByDescending(p => p.RecordTime)
.FirstAsync();
if (latestPriceRecord == null)
{
throw new UserFriendlyException("无法获取股票价格信息");
}
return latestPriceRecord.CurrentPrice;
}
/// <summary>
/// 计算交易手续费
/// </summary>
/// <param name="amount">交易金额</param>
/// <param name="transactionType">交易类型</param>
/// <returns>手续费</returns>
private decimal CalculateTradingFee(decimal amount, TransactionTypeEnum transactionType)
{
// 买入不收手续费卖出收2%
decimal feeRate = transactionType == TransactionTypeEnum.Buy ? 0m : 0.02m;
return amount * feeRate;
}
/// <summary>
/// 验证卖出时间
/// </summary>
/// <exception cref="UserFriendlyException">如果不在允许卖出的时间范围内</exception>
private void VerifySellTime()
{
// 如果是开发环境,跳过验证
if (_hostEnvironment.IsDevelopment())
{
return;
}
DateTime now = DateTime.Now;
// 检查是否为工作日(周一到周五)
if (now.DayOfWeek == DayOfWeek.Saturday || now.DayOfWeek == DayOfWeek.Sunday)
{
throw new UserFriendlyException("股票只能在工作日(周一至周五)卖出");
}
// 检查是否在下午5点到6点之间
if (now.Hour < 17 || now.Hour >= 18)
{
throw new UserFriendlyException("股票只能在下午5点到6点之间卖出");
}
}
/// <summary>
/// 批量保存多个股票的最新价格记录
/// </summary>
/// <param name="priceRecords">价格记录列表</param>
/// <returns>保存的记录数量</returns>
public async Task BatchSaveStockPriceRecordsAsync(List<StockPriceRecordEntity> priceRecords)
{
if (priceRecords == null || !priceRecords.Any())
{
return;
}
// 验证数据
for (int i = 0; i < priceRecords.Count; i++)
{
var record = priceRecords[i];
if (record.CurrentPrice <= 0)
{
throw new UserFriendlyException($"股票ID {record.StockId} 的价格必须大于0");
}
// 计算交易额(如果未设置)
if (record.Turnover == 0 && record.Volume > 0)
{
record.Turnover = record.CurrentPrice * record.Volume;
}
}
await _stockPriceRecordRepository.InsertManyAsync(priceRecords);
}
public async Task SaveStockAsync(List<StockModel> stockModels)
{
if (stockModels == null || !stockModels.Any())
{
return;
}
// 收集所有股票ID
var stockIds = stockModels.Select(m => m.Id).ToList();
// 一次性查询所有相关股票信息
var stockMarkets = await _stockMarketRepository.GetListAsync(s => stockIds.Contains(s.Id));
// 构建字典以便快速查找
var stockMarketsDict = stockMarkets.ToDictionary(s => s.Id);
// 将StockModel转换为StockPriceRecordEntity
var priceRecords = new List<StockPriceRecordEntity>();
// 获取当前小时的起始时间点
var currentHour = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, 0, 0);
foreach (var stockModel in stockModels)
{
if (stockModel.Values == null || !stockModel.Values.Any())
{
continue;
}
// 从字典中查找股票信息,而不是每次查询数据库
if (!stockMarketsDict.TryGetValue(stockModel.Id, out var stockMarket))
{
continue;
}
// 为每个价格点创建一个记录,并设置递增的时间
for (int i = 0; i < stockModel.Values.Count(); i++)
{
var priceValue = stockModel.Values[i];
var recordTime = currentHour.AddHours(i); // 从当前小时开始每个价格点加1小时
var priceRecord = new StockPriceRecordEntity
{
StockId = stockMarket.Id,
CurrentPrice = priceValue,
Volume = 0, // 可以根据实际情况设置
Turnover = 0, // 可以根据实际情况设置
PeriodType = PeriodTypeEnum.Hour,
RecordTime = recordTime // 直接在这里设置时间
};
priceRecords.Add(priceRecord);
}
}
// 批量保存价格记录
if (priceRecords.Any())
{
await _stockPriceRecordRepository.InsertManyAsync(priceRecords);
}
}
/// <summary>
/// 获取所有活跃股票的最新价格
/// </summary>
/// <returns>股票ID和最新价格的字典</returns>
private async Task<Dictionary<Guid, decimal>> GetLatestStockPricesAsync()
{
// 获取所有活跃的股票
var activeStocks = await _stockMarketRepository.GetListAsync(s => s.State && !s.IsDeleted);
var result = new Dictionary<Guid, decimal>();
foreach (var stock in activeStocks)
{
try
{
var price = await GetCurrentStockPriceAsync(stock.Id);
result.Add(stock.Id, price);
}
catch
{
// 如果获取价格失败使用默认价格10
result.Add(stock.Id, 10m);
}
}
return result;
}
/// <summary>
/// 生成最新股票记录
/// </summary>
/// <returns></returns>
public async Task GenerateStocksAsync()
{
// 获取所有活跃股票的最新价格
var stockPrices = await GetLatestStockPricesAsync();
if (!stockPrices.Any())
{
return; // 没有股票数据,直接返回
}
// 获取所有活跃股票信息,用于构建提示词
var activeStocks = await _stockMarketRepository.GetListAsync(s =>
s.State && !s.IsDeleted && stockPrices.Keys.Contains(s.Id));
// 获取最近10条新闻
var recentNews = await _newsManager.GetRecentNewsAsync(10);
// 构建新闻上下文
var newsContext = new StringBuilder();
if (recentNews.Any())
{
newsContext.AppendLine("以下是最近的新闻摘要:");
foreach (var news in recentNews)
{
newsContext.AppendLine($"- {news.CreationTime:yyyy-MM-dd}: {news.Title}");
newsContext.AppendLine($" {news.Summary}");
newsContext.AppendLine();
}
}
else
{
newsContext.AppendLine("最近没有重要新闻报道。");
}
// 构建股票信息上下文
var stocksContext = new StringBuilder();
stocksContext.AppendLine("以下是需要预测的股票信息:");
foreach (var stock in activeStocks)
{
if (stockPrices.TryGetValue(stock.Id, out var price))
{
stocksContext.AppendLine($"{stock.MarketName}id:{stock.Id},简介:{stock.Description} 最后一次价格:{price:F2}");
}
}
// 从文件读取问题模板
string promptTemplate = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", "stock", "marketPrompt.txt"));
// 替换变量
string question = promptTemplate
.Replace("{{newsContext}}", newsContext.ToString())
.Replace("{{stocksContext}}", stocksContext.ToString());
await _skClient.ChatCompletionAsync(question, ("StockPlugins", "save_stocks"));
}
}
}

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<ItemGroup>
<PackageReference Include="Microsoft.SemanticKernel" Version="1.40.0" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.Caching" Version="$(AbpVersion)" />
@@ -14,8 +15,6 @@
<ItemGroup>
<Folder Include="EventHandlers\" />
<Folder Include="Entities\" />
<Folder Include="Managers\" />
<Folder Include="Repositories\" />
</ItemGroup>

View File

@@ -0,0 +1,51 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Volo.Abp.Caching;
using Volo.Abp.Domain;
using Yi.Framework.Mapster;
using Yi.Framework.Stock.Domain.Managers;
using Yi.Framework.Stock.Domain.Managers.SemanticKernel;
using Yi.Framework.Stock.Domain.Managers.SemanticKernel.Plugins;
using Yi.Framework.Stock.Domain.Shared;
namespace Yi.Framework.Stock.Domain
{
[DependsOn(
typeof(YiFrameworkStockDomainSharedModule),
typeof(YiFrameworkMapsterModule),
typeof(AbpDddDomainModule),
typeof(AbpCachingModule)
)]
public class YiFrameworkStockDomainModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var services = context.Services;
// 配置绑定
var semanticKernelSection = configuration.GetSection("SemanticKernel");
services.Configure<SemanticKernelOptions>(configuration.GetSection("SemanticKernel"));
services.AddHttpClient();
#pragma warning disable SKEXP0010
// 从配置中获取值
var options = semanticKernelSection.Get<SemanticKernelOptions>();
//股市优先使用第一个ai模型
services.AddKernel()
.AddOpenAIChatCompletion(
modelId: options.ModelIds.FirstOrDefault(),
endpoint: new Uri(options.Endpoint),
apiKey: options.ApiKey);
#pragma warning restore SKEXP0010
// 添加插件
services.AddSingleton<KernelPlugin>(sp => KernelPluginFactory.CreateFromType<NewsPlugins>(serviceProvider: sp));
services.AddSingleton<KernelPlugin>(sp => KernelPluginFactory.CreateFromType<StockPlugins>(serviceProvider: sp));
// 注册NewsManager
services.AddTransient<NewsManager>();
}
}
}

View File

@@ -5,8 +5,10 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
/// <summary>
/// Discuss输入创建对象
/// </summary>
public class DiscussCreateInputVo
public class DiscussCreateInput
{
public DiscussTypeEnum DiscussType { get; set; }
public string Title { get; set; }
public string? Types { get; set; }
public string? Introduction { get; set; }
@@ -41,5 +43,29 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
/// 角色
/// </summary>
public List<string>? PermissionRoleCodes { get; set; } = new List<string>();
/// <summary>
/// 悬赏类型主题
/// </summary>
public DiscussRewardCreateInput? RewardData { get; set; }
}
public class DiscussRewardCreateInput
{
/// <summary>
/// 悬赏最小价值
/// </summary>
public decimal MinValue { get; set; }
/// <summary>
/// 悬赏最大价值
/// </summary>
public decimal? MaxValue { get; set; }
/// <summary>
/// 作者联系方式
/// </summary>
public string Contact { get; set; }
}
}

View File

@@ -32,6 +32,8 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
//是否置顶默认false
public bool IsTop { get; set; }
public DiscussTypeEnum DiscussType { get; set; }
public DiscussPermissionTypeEnum PermissionType { get; set; }
//是否禁止默认false
public bool IsBan { get; set; }

View File

@@ -24,7 +24,10 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
public Guid PlateId { get; set; }
//是否置顶默认false
public bool IsTop { get; set; }
/// <summary>
/// 主题类型
/// </summary>
public DiscussTypeEnum DiscussType { get; set; }
/// <summary>
/// 封面
/// </summary>
@@ -50,6 +53,7 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
public bool HasPermission { get;internal set; }
public DiscussRewardGetOutputDto? RewardData { get; set; }
/// <summary>
/// 设置权限
/// </summary>

View File

@@ -0,0 +1,23 @@
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss;
public class DiscussRewardGetOutputDto
{
/// <summary>
/// 是否已解决
/// </summary>
public bool IsResolved{ get; set; }
/// <summary>
/// 悬赏最小价值
/// </summary>
public decimal MinValue { get; set; }
/// <summary>
/// 悬赏最大价值
/// </summary>
public decimal? MaxValue { get; set; }
/// <summary>
/// 作者联系方式
/// </summary>
public string Contact { get; set; }
}

View File

@@ -2,7 +2,7 @@ using Yi.Framework.Bbs.Domain.Shared.Enums;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
{
public class DiscussUpdateInputVo
public class DiscussUpdateInput
{
public string Title { get; set; }
public string? Types { get; set; }

View File

@@ -6,7 +6,7 @@ namespace Yi.Framework.Bbs.Application.Contracts.IServices
/// <summary>
/// Discuss服务抽象
/// </summary>
public interface IDiscussService : IYiCrudAppService<DiscussGetOutputDto, DiscussGetListOutputDto, Guid, DiscussGetListInputVo, DiscussCreateInputVo, DiscussUpdateInputVo>
public interface IDiscussService : IYiCrudAppService<DiscussGetOutputDto, DiscussGetListOutputDto, Guid, DiscussGetListInputVo, DiscussCreateInput, DiscussUpdateInput>
{
}
}

View File

@@ -8,7 +8,7 @@ namespace Yi.Framework.Bbs.Application.Services
{
public class BbsUserInfoService : ApplicationService, IBbsUserInfoService
{
private BbsUserManager _bbsUserManager;
private readonly BbsUserManager _bbsUserManager;
public BbsUserInfoService(BbsUserManager bbsUserManager)
{
_bbsUserManager = bbsUserManager;

View File

@@ -2,8 +2,10 @@
using Volo.Abp;
using Volo.Abp.Application.Services;
using Volo.Abp.Uow;
using Volo.Abp.Users;
using Yi.Framework.Bbs.Application.Contracts.Dtos.Argee;
using Yi.Framework.Bbs.Domain.Entities.Forum;
using Yi.Framework.Bbs.Domain.Managers;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Bbs.Application.Services.Forum
@@ -13,12 +15,13 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
/// </summary>
public class AgreeService : ApplicationService, IApplicationService
{
public AgreeService(ISqlSugarRepository<AgreeEntity> repository, ISqlSugarRepository<DiscussAggregateRoot> discssRepository)
public AgreeService(ISqlSugarRepository<AgreeEntity> repository, ISqlSugarRepository<DiscussAggregateRoot> discssRepository, BbsUserManager bbsUserManager)
{
_repository = repository;
_discssRepository = discssRepository;
_bbsUserManager = bbsUserManager;
}
private readonly BbsUserManager _bbsUserManager;
private ISqlSugarRepository<AgreeEntity> _repository { get; set; }
private ISqlSugarRepository<DiscussAggregateRoot> _discssRepository { get; set; }
@@ -26,17 +29,17 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
/// <summary>
/// 点赞,返回true为点赞+1返回false为点赞-1
/// Todo: 可放入领域层
/// Todo: 可放入领域层,但是没必要,这个项目太简单了
/// </summary>
/// <returns></returns>
[Authorize]
public async Task<AgreeDto> PostOperateAsync(Guid discussId)
{
await _bbsUserManager.VerifyUserLimitAsync(CurrentUser.GetId());
var entity = await _repository.GetFirstAsync(x => x.DiscussId == discussId && x.CreatorId == CurrentUser.Id);
//判断是否已经点赞过
if (entity is null)
{
//没点赞过,添加记录即可,,修改总点赞数量
await _repository.InsertAsync(new AgreeEntity(discussId));
var discussEntity = await _discssRepository.GetByIdAsync(discussId);

View File

@@ -1,28 +1,20 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Repositories;
using Yi.Framework.Bbs.Application.Contracts.Dtos.Article;
using Yi.Framework.Bbs.Application.Contracts.Dtos.Plate;
using Yi.Framework.Bbs.Application.Contracts.IServices;
using Yi.Framework.Bbs.Domain.Entities.Forum;
using Yi.Framework.Bbs.Domain.Managers;
using Yi.Framework.Bbs.Domain.Repositories;
using Yi.Framework.Bbs.Domain.Shared.Consts;
using Yi.Framework.Bbs.Domain.Shared.Model;
using Yi.Framework.Core.Extensions;
using Yi.Framework.Ddd.Application;
using Yi.Framework.Rbac.Domain.Authorization;
using Yi.Framework.Rbac.Domain.Extensions;
using Yi.Framework.Rbac.Domain.Shared.Consts;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Bbs.Application.Services.Forum
@@ -36,19 +28,16 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
{
public ArticleService(IArticleRepository articleRepository,
ISqlSugarRepository<DiscussAggregateRoot> discussRepository,
IDiscussService discussService,
ForumManager forumManager) : base(articleRepository)
{
_articleRepository = articleRepository;
_discussRepository = discussRepository;
_discussService = discussService;
_forumManager = forumManager;
}
private ForumManager _forumManager;
private IArticleRepository _articleRepository;
private ISqlSugarRepository<DiscussAggregateRoot> _discussRepository;
private IDiscussService _discussService;
private readonly ForumManager _forumManager;
private readonly IArticleRepository _articleRepository;
private readonly ISqlSugarRepository<DiscussAggregateRoot> _discussRepository;
public override async Task<PagedResultDto<ArticleGetListOutputDto>> GetListAsync(ArticleGetListInputVo input)
{
@@ -123,7 +112,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
/// <exception cref="UserFriendlyException"></exception>
[Permission("bbs:article:add")]
[Authorize]
public async override Task<ArticleGetOutputDto> CreateAsync(ArticleCreateInputVo input)
public override async Task<ArticleGetOutputDto> CreateAsync(ArticleCreateInputVo input)
{
await VerifyPermissionAsync(input.DiscussId);
return await base.CreateAsync(input);

View File

@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Users;
using Yi.Framework.Bbs.Application.Contracts.Dtos.BbsUser;
using Yi.Framework.Bbs.Application.Contracts.Dtos.Comment;
using Yi.Framework.Bbs.Application.Contracts.IServices;
@@ -34,14 +35,11 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
_repository = CommentRepository;
_bbsUserManager = bbsUserManager;
}
private ForumManager _forumManager { get; set; }
private ISqlSugarRepository<DiscussAggregateRoot> _discussRepository { get; set; }
private IDiscussService _discussService { get; set; }
/// <summary>
/// 获取改主题下的评论,结构为二维列表,该查询无分页
/// </summary>
@@ -127,7 +125,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
{
throw new UserFriendlyException("评论不能为空");
}
await _bbsUserManager.VerifyUserLimitAsync(CurrentUser.GetId());
var discuess = await _discussRepository.GetFirstAsync(x => x.Id == input.DiscussId);
if (discuess is null)
{

View File

@@ -3,7 +3,6 @@ using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using TencentCloud.Pds.V20210701.Models;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.EventBus.Local;
@@ -33,17 +32,19 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
/// Discuss应用服务实现,用于参数校验、领域服务业务组合、日志记录、事务处理、账户信息
/// </summary>
public class DiscussService : YiCrudAppService<DiscussAggregateRoot, DiscussGetOutputDto, DiscussGetListOutputDto,
Guid, DiscussGetListInputVo, DiscussCreateInputVo, DiscussUpdateInputVo>,
Guid, DiscussGetListInputVo, DiscussCreateInput, DiscussUpdateInput>,
IDiscussService
{
private ISqlSugarRepository<DiscussTopEntity> _discussTopRepository;
private ISqlSugarRepository<AgreeEntity> _agreeRepository;
private BbsUserManager _bbsUserManager;
private IDiscussLableRepository _discussLableRepository;
public DiscussService(BbsUserManager bbsUserManager, ForumManager forumManager,
ISqlSugarRepository<DiscussTopEntity> discussTopRepository,
ISqlSugarRepository<PlateAggregateRoot> plateEntityRepository, ILocalEventBus localEventBus,
ISqlSugarRepository<AgreeEntity> agreeRepository, IDiscussLableRepository discussLableRepository) : base(forumManager._discussRepository)
ISqlSugarRepository<AgreeEntity> agreeRepository, IDiscussLableRepository discussLableRepository) : base(
forumManager._discussRepository)
{
_forumManager = forumManager;
_plateEntityRepository = plateEntityRepository;
@@ -59,13 +60,13 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
private ISqlSugarRepository<PlateAggregateRoot> _plateEntityRepository { get; set; }
/// <summary>
/// 单查
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async override Task<DiscussGetOutputDto> GetAsync(Guid id)
public override async Task<DiscussGetOutputDto> GetAsync(Guid id)
{
//查询主题发布 浏览主题 事件,浏览数+1
var output = await _forumManager._discussRepository._DbQueryable
@@ -98,32 +99,44 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
}, true)
.FirstAsync(discuss => discuss.Id == id);
if (output is null)
if (output is null)
{
throw new UserFriendlyException("该主题不存在", "404");
}
switch (output.DiscussType)
{
case DiscussTypeEnum.Article: break;
//查询的是悬赏主题
case DiscussTypeEnum.Reward:
var reward = await _forumManager._discussRewardRepository.GetAsync(x => x.DiscussId == output.Id);
output.RewardData = reward.Adapt<DiscussRewardGetOutputDto>();
break;
}
//组装点赞
var agreeCreatorList =
(await _agreeRepository._DbQueryable.Where(x => x.DiscussId == output.Id).Select(x=>x.CreatorId).ToListAsync());
(await _agreeRepository._DbQueryable.Where(x => x.DiscussId == output.Id).Select(x => x.CreatorId)
.ToListAsync());
//已登录
if (CurrentUser.Id is not null)
{
output.IsAgree = agreeCreatorList.Contains(CurrentUser.Id);
}
//组装标签
var lableDic=await _discussLableRepository.GetDiscussLableCacheMapAsync();
var lableDic = await _discussLableRepository.GetDiscussLableCacheMapAsync();
foreach (var lableId in output.DiscussLableIds)
{
if (lableDic.TryGetValue(lableId,out var item))
if (lableDic.TryGetValue(lableId, out var item))
{
output.Lables.Add(item.Adapt<DiscussLableGetOutputDto>());
}
}
//如果没有权限
if (!await _forumManager.VerifyDiscussPermissionAsync(output.Id,CurrentUser.Id, CurrentUser.Roles))
if (!await _forumManager.VerifyDiscussPermissionAsync(output.Id, CurrentUser.Id, CurrentUser.Roles))
{
output.SetNoPermission();
}
@@ -131,11 +144,12 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
{
output.SetPassPermission();
}
await _localEventBus.PublishAsync(new SeeDiscussEventArgs
{ DiscussId = output.Id, OldSeeNum = output.SeeNum });
return output;
}
/// <summary>
/// 查询
/// </summary>
@@ -159,7 +173,8 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
// .OrderByIF(input.Type == QueryDiscussTypeEnum.New,
// @"COALESCE(discuss.LastModificationTime, discuss.CreationTime) DESC")
//采用上方写法
.OrderByIF(input.Type == QueryDiscussTypeEnum.New,discuss=>SqlFunc.Coalesce(discuss.LastModificationTime,discuss.CreationTime),OrderByType.Desc)
.OrderByIF(input.Type == QueryDiscussTypeEnum.New, discuss => discuss.CreationTime, OrderByType.Desc)
// .OrderByIF(input.Type == QueryDiscussTypeEnum.New,discuss=>SqlFunc.Coalesce(discuss.LastModificationTime,discuss.CreationTime),OrderByType.Desc)
.OrderByIF(input.Type == QueryDiscussTypeEnum.Host, discuss => discuss.SeeNum, OrderByType.Desc)
.OrderByIF(input.Type == QueryDiscussTypeEnum.Suggest, discuss => discuss.AgreeNum, OrderByType.Desc)
.Select((discuss, user, info) => new DiscussGetListOutputDto
@@ -187,10 +202,10 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
(await _agreeRepository._DbQueryable.Where(x => discussId.Contains(x.DiscussId)).ToListAsync())
.GroupBy(x => x.DiscussId)
.ToDictionary(x => x.Key, y => y.Select(y => y.CreatorId).ToList());
var levelCacheDic= await _bbsUserManager.GetLevelCacheMapAsync();
var lableDic=await _discussLableRepository.GetDiscussLableCacheMapAsync();
var levelCacheDic = await _bbsUserManager.GetLevelCacheMapAsync();
var lableDic = await _discussLableRepository.GetDiscussLableCacheMapAsync();
//组装等级、是否点赞赋值、标签
items?.ForEach(x =>
{
@@ -198,20 +213,19 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
if (CurrentUser.Id is not null)
{
//默认fasle
if (agreeDic.TryGetValue(x.Id,out var userIds))
if (agreeDic.TryGetValue(x.Id, out var userIds))
{
x.IsAgree = userIds.Contains(CurrentUser.Id);
}
}
foreach (var lableId in x.DiscussLableIds)
{
if (lableDic.TryGetValue(lableId,out var item))
if (lableDic.TryGetValue(lableId, out var item))
{
x.Lables.Add(item.Adapt<DiscussLableGetOutputDto>());
}
}
});
return new PagedResultDto<DiscussGetListOutputDto>(total, items);
}
@@ -253,15 +267,15 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
}
}, true)
.ToListAsync();
var levelCacheDic= await _bbsUserManager.GetLevelCacheMapAsync();
var lableDic=await _discussLableRepository.GetDiscussLableCacheMapAsync();
var levelCacheDic = await _bbsUserManager.GetLevelCacheMapAsync();
var lableDic = await _discussLableRepository.GetDiscussLableCacheMapAsync();
output?.ForEach(x =>
{
x.User.LevelName = levelCacheDic[x.User.Level].Name;
x.User.LevelName = levelCacheDic[x.User.Level].Name;
foreach (var lableId in x.DiscussLableIds)
{
if (lableDic.TryGetValue(lableId,out var item))
if (lableDic.TryGetValue(lableId, out var item))
{
x.Lables.Add(item.Adapt<DiscussLableGetOutputDto>());
}
@@ -277,7 +291,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
/// <returns></returns>
[Permission("bbs:discuss:add")]
[Authorize]
public override async Task<DiscussGetOutputDto> CreateAsync(DiscussCreateInputVo input)
public override async Task<DiscussGetOutputDto> CreateAsync(DiscussCreateInput input)
{
var plate = await _plateEntityRepository.FindAsync(x => x.Id == input.PlateId);
if (plate is null)
@@ -300,14 +314,30 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
}
}
var entity = await _forumManager.CreateDiscussAsync(await MapToEntityAsync(input));
await _bbsUserManager.VerifyUserLimitAsync(CurrentUser.GetId());
var entity = await _forumManager.CreateDiscussAsync(await MapToEntityAsync(input),
input.RewardData.Adapt<DiscussRewardAggregateRoot>());
return await MapToGetOutputDtoAsync(entity);
}
public override Task<DiscussGetOutputDto> UpdateAsync(Guid id, DiscussUpdateInputVo input)
/// <summary>
/// 设置悬赏主题已解决
/// </summary>
/// <param name="discussId"></param>
/// <exception cref="UserFriendlyException"></exception>
[HttpPut("discuss/reward/resolve/{discussId}")]
[Authorize]
public async Task SetRewardResolvedAsync([FromRoute] Guid discussId)
{
return base.UpdateAsync(id, input);
var reward = await _forumManager._discussRewardRepository.GetFirstAsync(x => x.DiscussId == discussId);
if (reward is null)
{
throw new UserFriendlyException("未找到该悬赏主题", "404");
}
//设置已解决
reward.SetResolved();
await _forumManager._discussRewardRepository.UpdateAsync(reward);
}
}
}

View File

@@ -0,0 +1,17 @@
namespace Yi.Framework.Bbs.Domain.Shared.Enums;
/// <summary>
/// 主题类型
/// </summary>
public enum DiscussTypeEnum
{
/// <summary>
/// 文章
/// </summary>
Article = 0,
/// <summary>
/// 悬赏
/// </summary>
Reward=1
}

View File

@@ -39,6 +39,11 @@ namespace Yi.Framework.Bbs.Domain.Entities.Forum
public string? Introduction { get; set; }
public int AgreeNum { get; set; }
public int SeeNum { get; set; }
/// <summary>
/// 主题类型
/// </summary>
public DiscussTypeEnum DiscussType { get; set; }
/// <summary>
/// 封面
/// </summary>

View File

@@ -0,0 +1,38 @@
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Auditing;
namespace Yi.Framework.Bbs.Domain.Entities.Forum;
[SugarTable("DiscussReward")]
[SugarIndex($"index_{nameof(DiscussId)}", nameof(DiscussId), OrderByType.Asc)]
public class DiscussRewardAggregateRoot : FullAuditedAggregateRoot<Guid>
{
public Guid DiscussId { get; set; }
/// <summary>
/// 是否已解决
/// </summary>
public bool IsResolved{ get; set; }
/// <summary>
/// 悬赏最小价值
/// </summary>
public decimal? MinValue { get; set; }
/// <summary>
/// 悬赏最大价值
/// </summary>
public decimal? MaxValue { get; set; }
/// <summary>
/// 作者联系方式
/// </summary>
public string Contact { get; set; }
public void SetResolved()
{
IsResolved = true;
}
}

View File

@@ -1,5 +1,4 @@
using TencentCloud.Tbm.V20180129.Models;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
using Volo.Abp.EventBus.Local;

View File

@@ -13,25 +13,49 @@ namespace Yi.Framework.Bbs.Domain.Managers
public class BbsUserManager : DomainService
{
public ISqlSugarRepository<UserAggregateRoot> _userRepository;
public ISqlSugarRepository<BbsUserExtraInfoEntity> _bbsUserInfoRepository;
// public Dictionary<int, LevelCacheItem> _levelCacheDic;
private LevelManager _levelManager;
private readonly LevelManager _levelManager;
public BbsUserManager(ISqlSugarRepository<UserAggregateRoot> userRepository,
ISqlSugarRepository<BbsUserExtraInfoEntity> bbsUserInfoRepository,
LevelManager levelManager
)
{
_userRepository = userRepository;
_bbsUserInfoRepository = bbsUserInfoRepository;
_levelManager = levelManager;
}
/// <summary>
/// 校验用户限制
/// </summary>
/// <param name="userId"></param>
/// <exception cref="UserFriendlyException"></exception>
public async Task VerifyUserLimitAsync(Guid userId)
{
var userInfo = await GetBbsUserInfoAsync(userId);
if (userInfo.UserLimit == UserLimitEnum.Ban)
{
throw new UserFriendlyException("你已被禁用,如存疑虑,请联系管理员进行申诉");
}
if (userInfo.UserLimit == UserLimitEnum.Dangerous)
{
throw new UserFriendlyException("您的账号被标记为危险状态,请遵规守法,等待后续解除");
}
}
/// <summary>
/// 获取等级关系
/// </summary>
/// <returns></returns>
public async Task<Dictionary<int, LevelCacheItem>> GetLevelCacheMapAsync()
{
return await _levelManager.GetCacheMapAsync();
}
/// <summary>
/// 获取bbs用户信息
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public async Task<BbsUserInfoDto?> GetBbsUserInfoAsync(Guid userId)
{
var userInfo = await _userRepository._DbQueryable
@@ -50,7 +74,7 @@ namespace Yi.Framework.Bbs.Domain.Managers
}, true)
.FirstAsync(user => user.Id == userId);
var levelCacheDic= await GetLevelCacheMapAsync();
var levelCacheDic = await GetLevelCacheMapAsync();
userInfo.LevelName = levelCacheDic[userInfo.Level].Name;
return userInfo;
}
@@ -73,8 +97,8 @@ namespace Yi.Framework.Bbs.Domain.Managers
DiscussNumber = info.DiscussNumber
}, true)
.ToListAsync();
var levelCacheDic= await GetLevelCacheMapAsync();
userInfos?.ForEach(userInfo => userInfo.LevelName =levelCacheDic[userInfo.Level].Name);
var levelCacheDic = await GetLevelCacheMapAsync();
userInfos?.ForEach(userInfo => userInfo.LevelName = levelCacheDic[userInfo.Level].Name);
return userInfos ?? new List<BbsUserInfoDto>();
}

View File

@@ -21,21 +21,30 @@ namespace Yi.Framework.Bbs.Domain.Managers
public readonly ISqlSugarRepository<PlateAggregateRoot, Guid> _plateEntityRepository;
public readonly ISqlSugarRepository<CommentAggregateRoot, Guid> _commentRepository;
public readonly ISqlSugarRepository<ArticleAggregateRoot, Guid> _articleRepository;
public ForumManager(ISqlSugarRepository<DiscussAggregateRoot, Guid> discussRepository, ISqlSugarRepository<PlateAggregateRoot, Guid> plateEntityRepository, ISqlSugarRepository<CommentAggregateRoot, Guid> commentRepository, ISqlSugarRepository<ArticleAggregateRoot, Guid> articleRepository)
public readonly ISqlSugarRepository<DiscussRewardAggregateRoot,Guid> _discussRewardRepository;
public ForumManager(ISqlSugarRepository<DiscussAggregateRoot, Guid> discussRepository, ISqlSugarRepository<PlateAggregateRoot, Guid> plateEntityRepository, ISqlSugarRepository<CommentAggregateRoot, Guid> commentRepository, ISqlSugarRepository<ArticleAggregateRoot, Guid> articleRepository, ISqlSugarRepository<DiscussRewardAggregateRoot, Guid> discussRewardRepository)
{
_discussRepository = discussRepository;
_plateEntityRepository = plateEntityRepository;
_commentRepository = commentRepository;
_articleRepository = articleRepository;
_discussRewardRepository = discussRewardRepository;
}
//主题是不能直接创建的,需要由领域服务统一创建
public async Task<DiscussAggregateRoot> CreateDiscussAsync(DiscussAggregateRoot entity)
public async Task<DiscussAggregateRoot> CreateDiscussAsync(DiscussAggregateRoot entity,DiscussRewardAggregateRoot rewardEntity=null)
{
entity.CreationTime = DateTime.Now;
entity.AgreeNum = 0;
entity.SeeNum = 0;
return await _discussRepository.InsertReturnEntityAsync(entity);
var discuss = await _discussRepository.InsertReturnEntityAsync(entity);
if (discuss.DiscussType==DiscussTypeEnum.Reward)
{
rewardEntity.DiscussId=discuss.Id;
Check.NotNull(rewardEntity,"悬赏类型,悬赏信息不能为空");
await _discussRewardRepository.InsertAsync(rewardEntity);
}
return discuss;
}
public async Task<CommentAggregateRoot> CreateCommentAsync(Guid discussId, Guid parentId, Guid rootId, string content)

View File

@@ -37,10 +37,10 @@ namespace Yi.Framework.Bbs.Domain.Managers
/// <returns></returns>
public async Task<Dictionary<int, LevelCacheItem>> GetCacheMapAsync()
{
var items = _levelCache.GetOrAdd(LevelConst.LevelCacheKey, () =>
var items =await _levelCache.GetOrAddAsync(LevelConst.LevelCacheKey,async () =>
{
var cacheItem = (_repository.GetListAsync().Result)
.OrderByDescending(x => x.CurrentLevel).ToList()
var cacheItem = ((await _repository.GetListAsync())
.OrderByDescending(x => x.CurrentLevel))
.Adapt<List<LevelCacheItem>>();
return cacheItem;
});

View File

@@ -34,7 +34,7 @@ public class DiscussLableRepository : SqlSugarRepository<DiscussLableAggregateRo
return entities.Adapt<List<DiscussLableCacheItem>>();
}, () =>
new DistributedCacheEntryOptions()
{ AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2) }
{ AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2) },hideErrors:true
);
return cahce.ToDictionary(x => x.Id);
}

View File

@@ -23,7 +23,7 @@ namespace Yi.Framework.ChatHub.Application.Services
/// <param name="chatContext"></param>
/// <returns></returns>
[Authorize]
[HttpPost("ai-chat/chat/{model}")]
[HttpPost("ai-chat/chat/{*model}")]
public async Task ChatAsync([FromRoute]string model, [FromBody] List<AiChatContextDto> chatContext)
{
const int maxChar = 10;

View File

@@ -1,11 +1,14 @@
using System.Collections.Generic;
using System.Net;
using Microsoft.Extensions.Options;
using OpenAI;
using OpenAI.Managers;
using OpenAI.ObjectModels;
using OpenAI.ObjectModels.RequestModels;
using OpenAI.ObjectModels.ResponseModels;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
// using OpenAI;
// using OpenAI.Managers;
// using OpenAI.ObjectModels;
// using OpenAI.ObjectModels.RequestModels;
// using OpenAI.ObjectModels.ResponseModels;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Services;
using Yi.Framework.ChatHub.Domain.Shared.Dtos;
@@ -14,60 +17,50 @@ namespace Yi.Framework.ChatHub.Domain.Managers
{
public class AiManager : ISingletonDependency
{
public AiManager(IOptions<AiOptions> options)
private readonly Kernel _kernel;
public AiManager(Kernel kernel)
{
this.OpenAIService = new OpenAIService(new OpenAiOptions()
{
ApiKey = options.Value.ApiKey,
BaseDomain = options.Value.BaseDomain
});
_kernel = kernel;
}
private OpenAIService OpenAIService { get; }
public async IAsyncEnumerable<string> ChatAsStreamAsync(string model, List<AiChatContextDto> aiChatContextDtos)
public async IAsyncEnumerable<string?> ChatAsStreamAsync(string model, List<AiChatContextDto> aiChatContextDtos)
{
if (aiChatContextDtos.Count == 0)
{
yield return null;
}
var openSettings = new OpenAIPromptExecutionSettings()
{
MaxTokens =1000
};
List<ChatMessage> messages = aiChatContextDtos.Select(x =>
{
if (x.AnswererType == AnswererTypeEnum.Ai)
{
return ChatMessage.FromSystem(x.Message);
}
else
{
return ChatMessage.FromUser(x.Message);
}
}).ToList();
var completionResult = OpenAIService.ChatCompletion.CreateCompletionAsStream(new ChatCompletionCreateRequest
{
Messages = messages,
Model =model
});
var chatCompletionService = this._kernel.GetRequiredService<IChatCompletionService>(model);
HttpStatusCode? error = null;
await foreach (var result in completionResult)
var history =new ChatHistory();
foreach (var aiChatContextDto in aiChatContextDtos)
{
if (result.Successful)
if (aiChatContextDto.AnswererType==AnswererTypeEnum.Ai)
{
yield return result.Choices.FirstOrDefault()?.Message.Content ?? null;
history.AddSystemMessage(aiChatContextDto.Message);
}
else
else if(aiChatContextDto.AnswererType==AnswererTypeEnum.User)
{
error = result.HttpStatusCode;
break;
history.AddUserMessage(aiChatContextDto.Message);
}
}
if (error == HttpStatusCode.PaymentRequired)
var results = chatCompletionService.GetStreamingChatMessageContentsAsync(
chatHistory: history,
executionSettings: openSettings,
kernel: _kernel);
if (results is null)
{
yield return "余额不足,请联系站长充值";
yield return null;
}
await foreach (var result in results)
{
yield return result.Content;
}
}
}
}

View File

@@ -7,9 +7,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Betalgo.OpenAI" Version="8.6.1" />
<PackageReference Include="Volo.Abp.AspNetCore.SignalR" Version="$(AbpVersion)" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.40.0" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.Caching" Version="$(AbpVersion)" />
<ProjectReference Include="..\..\..\framework\Yi.Framework.Caching.FreeRedis\Yi.Framework.Caching.FreeRedis.csproj" />

View File

@@ -1,6 +1,10 @@
using Volo.Abp.Domain;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Volo.Abp.Domain;
using Yi.Framework.Caching.FreeRedis;
using Yi.Framework.ChatHub.Domain.Shared;
using Yi.Framework.Core.Options;
namespace Yi.Framework.ChatHub.Domain
@@ -13,9 +17,27 @@ namespace Yi.Framework.ChatHub.Domain
)]
public class YiFrameworkChatHubDomainModule : AbpModule
{
public override async Task OnPostApplicationInitializationAsync(ApplicationInitializationContext context)
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var services = context.Services;
// 配置绑定
var semanticKernelSection = configuration.GetSection("SemanticKernel");
services.Configure<SemanticKernelOptions>(configuration.GetSection("SemanticKernel"));
#pragma warning disable SKEXP0010
// 从配置中获取值
var options = semanticKernelSection.Get<SemanticKernelOptions>();
foreach (var optionsModelId in options.ModelIds)
{
services.AddKernel()
.AddOpenAIChatCompletion(
serviceId: optionsModelId,
modelId: optionsModelId,
endpoint: new Uri(options.Endpoint),
apiKey: options.ApiKey);
}
#pragma warning restore SKEXP0010
}
}
}

View File

@@ -6,8 +6,10 @@ using System.Threading.Tasks;
using Mapster;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Volo.Abp;
using Volo.Abp.Application.Services;
using Volo.Abp.Caching;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids;
using Yi.Framework.Core.Enums;
@@ -16,6 +18,7 @@ using Yi.Framework.Rbac.Application.Contracts.Dtos.FileManager;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.Rbac.Domain.Entities;
using Yi.Framework.Rbac.Domain.Managers;
using Yi.Framework.Rbac.Domain.Shared.Caches;
namespace Yi.Framework.Rbac.Application.Services
{
@@ -23,11 +26,13 @@ namespace Yi.Framework.Rbac.Application.Services
{
private readonly IRepository<FileAggregateRoot> _repository;
private readonly FileManager _fileManager;
private readonly IMemoryCache _memoryCache;
public FileService(IRepository<FileAggregateRoot> repository, FileManager fileManager)
public FileService(IRepository<FileAggregateRoot> repository, FileManager fileManager, IMemoryCache memoryCache)
{
_repository = repository;
_fileManager = fileManager;
_memoryCache = memoryCache;
}
/// <summary>
@@ -37,14 +42,22 @@ namespace Yi.Framework.Rbac.Application.Services
[Route("file/{code}/{isThumbnail?}")]
public async Task<IActionResult> Get([FromRoute] Guid code, [FromRoute] bool? isThumbnail)
{
var file = await _repository.GetAsync(x => x.Id == code);
var fileCache = await _memoryCache.GetOrCreateAsync($"File:{code}", async (options) =>
{
options.AbsoluteExpiration = DateTime.Now.AddDays(1);
var file = await _repository.GetAsync(x => x.Id == code);
if (file == null!) return null;
return file.Adapt<FileCacheItem>();
});
var file = fileCache?.Adapt<FileAggregateRoot>();
var path = file?.GetQueryFileSavePath(isThumbnail);
if (path is null || !File.Exists(path))
{
return new NotFoundResult();
}
var steam = await File.ReadAllBytesAsync(path);
return new FileContentResult(steam, file.GetMimeMapping());
var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
return new FileStreamResult(stream, file!.GetMimeMapping());
}
/// <summary>
@@ -57,12 +70,13 @@ namespace Yi.Framework.Rbac.Application.Services
for (int i = 0; i < file.Count; i++)
{
var entity= entities[i];
using (var steam = file[i].OpenReadStream())
{
await _fileManager.SaveFileAsync(entity,steam);
}
var entity = entities[i];
using (var steam = file[i].OpenReadStream())
{
await _fileManager.SaveFileAsync(entity, steam);
}
}
return entities.Adapt<List<FileGetListOutputDto>>();
}
}

View File

@@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Microsoft.VisualBasic;
using TencentCloud.Mna.V20210119.Models;
using Volo.Abp.Application.Services;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;

View File

@@ -51,7 +51,7 @@ namespace Yi.Framework.Rbac.Application.Services.System
.WhereIF(!string.IsNullOrEmpty(input.DeptName), u => u.DeptName.Contains(input.DeptName!))
.WhereIF(input.State is not null, u => u.State == input.State)
.OrderBy(u => u.OrderNum, OrderByType.Asc)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
.ToListAsync();
return new PagedResultDto<DeptGetListOutputDto>
{
Items = await MapToGetListOutputDtosAsync(entities),

View File

@@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using Volo.Abp.Application.Dtos;
using Yi.Framework.Ddd.Application;
@@ -54,5 +55,25 @@ namespace Yi.Framework.Rbac.Application.Services.System
throw new UserFriendlyException(RoleConst.Exist);
}
}
/// <summary>
/// 更新状态
/// </summary>
/// <param name="id"></param>
/// <param name="state"></param>
/// <returns></returns>
[Route("post/{id}/{state}")]
public async Task<PostGetOutputDto> UpdateStateAsync([FromRoute] Guid id, [FromRoute] bool state)
{
var entity = await _repository.GetByIdAsync(id);
if (entity is null)
{
throw new ApplicationException("岗位未存在");
}
entity.State = state;
await _repository.UpdateAsync(entity);
return await MapToGetOutputDtoAsync(entity);
}
}
}

View File

@@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using TencentCloud.Tcr.V20190924.Models;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Caching;

View File

@@ -0,0 +1,29 @@
namespace Yi.Framework.Rbac.Domain.Shared.Caches;
public class FileCacheItem
{
public Guid Id { get; set; }
/// <summary>
/// 文件大小
///</summary>
public decimal FileSize { get; set; }
/// <summary>
/// 文件名
///</summary>
public string FileName { get; set; }
/// <summary>
/// 文件路径
///</summary>
public string FilePath { get; set; }
public DateTime CreationTime { get; set; }
public Guid? CreatorId { get; set; }
public Guid? LastModifierId { get; set; }
public DateTime? LastModificationTime { get; set; }
}

View File

@@ -34,8 +34,7 @@ namespace Yi.Framework.Rbac.Domain.Entities
var type = GetFileType();
var savePath = GetSaveFilePath();
var filePath = Path.Combine(savePath, this.FileName);
this.FilePath = filePath;
this.FilePath = savePath;
}
/// <summary>

View File

@@ -1,59 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TencentCloud.Common.Profile;
using TencentCloud.Common;
using TencentCloud.Sms.V20210111.Models;
using TencentCloud.Sms.V20210111;
using Volo.Abp.Domain.Services;
using Microsoft.Extensions.Logging;
namespace Yi.Framework.Rbac.Domain.Managers
{
public class TencentCloudManager : DomainService
{
private ILogger<TencentCloudManager> _logger;
public TencentCloudManager(ILogger<TencentCloudManager> logger)
{
_logger= logger;
}
public async Task SendSmsAsync()
{
try
{
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey此处还需注意密钥对的保密
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露并威胁账号下所有资源的安全性。以下代码示例仅供参考建议采用更安全的方式来使用密钥请参见https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
Credential cred = new Credential
{
SecretId = "SecretId",
SecretKey = "SecretKey"
};
// 实例化一个client选项可选的没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
// 实例化一个http选项可选的没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.Endpoint = ("sms.tencentcloudapi.com");
clientProfile.HttpProfile = httpProfile;
// 实例化要请求产品的client对象,clientProfile是可选的
SmsClient client = new SmsClient(cred, "", clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
SendSmsRequest req = new SendSmsRequest();
// 返回的resp是一个SendSmsResponse的实例与请求对象对应
SendSmsResponse resp = await client.SendSms(req);
// 输出json格式的字符串回包
_logger.LogInformation("腾讯云Sms返回"+AbstractModel.ToJsonString(resp));
}
catch (Exception e)
{
_logger.LogError(e,e.ToString());
}
}
}
}
// using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Text;
// using System.Threading.Tasks;
// using TencentCloud.Common.Profile;
// using TencentCloud.Common;
// using TencentCloud.Sms.V20210111.Models;
// using TencentCloud.Sms.V20210111;
// using Volo.Abp.Domain.Services;
// using Microsoft.Extensions.Logging;
//
// namespace Yi.Framework.Rbac.Domain.Managers
// {
// public class TencentCloudManager : DomainService
// {
// private ILogger<TencentCloudManager> _logger;
// public TencentCloudManager(ILogger<TencentCloudManager> logger)
// {
// _logger= logger;
// }
//
// public async Task SendSmsAsync()
// {
//
// try
// {
// // 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey此处还需注意密钥对的保密
// // 代码泄露可能会导致 SecretId 和 SecretKey 泄露并威胁账号下所有资源的安全性。以下代码示例仅供参考建议采用更安全的方式来使用密钥请参见https://cloud.tencent.com/document/product/1278/85305
// // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
// Credential cred = new Credential
// {
// SecretId = "SecretId",
// SecretKey = "SecretKey"
// };
// // 实例化一个client选项可选的没有特殊需求可以跳过
// ClientProfile clientProfile = new ClientProfile();
// // 实例化一个http选项可选的没有特殊需求可以跳过
// HttpProfile httpProfile = new HttpProfile();
// httpProfile.Endpoint = ("sms.tencentcloudapi.com");
// clientProfile.HttpProfile = httpProfile;
//
// // 实例化要请求产品的client对象,clientProfile是可选的
// SmsClient client = new SmsClient(cred, "", clientProfile);
// // 实例化一个请求对象,每个接口都会对应一个request对象
// SendSmsRequest req = new SendSmsRequest();
//
// // 返回的resp是一个SendSmsResponse的实例与请求对象对应
// SendSmsResponse resp = await client.SendSms(req);
// // 输出json格式的字符串回包
// _logger.LogInformation("腾讯云Sms返回"+AbstractModel.ToJsonString(resp));
// }
// catch (Exception e)
// {
// _logger.LogError(e,e.ToString());
// }
// }
// }
// }

View File

@@ -8,7 +8,7 @@
<PackageReference Include="IPTools.China" Version="1.6.0" />
<PackageReference Include="TencentCloudSDK" Version="3.0.966" />
<!-- <PackageReference Include="TencentCloudSDK" Version="3.0.966" />-->
<PackageReference Include="UAParser" Version="3.1.47" />

View File

@@ -1,5 +1,6 @@
using Medallion.Threading;
using Medallion.Threading.Redis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;
using Volo.Abp.AspNetCore.SignalR;
@@ -42,14 +43,18 @@ namespace Yi.Framework.Rbac.Domain
//配置阿里云短信
Configure<AliyunOptions>(configuration.GetSection(nameof(AliyunOptions)));
//分布式锁
context.Services.AddSingleton<IDistributedLockProvider>(sp =>
//分布式锁,需要redis
if (configuration.GetSection("Redis").GetValue<bool>("IsEnabled"))
{
var connection = ConnectionMultiplexer
.Connect(configuration["Redis:Configuration"]);
return new
RedisDistributedSynchronizationProvider(connection.GetDatabase());
});
context.Services.AddSingleton<IDistributedLockProvider>(sp =>
{
var connection = ConnectionMultiplexer
.Connect(configuration["Redis:Configuration"]);
return new
RedisDistributedSynchronizationProvider(connection.GetDatabase());
});
}
}
}
}

View File

@@ -182,18 +182,16 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
IsDeleted = false
};
entities.Add(server);
//定时任务
MenuAggregateRoot task = new MenuAggregateRoot(_guidGenerator.Create(), monitoring.Id)
{
MenuName = "定时任务",
PermissionCode = "monitor:job:list",
MenuType = MenuTypeEnum.Menu,
Router = "job",
Router = "http://ccnetcore.com:16001/hangfire",
IsShow = true,
IsLink = false,
IsCache = true,
Component = "monitor/job/index",
IsLink = true,
MenuIcon = "job",
OrderNum = 97,
IsDeleted = false
@@ -219,7 +217,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
{
MenuName = "接口文档",
MenuType = MenuTypeEnum.Menu,
Router = "http://localhost:19001/swagger",
Router = "http://ccnetcore.com:16001/swagger",
IsShow = true,
IsLink = true,
MenuIcon = "list",

View File

@@ -1,15 +0,0 @@
using Volo.Abp.Application.Services;
namespace Yi.Framework.Stock.Application.Services
{
/// <summary>
/// 常用魔改及扩展示例
/// </summary>
public class TestService : ApplicationService
{
public string TTT()
{
return "你好";
}
}
}

View File

@@ -1,19 +0,0 @@
using Volo.Abp.Caching;
using Volo.Abp.Domain;
using Yi.Framework.Stock.Domain.Shared;
using Yi.Framework.Mapster;
namespace Yi.Framework.Stock.Domain
{
[DependsOn(
typeof(YiFrameworkStockDomainSharedModule),
typeof(YiFrameworkMapsterModule),
typeof(AbpDddDomainModule),
typeof(AbpCachingModule)
)]
public class YiFrameworkStockDomainModule : AbpModule
{
}
}

View File

@@ -30,7 +30,6 @@ namespace Yi.Abp.Application.Services
public ISqlSugarRepository<BannerAggregateRoot> sqlSugarRepository { get; set; }
/// <summary>
/// 动态Api
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
@@ -186,12 +185,12 @@ namespace Yi.Abp.Application.Services
/// <summary>
/// 分布式送abp版本abp套了一层娃。但是纯粹鸡肋不建议使用这个
/// </summary>
public IAbpDistributedLock AbpDistributedLock { get; set; }
public IAbpDistributedLock AbpDistributedLock => LazyServiceProvider.LazyGetService<IAbpDistributedLock>();
/// <summary>
/// 分布式锁推荐使用版本yyds分布式锁永远的神
/// </summary>
public IDistributedLockProvider DistributedLock { get; set; }
public IDistributedLockProvider DistributedLock => LazyServiceProvider.LazyGetService<IDistributedLockProvider>();
/// <summary>
/// 分布式锁

View File

@@ -10,7 +10,7 @@
<ProjectReference Include="..\..\module\digital-collectibles\Yi.Framework.DigitalCollectibles.Application\Yi.Framework.DigitalCollectibles.Application.csproj" />
<ProjectReference Include="..\..\module\rbac\Yi.Framework.Rbac.Application\Yi.Framework.Rbac.Application.csproj" />
<ProjectReference Include="..\..\module\setting-management\Yi.Framework.SettingManagement.Application\Yi.Framework.SettingManagement.Application.csproj" />
<ProjectReference Include="..\..\module\stock\Yi.Framework.Stock.Application\Yi.Framework.Stock.Application.csproj" />
<ProjectReference Include="..\..\module\ai-stock\Yi.Framework.Stock.Application\Yi.Framework.Stock.Application.csproj" />
<ProjectReference Include="..\..\module\tenant-management\Yi.Framework.TenantManagement.Application\Yi.Framework.TenantManagement.Application.csproj" />
<ProjectReference Include="..\Yi.Abp.Application.Contracts\Yi.Abp.Application.Contracts.csproj" />
<ProjectReference Include="..\Yi.Abp.Domain\Yi.Abp.Domain.csproj" />

View File

@@ -11,7 +11,7 @@
<ProjectReference Include="..\..\module\digital-collectibles\Yi.Framework.DigitalCollectibles.SqlSugarCore\Yi.Framework.DigitalCollectibles.SqlSugarCore.csproj" />
<ProjectReference Include="..\..\module\rbac\Yi.Framework.Rbac.SqlSugarCore\Yi.Framework.Rbac.SqlSugarCore.csproj" />
<ProjectReference Include="..\..\module\setting-management\Yi.Framework.SettingManagement.SqlSugarCore\Yi.Framework.SettingManagement.SqlSugarCore.csproj" />
<ProjectReference Include="..\..\module\stock\Yi.Framework.Stock.SqlSugarCore\Yi.Framework.Stock.SqlSugarCore.csproj" />
<ProjectReference Include="..\..\module\ai-stock\Yi.Framework.Stock.SqlSugarCore\Yi.Framework.Stock.SqlSugarCore.csproj" />
<ProjectReference Include="..\..\module\tenant-management\Yi.Framework.TenantManagement.SqlSugarCore\Yi.Framework.TenantManagement.SqlSugarCore.csproj" />
<ProjectReference Include="..\Yi.Abp.Domain\Yi.Abp.Domain.csproj" />
</ItemGroup>

View File

@@ -0,0 +1,34 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.BackgroundWorkers.Hangfire;
using Yi.Framework.Stock.Domain.Managers;
namespace Yi.Abp.Web.Jobs.ai_stock
{
public class GenerateNewsJob : HangfireBackgroundWorkerBase
{
private NewsManager _newsManager;
public GenerateNewsJob(NewsManager newsManager)
{
_newsManager = newsManager;
RecurringJobId = "AI股票新闻生成";
//每个小时整点执行一次
CronExpression = "0 0 * * * ?";
}
public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
{
// 每次触发只有2/24的概率执行生成新闻
var random = new Random();
var probability = random.Next(0, 24);
if (probability < 2)
{
await _newsManager.GenerateNewsAsync();
}
}
}
}

View File

@@ -0,0 +1,26 @@
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.BackgroundWorkers.Hangfire;
using Yi.Framework.Stock.Domain.Managers;
namespace Yi.Abp.Web.Jobs.ai_stock
{
public class GenerateStockPricesJob : HangfireBackgroundWorkerBase
{
private readonly StockMarketManager _stockMarketManager;
public GenerateStockPricesJob(StockMarketManager stockMarketManager)
{
_stockMarketManager = stockMarketManager;
RecurringJobId = "AI股票价格生成";
//每天凌晨1点执行一次
CronExpression = "0 0 1 * * ?";
}
public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
{
await _stockMarketManager.GenerateStocksAsync();
}
}
}

View File

@@ -4,15 +4,17 @@ using Yi.Abp.Web;
//创建日志,可使用{SourceContext}记录
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.AspNetCore.Hosting.Diagnostics", LogEventLevel.Error)
.MinimumLevel.Override("Quartz", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.Async(c => c.File("logs/all/log-.txt", rollingInterval: RollingInterval.Day, restrictedToMinimumLevel: LogEventLevel.Debug))
.WriteTo.Async(c => c.File("logs/error/errorlog-.txt", rollingInterval: RollingInterval.Day, restrictedToMinimumLevel: LogEventLevel.Error))
.WriteTo.Async(c => c.Console())
.CreateLogger();
//由于后端处理请求中,前端请求已经结束,此类日志可不记录
.Filter.ByExcluding(log =>log.Exception?.GetType() == typeof(TaskCanceledException)||log.MessageTemplate.Text.Contains("\"message\": \"A task was canceled.\""))
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.AspNetCore.Hosting.Diagnostics", LogEventLevel.Error)
.MinimumLevel.Override("Quartz", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.Async(c => c.File("logs/all/log-.txt", rollingInterval: RollingInterval.Day, restrictedToMinimumLevel: LogEventLevel.Debug))
.WriteTo.Async(c => c.File("logs/error/errorlog-.txt", rollingInterval: RollingInterval.Day, restrictedToMinimumLevel: LogEventLevel.Error))
.WriteTo.Async(c => c.Console())
.CreateLogger();
try
{

View File

@@ -47,6 +47,15 @@
<Content Update="appsettings.Development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\plugins\news\config.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\plugins\news\skprompt.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\stock\**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>

Some files were not shown because too many files have changed in this diff Show More