Compare commits

..

351 Commits

Author SHA1 Message Date
dubai
fc5779225e docs(readme): 更新 API 响应风格兼容说明 2026-02-12 01:01:13 +08:00
dubai
287b30e2c4 fix(request): 针对ABP 风格响应添加防御性检查,处理 HTTP 200 状态下返回的 error 对象 2026-02-12 00:51:02 +08:00
dubai
d00cdcf122 feat(api): 兼容多种后端API响应风格
- 在环境变量中新增VITE_GLOB_API_STYLE配置项以支持auto、furion和abp三种响应风格
- 在全局类型定义中添加apiStyle字段,支持对应响应风格类型约束
- request.ts中实现Furion风格响应判断和自动切换ABP风格处理逻辑
- 增加ABP风格错误处理,包含401/403自动登出及验证错误提示
- ABP风格下支持HTTP 200直接成功返回数据和不同成功提示模式
- use-app-config.ts中添加apiStyle配置读取及默认值逻辑
- 注释掉YiAbpWebModule中Furion统一响应API的启用代码,改为默认使用ABP风格
2026-02-12 00:43:52 +08:00
dubai
92064cc4f5 fix: 切换回默认接口风格 2026-02-12 00:42:35 +08:00
dubai
b1f9aba4f6 fix: 把 Yi.Vben5.Vue3 下的 public 目录放出来 2026-02-11 23:32:12 +08:00
dubai
e07714ee54 fix(api): 优化401和403错误处理,增加403错误登出逻辑 2026-02-11 23:17:10 +08:00
wcg
f5da80de9e refactor(system): 移除岗位关联部门功能并优化岗位加载逻辑
- 删除岗位表单中的部门选择组件及相关联动逻辑
- 修改岗位接口调用为无参数版本,加载全部岗位数据
- 优化用户手机号字段验证规则,支持字符串和数字
- 用户界面中岗位选择不再依赖部门选择,初始化时加载全部岗位
- 移除岗位抽屉中部门树获取及展示逻辑,简化表单结构
- 调整岗位数据提交时移除部门字段,确保数据一致性
- 修正岗位选择组件占位符及禁用状态逻辑,提升用户体验
2026-02-11 16:43:08 +08:00
wcg
1fd75a198d refactor(vite): 修复代理配置类型错误 2026-02-11 10:16:12 +08:00
wcg
16b2238f5b fix(preferences): 详细说明本地图片路径为"apps/web-antd/public/"目录 2026-02-10 16:26:30 +08:00
wcg
9afeb59766 fix(rbac): 角色菜单树接口增加菜单来源参数 2026-01-27 18:11:52 +08:00
chenchun
960d8a309c fix: 允许静态文件返回未知文件类型并设置默认内容类型
在 UseStaticFiles 配置中启用 ServeUnknownFileTypes 并将 DefaultContentType 设为 application/octet-stream,确保像
2026-01-23 16:52:58 +08:00
dubai
adf09f4753 feat(menu): 添加 Vben5 路由构建功能并优化菜单转换逻辑
- 为 MenuAggregateRoot 添加 Vben5RouterBuild 扩展方法,支持 Vben5 框架的路由构建
- 在 Vben5RouterBuild 中实现完整的 URL 类型检测和内嵌 iframe 处理逻辑
- 添加对内嵌链接、外部链接和普通路由的不同处理策略
- 优化路由名称生成规则,支持开头大写处理
- 在种子数据中添加示例并注释说明
2026-01-11 20:49:47 +08:00
dubai
dc0c83a620 chore(menu): 注释代码生成菜单种子数据 2026-01-11 19:14:34 +08:00
dubai
a1210c1efd fix(menu): 修复菜单列表接口参数缺失menuSource
- 不传递参数时后端默认为ruoyi菜单
2026-01-11 18:41:53 +08:00
dubai
18f253371b fix(rbac): 修复角色授权用户菜单数据种子中的父子关系配置
- 为 roleAuthUser 菜单实体添加了正确的 ParentId系统管理
2026-01-11 18:33:22 +08:00
dubai
bd410087af fix(profile): 暂不支持社交账号绑定提示 2026-01-11 14:28:05 +08:00
dubai
d4678f2fe3 refactor(system): 移除旧用户个人主页信息接口定义 2026-01-11 14:24:21 +08:00
dubai
6f64ebfba3 fix(profile): 修复个人中心接口报错 2026-01-11 14:23:26 +08:00
dubai
689b136724 fix(core): 统一创建时间字段名从 createTime 到 creationTime 2026-01-11 14:15:58 +08:00
dubai
d9d3118879 chore(app): 移除更新提示功能 2026-01-11 14:11:37 +08:00
dubai
1d78fec144 fix(core): 统一电话号码字段名称从 phonenumber 改为 phone 2026-01-11 14:06:04 +08:00
dubai
d12769dd29 fix(core): 统一用户昵称字段名称从 nickName 改为 nick 2026-01-11 14:04:57 +08:00
dubai
bd51228293 docs(readme): 更新项目文档 2026-01-11 14:02:38 +08:00
dubai
eaf5c01a33 fix(project): 移除不必要的项目引用
- 移除 Yi.Abp.SqlSugarCore 中对 Yi.Framework.Stock.SqlSugarCore 的引用
- 移除 Yi.Framework.Rbac.Application.Contracts 中对 Yi.Framework.Bbs.Domain.Shared 的引用
2026-01-11 14:01:44 +08:00
橙子
369d5ef54e update README.md.
Signed-off-by: 橙子 <454313500@qq.com>
2026-01-10 10:31:36 +00:00
橙子
8975b76e79 update README.md.
Signed-off-by: 橙子 <454313500@qq.com>
2026-01-10 10:29:36 +00:00
橙子
7512a373a6 update README.md.
Signed-off-by: 橙子 <454313500@qq.com>
2026-01-10 10:23:55 +00:00
橙子
7b3b7292ea style: 新增vben系统截图
Signed-off-by: 橙子 <454313500@qq.com>
2026-01-10 10:23:09 +00:00
橙子
816f680672 update README.md.
Signed-off-by: 橙子 <454313500@qq.com>
2026-01-10 10:19:31 +00:00
橙子
50e86f99a4 update README.md.
Signed-off-by: 橙子 <454313500@qq.com>
2026-01-10 10:15:16 +00:00
橙子
6701a09ad1 update README.md.
Signed-off-by: 橙子 <454313500@qq.com>
2026-01-10 10:11:38 +00:00
ccnetcore
ecba8d5c66 ci: 修改发布 2026-01-10 18:06:48 +08:00
ccnetcore
69618eef33 Merge remote-tracking branch 'origin/main' 2026-01-10 18:06:15 +08:00
ccnetcore
44394dffac ci: 修改发布 2026-01-10 18:06:04 +08:00
橙子
66c2d641a8 update README.md.
Signed-off-by: 橙子 <454313500@qq.com>
2026-01-10 10:04:56 +00:00
ccnetcore
7e90624467 chore: 调整发布脚本打包目录路径 2026-01-10 17:34:08 +08:00
ccnetcore
19e26f4383 ci: 新增发布流程 2026-01-10 17:24:19 +08:00
ccnetcore
699fc54885 feat: 精简模板代码 2026-01-10 17:11:37 +08:00
ccnetcore
05b81b710f feat: 合并提交 2026-01-10 15:41:12 +08:00
wcg
3c9c882c42 feat: 新增系统字典:Vben5通知类型 2026-01-04 14:11:47 +08:00
dubai
176af22572 fix: 把 Yi.Vben5.Vue3 下的 packages 目录放出来 2026-01-04 14:10:59 +08:00
wcg
51ee3fb460 feat(project): 添加vben5前端 2026-01-04 13:45:07 +08:00
wcg
2c0689fe02 fix:删除文件夹 2026-01-04 13:43:01 +08:00
dubai
1c8899ed4f feat: 新增vben5前端 2026-01-04 13:40:02 +08:00
wcg
30c45eeb59 fix(rbac): 修复字典数据种子和菜单种子中的错误 2026-01-04 13:01:12 +08:00
wcg
cbd76d2952 config(App): 允许vben5默认端口5666跨域请求 2026-01-04 11:50:40 +08:00
wcg
9a121af7bd feat(rbac): 添加操作日志标题模糊搜索 2026-01-04 11:31:28 +08:00
wcg
96503a2f15 feat(role): 添加角色菜单和部门树获取功能 2026-01-04 11:29:52 +08:00
wcg
29f61e1dc9 feat(menu): 添加菜单树构建功能和相关接口 2026-01-04 11:05:53 +08:00
wcg
80d8ac2bc8 feat(menu): 添加菜单树构建功能和相关接口 2026-01-04 11:02:13 +08:00
wcg
f77c775229 feat(rbac): 添加Vben5菜单 2026-01-04 10:55:28 +08:00
wcg
38463fc477 feat(rbac): 为字典种子数据添加列表样式类 2026-01-04 10:43:19 +08:00
wcg
5a212d7949 refactor(application): 将 GetSelectDataListAsync 方法返回类型从 PagedResultDto<T> 改为 List<T> 2026-01-04 10:33:16 +08:00
wcg
fe7e2eb660 feat(user): 添加按部门获取用户列表功能 2026-01-04 10:31:46 +08:00
wcg
225d8d4726 feat(dept): 添加排除指定部门及其子孙部门的查询功能 2026-01-04 10:29:10 +08:00
wcg
b69c6d86c1 feat(dept): 添加部门树形结构功能和相关API接口 2026-01-04 10:23:42 +08:00
wcg
fe7c1763ba feat(config): 添加配置数据种子和根据key查询配置功能
- 添加初始密码配置项的种子数据 (sys.user.initPassword: 123456)
- 在 ConfigService 中新增 GetConfigKeyAsync 方法支持根据key查询配置值
2026-01-04 10:13:51 +08:00
橙子
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
橙子
4dce50438c feat: 完成 2025-03-01 00:12:56 +08:00
橙子
de7f9264fd Merge branch 'refs/heads/stock' into perf-ai 2025-03-01 00:08:32 +08:00
橙子
f00a90f1ef Merge branch 'refs/heads/abp' into perf-ai 2025-03-01 00:08:22 +08:00
橙子
2c578cc9e4 feat: 新增模块 2025-03-01 00:06:56 +08:00
橙子
a04974d905 feat: 新增stock模块 2025-02-28 23:50:35 +08:00
chenchun
23cedbec83 feat: 支持更多ai接入 2025-02-25 15:09:17 +08:00
橙子
3e07ca822a refactor: ai+人工重构优化 framework 2025-02-23 03:06:06 +08:00
橙子
f9341fd2ac feat: 完成单元测试搭建 2025-02-23 01:41:31 +08:00
橙子
f6b19ec2a5 feat: 完成job模块优化 2025-02-23 01:31:30 +08:00
橙子
f8a4c737ee update README.md.
Signed-off-by: 橙子 <454313500@qq.com>
2025-02-22 08:32:32 +00:00
橙子
c395900861 !89 docs: Improve module and dependency documentation clarity
Merge pull request !89 from bytebistro/abp
2025-02-22 07:59:31 +00:00
橙子
b2ad667894 style: 修改md 2025-02-22 15:56:54 +08:00
橙子
45736dfce9 feat: 完成多租户优化改造 2025-02-22 15:26:00 +08:00
chenchun
753b5b0a26 feat: 优化多租户配置 2025-02-21 18:00:06 +08:00
bytebistro
3aaed801f6 docs: Improve module and dependency documentation clarity 2025-02-14 14:37:45 +08:00
橙子
1fd4f2754a feat:优化文章性能 2025-02-12 22:25:27 +08:00
橙子
bedee3391e feat:聊天室支持公式,优化文章 2025-02-12 22:25:15 +08:00
橙子
176a672e86 update README-en.md.
Signed-off-by: 橙子 <454313500@qq.com>
2025-02-09 14:29:55 +00:00
橙子
9da6bcde41 update README.md.
Signed-off-by: 橙子 <454313500@qq.com>
2025-02-09 14:29:18 +00:00
橙子
3f8daa1d17 update README-Docker.md.
Signed-off-by: 橙子 <454313500@qq.com>
2025-02-09 14:25:52 +00:00
橙子
ed873da3b6 feat: 支持docker 2025-02-09 22:23:23 +08:00
橙子
c0dfa83828 doc: 完善docker文档 2025-02-09 01:49:07 +08:00
橙子
db08688968 Merge remote-tracking branch 'origin/abp' into abp 2025-02-09 01:28:22 +08:00
橙子
400a146a48 feat: 新增docker支持 2025-02-09 01:28:13 +08:00
chenchun
a645264da7 feat: 统一修改时区 2025-02-08 10:39:53 +08:00
chenchun
09d19d876f fix: 时区默认采用上海 2025-02-08 10:37:33 +08:00
橙子
373877cfcf feat: 支持hangfire内存模式 2025-02-07 17:52:38 +08:00
橙子
9e143c0a75 style: 修改job时间 2025-02-07 16:06:46 +08:00
橙子
37b16e8395 perf: 整体优化细节 2025-02-06 12:54:48 +08:00
橙子
9c94953e0e fix: 修复文件上传问题 2025-02-06 11:41:56 +08:00
橙子
2c48b8f881 feat:新增面试宝典 2025-02-05 11:58:50 +08:00
橙子
4c4b78dda7 perf: 优化包版本 2025-02-05 11:36:20 +08:00
橙子
4ba9a7917f feat:支持更新时间或者创建时间排序 2025-02-05 00:59:25 +08:00
橙子
5d286ebc9e feat: db操作支持不修改更新审计日志 2025-02-05 00:02:04 +08:00
橙子
e69bbb46b3 fix: 修复跳转刷新问题 2025-02-04 15:47:59 +08:00
橙子
f203c53e19 Merge branch 'abp' into digital-collectibles 2025-02-04 15:40:13 +08:00
橙子
8a9a0c8396 fix: 修复跳转刷新问题 2025-02-04 15:40:00 +08:00
橙子
813b08bf1c Merge branch 'abp' into digital-collectibles
# Conflicts:
#	Yi.Bbs.Vue3/src/views/home/Index.vue
2025-02-04 15:28:23 +08:00
橙子
bce9b58265 perf: 优化bbs前端整体显示 2025-02-04 15:23:20 +08:00
橙子
85223629c1 Merge branch 'refs/heads/abp' into digital-collectibles 2025-02-03 10:30:26 +08:00
橙子
9a73789788 feat: 全面支持deepseek 2025-02-03 01:18:15 +08:00
橙子
25929483c3 feat: 支持多ai聊天 2025-02-02 00:22:27 +08:00
橙子
0e90c54dbb Merge remote-tracking branch 'origin/abp' into abp 2025-02-01 21:53:12 +08:00
橙子
5dea4ab18c feat: 支持分布式锁 2025-02-01 21:53:05 +08:00
橙子
7187397687 !87 update Yi.RuoYi.Vue3/src/views/system/role/index.vue.
Merge pull request !87 from YangHaiPing/N/A
2025-01-31 13:58:12 +00:00
橙子
bf8454a963 !84 拼写错误
Merge pull request !84 from 高级CV工程师/abp
2025-01-31 13:57:21 +00:00
橙子
2b2ac9b924 !86 refactor(tool): 使工具支持跨平台运行
Merge pull request !86 from a2008q/abp
2025-01-31 13:56:36 +00:00
YangHaiPing
78b874936c update Yi.RuoYi.Vue3/src/views/system/role/index.vue.
系统管理-角色管理-数据权限:数据回显时“权限范围”字段信息展示的为对应索引值,不是下拉选项值

Signed-off-by: YangHaiPing <yang1426251993@163.com>
2025-01-30 19:10:35 +00:00
易昊弘
b9866af6cd refactor(tool): 优化模块添加命令执行逻辑
- 优化了路径组合的方式,使代码更加简洁
- 修复AddModule在mac/linux下只能添加一个文件夹的问题
2025-01-22 17:58:46 +08:00
易昊弘
350e4a5753 refactor(tool): 使工具支持跨平台运行
- 在 AddModuleCommand 和 CloneCommand 中增加了对操作系统类型的判断
- 为 Windows、macOS 和 Linux 系统分别设置了不同的进程启动信息
-优化了路径组合方式,使用 Path.Combine 以确保跨平台兼容性
2025-01-21 19:35:30 +08:00
橙子
2544d03434 Merge branch 'refs/heads/abp' into digital-collectibles 2025-01-19 18:53:20 +08:00
橙子
d1c1eed52e fix: 修复权限校验报错 2025-01-19 18:53:09 +08:00
橙子
2329cfd1da Merge branch 'refs/heads/abp' into digital-collectibles 2025-01-19 18:41:03 +08:00
橙子
9960c63f59 feat: 新增角色查看主题权限 2025-01-19 18:40:42 +08:00
橙子
680f9ae246 Merge branch 'abp' into digital-collectibles 2025-01-19 15:42:10 +08:00
橙子
7994e66283 fix: 修复有序及无序标签显示 2025-01-19 15:41:59 +08:00
橙子
2c9d344b76 feat: 完善标签分类功能 2025-01-19 15:17:48 +08:00
橙子
6a9e28700b Merge branch 'refs/heads/abp' into digital-collectibles 2025-01-19 13:23:23 +08:00
橙子
811e5c1a8c fix: 删除多余types 2025-01-19 13:23:12 +08:00
橙子
545cef34a9 feat: 新增知识宝典 2025-01-19 13:20:09 +08:00
橙子
950c89a8bc Merge branch 'refs/heads/abp' into digital-collectibles 2025-01-19 13:14:24 +08:00
橙子
7b5bc0fe3e perf: 优化主题权限 2025-01-19 13:14:08 +08:00
橙子
e05514bc41 chorm: 修改包 2025-01-19 03:32:17 +08:00
橙子
438abf6cea feat: 新增标签模块 2025-01-19 03:31:48 +08:00
橙子
5d5ebb559b Merge branch 'refs/heads/abp' into digital-collectibles 2025-01-18 01:09:42 +08:00
橙子
3be5675828 perf: 优化主题查询 2025-01-18 01:07:38 +08:00
橙子
6482218a68 fix: 修复sqlsguar删除事件 2025-01-17 17:09:50 +08:00
橙子
8d6824b03d fix: 修复授权接口 2025-01-04 17:29:05 +08:00
橙子
69ab65dbc6 Merge branch 'refs/heads/abp' into digital-collectibles 2024-12-26 23:05:18 +08:00
橙子
4b0b0e4451 fix: 修正file为空捕获 2024-12-26 23:02:10 +08:00
橙子
5d21fd0f7c Merge branch 'refs/heads/abp' into digital-collectibles 2024-12-26 21:58:53 +08:00
橙子
0184015ba8 feat:仓储支持工作单元 2024-12-26 21:58:42 +08:00
橙子
ffb329a0d9 feat:支持双开关切换 2024-12-26 21:57:27 +08:00
高级CV工程师
03a712fcfe 优化token输入 2024-12-23 19:25:56 +08:00
高级CV工程师
652c2b6fd0 拼写错误 2024-12-23 12:15:16 +08:00
橙子
f0093499d1 Merge branch 'refs/heads/abp' into digital-collectibles 2024-12-21 23:31:17 +08:00
橙子
c00ada5aee refactor: 完成文件模块优化重构 2024-12-21 23:00:43 +08:00
橙子
6c409bfa00 refactor: 重构文件模块 2024-12-21 18:00:43 +08:00
橙子
1c7637d28b Merge branch 'refs/heads/abp' into digital-collectibles 2024-12-21 15:06:15 +08:00
橙子
48dc89c43d feat: 支持对不同数据库的guid设置方式 2024-12-21 15:04:54 +08:00
橙子
b60cc50508 Merge remote-tracking branch 'origin/abp' into abp 2024-12-21 14:49:58 +08:00
橙子
4e66762036 fix: 修复问题权限赋值 2024-12-21 14:49:51 +08:00
橙子
ff4e1dd182 !82 docker 构建
Merge pull request !82 from fenngmr/feat-docker-build
2024-12-21 06:46:08 +00:00
橙子
36442072ba !83 options 拼写错误
Merge pull request !83 from 高级CV工程师/N/A
2024-12-21 06:45:35 +00:00
高级CV工程师
ea134f52be options 拼写错误
Signed-off-by: 高级CV工程师 <2535688890@qq.com>
2024-12-21 03:33:37 +00:00
fengxian.guo
fe97ba1c19 feat: docker 构建失败问题解决,及docker构建文档 2024-12-21 08:57:23 +08:00
fengxian.guo
2cd8b73aa3 feat: docker 构建失败问题解决,及docker构建文档 2024-12-21 08:37:10 +08:00
橙子
07a5b69511 !81 数据字典详情页重复代码修复
Merge pull request !81 from canye/abp
2024-12-15 09:15:23 +00:00
canye
da0e207b44 数据字典详情页重复代码修复 2024-12-15 17:02:36 +08:00
橙子
fb0edc0ee0 fix: 修复pure问题 2024-12-15 11:32:41 +08:00
橙子
8a26a4aeec fix: 修复pure若干问题 2024-12-15 11:32:04 +08:00
橙子
723ce1bd5d Merge branch 'refs/heads/abp' into digital-collectibles 2024-12-11 19:49:46 +08:00
橙子
8408b39fbb style: 文件未找到,不提示 2024-12-11 19:48:37 +08:00
橙子
5dfaf75440 feat: 新增实体领域事件 2024-12-10 22:04:46 +08:00
橙子
bb3b3702e1 fix: 支持缓存 2024-12-10 00:08:04 +08:00
橙子
81138fcaef Merge branch 'refs/heads/abp' into digital-collectibles 2024-12-10 00:06:14 +08:00
橙子
6be5398114 perf: 优化等级处理 2024-12-08 03:43:04 +08:00
chenchun
365ae8ef0a Merge branch 'refs/heads/abp' into digital-collectibles 2024-12-02 10:11:55 +08:00
chenchun
3932b24fda fix: 修复审计日志无工作单元 2024-12-02 10:11:25 +08:00
橙子
f44737216f feat: 合并 2024-11-30 23:53:24 +08:00
橙子
e4180a0c1a Merge branch 'refs/heads/abp' into digital-collectibles
# Conflicts:
#	Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs
2024-11-30 23:52:33 +08:00
橙子
356938d6d3 pref: 优化db工作单元 2024-11-30 23:45:19 +08:00
chenchun
1090907178 perf: 优化动态api启动 2024-11-29 18:01:54 +08:00
chenchun
da2f7073f9 style: 优化abp-cli提示 2024-11-29 15:25:16 +08:00
chenchun
57fe0e702c Merge remote-tracking branch 'origin/digital-collectibles' into digital-collectibles 2024-11-29 15:01:10 +08:00
chenchun
90d4a98e1e perf: 优化购买商品 2024-11-29 15:00:57 +08:00
chenchun
d047f8aa32 Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-29 14:58:27 +08:00
chenchun
f656ec32c1 perf: 优化在线hub 2024-11-29 14:58:10 +08:00
橙子
3415543175 feat: 支持自动刷新价值调整 2024-11-24 19:07:42 +08:00
chenchun
4191bc86ef Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-20 16:11:53 +08:00
chenchun
1cc0ef916f feat:去除SqlSugarDbContextFactory的缓存 2024-11-20 16:11:33 +08:00
chenchun
fdbee4562a Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-20 13:44:30 +08:00
chenchun
5d793344cd fix: 修复移除属性注入 2024-11-20 13:43:35 +08:00
橙子
559a45c917 Merge branch 'refs/heads/abp' into digital-collectibles
# Conflicts:
#	Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarCoreExtensions.cs
#	Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs
2024-11-19 21:48:48 +08:00
橙子
cadbc09846 update Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlsugarCoreExtensions.cs.
Signed-off-by: 橙子 <454313500@qq.com>
2024-11-19 13:45:54 +00:00
橙子
bcaca0b782 Merge branch 'refs/heads/multipleDbContext' into abp
# Conflicts:
#	Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarCoreExtensions.cs
2024-11-19 21:44:57 +08:00
橙子
5cee7319c6 update Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlsugarCoreExtensions.cs.
Signed-off-by: 橙子 <454313500@qq.com>
2024-11-19 13:43:25 +00:00
chenchun
e960db0d3e feat: 完成hangfire支持工作单元 2024-11-19 18:38:58 +08:00
chenchun
eb2c05e9df feat: 完成支持多db模式 2024-11-19 16:36:33 +08:00
chenchun
353a6b9d0c feat: 完成dbfactory搭建 2024-11-19 12:05:26 +08:00
chenchun
5d2d269f11 feat: 完成多db功能搭建 2024-11-19 11:53:57 +08:00
chenchun
9acb157fae feat: 完成ISqlSugarDbContextDependencies抽离 2024-11-19 11:19:13 +08:00
橙子
4198b53996 feat: 搭建多dbcontext模式 2024-11-18 00:03:20 +08:00
橙子
d8286fb005 style: 新增背景,部分页面bbs适配移动端 2024-11-17 12:19:48 +08:00
橙子
a7bf5e8873 Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-16 13:10:17 +08:00
橙子
fdec9ed6b8 feat: 支持hangfire验证 2024-11-16 13:10:06 +08:00
橙子
a2e2072634 Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-16 11:07:35 +08:00
橙子
84cd83894b fix: 修复authservice状态问题 2024-11-16 11:07:22 +08:00
橙子
32e4145927 Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-16 11:01:18 +08:00
橙子
18dd177961 fix: 修复hangfire ufc时间问题 2024-11-16 11:01:06 +08:00
橙子
e94c65d24a fix: 增加判断 2024-11-16 10:54:14 +08:00
橙子
e72c0d4480 chorm: 构建 2024-11-15 22:08:48 +08:00
橙子
1a7f1c3d15 fix: 新增商品内容 2024-11-15 22:06:15 +08:00
橙子
ace5374813 Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-15 21:39:14 +08:00
橙子
41f91ea12d Merge remote-tracking branch 'origin/abp' into abp 2024-11-15 21:38:26 +08:00
橙子
91bf5f93cd feat: bbs评论为空验证 2024-11-15 21:38:18 +08:00
橙子
9445fa8005 !78 修复移动端模式,菜单无法显示问题
Merge pull request !78 from Po/N/A
2024-11-15 13:33:22 +00:00
Po
6b491d1246 修复移动端模式,菜单无法显示问题
Signed-off-by: Po <448443959@qq.com>
2024-11-15 13:07:21 +00:00
橙子
644045b307 feat: 支持hangfire 2024-11-15 20:24:17 +08:00
橙子
0bf53a1c0d Merge branch 'refs/heads/abp' into digital-collectibles
# Conflicts:
#	Yi.Abp.Net8/Yi.Abp.sln
#	Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application/Jobs/AccessLogCacheJob.cs
#	Yi.Abp.Net8/src/Yi.Abp.Application/Jobs/DemoResetJob.cs
#	Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs
2024-11-15 20:16:23 +08:00
橙子
05ca1aa224 feat: 支持商品自动流拍 2024-11-14 20:30:48 +08:00
chenchun
02d45bb6a7 feat: 支持自动下架商品 2024-11-14 18:56:10 +08:00
橙子
8e805a4cf8 Merge remote-tracking branch 'origin/digital-collectibles' into digital-collectibles
# Conflicts:
#	Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application/Services/Analyses/BbsUserAnalyseService.cs
#	Yi.Abp.Net8/module/digital-collectibles/Yi.Framework.DigitalCollectibles.Application/Services/Analyses/ValueAnalyseService.cs
#	Yi.Abp.Net8/module/digital-collectibles/Yi.Framework.DigitalCollectibles.Domain/Managers/CollectiblesManager.cs
2024-11-13 22:34:18 +08:00
chenchun
43c4c03832 feat: 完成积分、价值排行榜 2024-11-13 22:32:42 +08:00
chenchun
bf2bcd1395 feat: 完成积分、价值排行榜 2024-11-13 19:01:23 +08:00
橙子
f9217dc066 style: 新增shop 2024-11-12 22:29:06 +08:00
橙子
dad4ca4ab4 fix: 修复钱钱保留两位小数 2024-11-11 23:40:20 +08:00
橙子
b282ee8273 style: 新增wxss支持 2024-11-10 22:31:56 +08:00
橙子
3dcb7d0a39 style: 修改概率 2024-11-10 20:21:08 +08:00
橙子
6703897fb1 feat: 完善小程序通知 2024-11-10 20:17:36 +08:00
橙子
eef2ed0d64 Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-10 17:33:34 +08:00
橙子
7c13ed6497 fix: 修复依赖注入问题 2024-11-10 17:14:50 +08:00
橙子
93dea4fa46 feat: 支持微信通知 2024-11-10 13:41:04 +08:00
橙子
83fb93da11 feat: 完成微信小程序消息推送 2024-11-09 19:05:42 +08:00
橙子
3fbaffe9a2 feat: 优化账号绑定逻辑 2024-11-09 01:20:53 +08:00
橙子
3b93e8b8ec fix: 修复缓存批量删除 2024-11-09 00:00:02 +08:00
chenchun
24cf087320 Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-08 13:58:13 +08:00
chenchun
81f9fd7473 fix:修复矿池刷新数量 2024-11-04 18:54:35 +08:00
橙子
e2091eb986 chorm: 构建 2024-11-03 21:16:01 +08:00
橙子
22a8703978 feat: 完成bbs商城 2024-11-03 18:01:47 +08:00
橙子
1468a7b878 feat: 完成商城系统 2024-11-03 15:49:41 +08:00
橙子
fe7211860f feat: 搭建商城基础业务 2024-11-03 15:27:01 +08:00
橙子
fddf80e74a feat: 新增商城领域 2024-11-03 02:38:05 +08:00
橙子
5054391f6b feat: 完成账号绑定功能 2024-11-03 01:38:12 +08:00
橙子
5e096a277c Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-02 21:06:59 +08:00
橙子
a21f2342d8 Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-02 15:48:40 +08:00
橙子
ab6563899c Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-02 15:35:10 +08:00
橙子
f82122edf0 Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-02 15:29:07 +08:00
橙子
76d94c0bc9 fix: 修复定时任务 2024-11-02 13:31:18 +08:00
chenchun
31dceec787 feat: 新增邀请码 2024-11-01 18:37:46 +08:00
橙子
a2106bba3e Merge branch 'refs/heads/abp' into digital-collectibles 2024-10-31 21:21:52 +08:00
chenchun
b321283f99 fix: 修复code生成规则 2024-10-31 14:27:34 +08:00
chenchun
505e4b6586 feat: 完成 2024-10-30 18:25:44 +08:00
chenchun
b2efd065be feat: 新增记录 2024-10-30 12:02:34 +08:00
chenchun
050af30acb Merge branch 'refs/heads/abp' into digital-collectibles 2024-10-30 11:49:15 +08:00
chenchun
677dca2231 feat: 新增记录 2024-10-30 11:48:13 +08:00
橙子
47ca08e432 Merge branch 'refs/heads/abp' into digital-collectibles 2024-10-29 22:40:38 +08:00
chenchun
1cb396aa14 feat: 支持接口控制 2024-10-29 14:26:23 +08:00
橙子
a47d271a33 feat: 完善,调整矿物刷新 2024-10-28 22:40:23 +08:00
橙子
363be13d12 fix: 修复上架物品问题 2024-10-28 20:56:33 +08:00
chenchun
3bc044e148 feat: 新增挖矿成功事件 2024-10-28 18:37:15 +08:00
chenchun
b8e6dfcd99 feat:新增成功挖到矿物事件 2024-10-28 16:29:38 +08:00
chenchun
4b320c2af2 fix: 修复个人仓库 2024-10-28 13:53:09 +08:00
橙子
dc28d701ba Merge branch 'refs/heads/abp' into digital-collectibles 2024-10-27 21:48:58 +08:00
橙子
77c423f421 Merge branch 'refs/heads/abp' into digital-collectibles 2024-10-26 20:27:11 +08:00
橙子
8a157ba472 Merge branch 'refs/heads/abp' into digital-collectibles
# Conflicts:
#	Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs
2024-10-26 15:52:33 +08:00
橙子
779e84213e feat: 支持手机号为空的临时账号 2024-10-26 10:21:16 +08:00
橙子
254975fcd3 fix: 修复登录 2024-10-25 20:45:35 +08:00
chenchun
e5773df1ab fix: 修复bug 2024-10-25 17:55:18 +08:00
chenchun
c2290f95cf feat: 兼容支持用户名_ 2024-10-25 17:36:23 +08:00
chenchun
6eb72c0303 Merge branch 'refs/heads/abp' into digital-collectibles 2024-10-25 17:31:36 +08:00
chenchun
c4e79e46cf Merge branch 'refs/heads/pr_74' into digital-collectibles
# Conflicts:
#	Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/DictionaryService.cs
2024-10-25 17:30:10 +08:00
橙子
4fadba27dc style: 优化样式 2024-10-24 21:39:20 +08:00
chenchun
7eab4dd5b1 feat: 完成账号逻辑处理 2024-10-22 12:20:53 +08:00
橙子
0e6d380b7e feat: 补充绑定逻辑 2024-10-21 23:35:10 +08:00
橙子
1a73f7bef3 Merge branch 'refs/heads/abp' into digital-collectibles 2024-10-21 23:29:09 +08:00
橙子
a518e7b210 Merge branch 'refs/heads/abp' into digital-collectibles
# Conflicts:
#	Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs
2024-10-21 23:08:21 +08:00
橙子
e2dae1c4ab Merge remote-tracking branch 'refs/remotes/origin/abp-dev-10-21' into digital-collectibles 2024-10-21 20:08:15 +08:00
橙子
0d00f91b31 style: 开启审计日志 2024-10-20 23:43:35 +08:00
橙子
d3b87e8984 style: 调整概率 2024-10-20 22:37:13 +08:00
橙子
fcaad5c6cc feat: 新增用户信息 2024-10-20 22:31:39 +08:00
橙子
8ca741792a fix: 修复挖矿计算问题 2024-10-19 22:44:50 +08:00
橙子
0f687a7e34 feat: 完成微信小程序账户应用服务 2024-10-19 16:17:26 +08:00
橙子
736995c35b feat: 新增微信小程序模块 2024-10-19 14:02:29 +08:00
chenchun
4ae548cc5b Merge branch 'refs/heads/abp' into digital-collectibles 2024-10-18 13:25:57 +08:00
橙子
942868f17f fix: 修复依赖注入问题 2024-10-17 23:45:38 +08:00
橙子
8a57bf52f9 Merge branch 'refs/heads/abp' into digital-collectibles 2024-10-17 23:03:49 +08:00
橙子
260d87c97e feat: 完成 2024-10-17 00:27:20 +08:00
橙子
cb1bac25a3 style: 修改概率 2024-10-16 22:48:11 +08:00
橙子
ef20ef7014 feat: 完成搭建基础功能 2024-10-16 22:47:04 +08:00
chenchun
b88cad7b80 feat: 新增配置 2024-10-16 17:49:47 +08:00
橙子
3383e86064 feat: 完成基础接口搭建 2024-10-15 22:14:14 +08:00
橙子
a0ef3af155 feat: 完成基础框架搭建 2024-10-15 21:50:31 +08:00
chenchun
e8fcab4c6b feat: 新增矿池机制 2024-10-15 18:32:50 +08:00
橙子
1c6a795061 feat: 框架搭建 2024-10-14 23:31:08 +08:00
橙子
36246c2945 feat: 新增数字藏品模块 2024-10-14 22:40:28 +08:00
橙子
7af54f600f feat: 搭建模块 2024-10-14 21:45:14 +08:00
2286 changed files with 145377 additions and 13307 deletions

8
.gitignore vendored
View File

@@ -154,6 +154,10 @@ PublishScripts/
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# 把 Yi.Vben5.Vue3 下的 packages 目录重新放出来
!**/Yi.Vben5.Vue3/packages/
!**/Yi.Vben5.Vue3/packages/**
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
@@ -263,7 +267,10 @@ src/Acme.BookStore.Blazor.Server.Tiered/Logs/*
# Use abp install-libs to restore.
**/wwwroot/libs/*
public
dist
# 把 Yi.Vben5.Vue3 下的 public 目录重新放出来
!**/Yi.Vben5.Vue3/apps/web-antd/public/
.vscode
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Development.json
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Production.json
@@ -275,3 +282,4 @@ database_backup
/Yi.Abp.Net8/src/Yi.Abp.Web/logs/
/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db
package-lock.json

37
README-Docker.md Normal file
View File

@@ -0,0 +1,37 @@
# 🍉Docker 构建说明
## 🍊后端
执行目录Yi\Yi.Abp.Net8
#### 🍊启动
D:/code/csharp/source/Yi/Yi.Bbs.Vue3/yi-bbs.conf 为我的配置文件,内部带了默认的配置文件,根据自己配置进行更改
//不带配置文件
docker run -d --name yi.admin -p 19001:19001 jiftcc/yi.admin:1.0.0
//带配置文件
docker run -d --name yi.admin -p 19001:19001 -v D:/code/csharp/source/Yi/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json:/app/appsettings.json jiftcc/yi.admin:1.0.0
#### 🍊完整代码编译
docker build -t jiftcc/yi.admin:1.0.0 -f Dockerfile .
#### 🍊快速产物编译
docker build -t jiftcc/yi.admin:1.0.0 -f DockerfileFast .
****
## 🍇前端
执行目录Yi\Yi.Bbs.Vue3
#### 🍇启动
D:/code/csharp/source/Yi/Yi.Bbs.Vue3/yi-bbs.conf 为我的conf配置目录默认反向代理到ccnetcore.com根据自己后端地址进行修改配置
docker run -d --name yi.bbs -p 18001:18001 -v D:/code/csharp/source/Yi/Yi.Bbs.Vue3/yi-bbs.conf:/etc/nginx/conf.d/yi-bbs.conf jiftcc/yi.bbs:1.0.0
#### 🍇完整代码编译
docker build -t jiftcc/yi.bbs:1.0.0 -f Dockerfile .
#### 🍇快速产物编译
docker build -t jiftcc/yi.bbs:1.0.0 -f DockerfileFast .

View File

@@ -35,6 +35,18 @@ A Comprehensive Solution, Ultimately Just Another Wheel.
- Yi.RuoYi.Vue3RuoYi JS Backend Frontend
****
## 🍉 docker
Full contentREADME-Docker.md
backend`docker run -d --name yi.admin -p 19001:19001 jiftcc/yi.admin:last`
bbs frontend`docker run -d --name yi.bbs -p 18001:18001 -v /home/Yi/Yi.Bbs.Vue3/yi-bbs.conf:/etc/nginx/conf.d/yi-bbs.conf jiftcc/yi.bbs:last`
> In addition, we provide Docker build operation, and we hope that you can build your own image through this method
****
## 🍊 Official website and demo link

View File

@@ -1,12 +1,18 @@
<h1 align="center"><img align="left" height="150px" src="https://ccnetcore.com/prod-api/wwwroot/logo.png"> Yi框架</h1>
<h4 align="center">一套以用户体验出发的.Net8 Web开源框架</h4>
<h5 align="center">支持Abp.vNext 版本原生版本、Furion版本前端接入Ruoyi/Pure Vue</h5>
<h5 align="center">支持Abp.vNext 版本原生版本、Furion版本前端接入Vben/Ruoyi/Pure Vue</h5>
<h2 align="center">集大成者,终究轮子</h2>
[![star](https://gitee.com/ccnetcore/yi/badge/star.svg?theme=dark)](https://gitee.com/ccnetcore/Yi)
[![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) | 简体中文
****
## 🍍 简介:
@@ -22,7 +28,7 @@ YiFramework是一个基于.Net8+Abp.vNext+SqlSugar的DDD领域驱动设计后端
Yi框架-一套与SqlSugar一样爽的.Net8开源框架。
与Sqlsugar理念一致以用户体验出发。
适合.Net8学习、Sqlsugar学习 、项目二次开发。
全生态拥抱AI接入AI100%代码经过AI洗礼
集大成者,终究轮子
更新频繁可watching持续关注。
@@ -33,25 +39,52 @@ Yi框架-一套与SqlSugar一样爽的.Net8开源框架。
**分支目录:**
- 分支**Abp**: 基于Abp.vNext分支DDD领域驱动设计,回归开发本质,极度简单,一个后台支持以下多个前端
- 分支**main**: 基于Abp.vNext分支默认分支只使用Rbac权限管理后台
- 分支**abp**: 基于Abp.vNext分支完整分支具备超多内置模块
- Yi.Abp.Net8后端
- Yi.Bbs.Vue3Bbs社区 前端
- Yi.Doc.Md: 开源文档教程
- Yi.Vben5.Vue3Vben ts后台前端
- Yi.Pure.Vue3Pure ts后台前端
- Yi.RuoYi.Vue3RuoYi js后台前端
****
## 🍉 docker 一键启动
完整内容在README-Docker.md
后端:`docker run -d --name yi.admin -p 19001:19001 jiftcc/yi.admin:last`
bbs前端`docker run -d --name yi.bbs -p 18001:18001 -v /home/Yi/Yi.Bbs.Vue3/yi-bbs.conf:/etc/nginx/conf.d/yi-bbs.conf jiftcc/yi.bbs:last`
> 另外我们提供docker的build操作我们更希望你能通过此种方式二开构建属于自己的镜像因为上面镜像更新不及时
****
## 🍊 官网及演示地址:
废话少说直接上地址
Yi社区官网网址Bbs社区正式[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
- Yi社区官网网址Bbs社区正式[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
Rbac后台演示地址https://ccnetcore.com:1000 用户cc、密码123456
DotNet后端主要由SharpDance 意框架官方团队 及 数百名 开源用户 共同维护
Pure后台演示地址https://ccnetcore.com:1001 用户cc、密码123456
- Vben后台演示地址https://data.ccnetcore.com:2000 用户cc、密码123456
Vben前端主要由【https://gitee.com/vichen2021】贡献维护
- Ruoyi后台演示地址https://data.ccnetcore.com:1000 用户cc、密码123456
Ruoyi前端主要由30多名开源用户共同贡献维护
- Pure后台演示地址https://data.ccnetcore.com:1001 用户cc、密码123456
Pure前端主要由3名开源用户共同贡献维护
## 🍏 支持:
@@ -60,8 +93,11 @@ Pure后台演示地址https://ccnetcore.com:1001 用户cc、密码123456
- [x] 完全支持微服务架构
****
## 🍇 详细到爆炸的Yi框架教程导航
0. [社区导航大全](https://ccnetcore.com/article/aaa00329-7f35-d3fe-d258-3a0f8380b742/fb8c871b-41fc-21bc-474f-3a154498f42b)
1. [框架快速开始教程](https://ccnetcore.com/article/aaa00329-7f35-d3fe-d258-3a0f8380b742)(已完成)
2. [框架功能模块教程](https://ccnetcore.com/article/8c464ab3-8ba5-2761-a4b0-3a0f83a9f312)(已完成)
3. [实战演练开发教程](https://ccnetcore.com/article/e89c9593-f337-ada7-d108-3a0f83ae48e6)(已完成)
@@ -98,8 +134,13 @@ Pure后台演示地址https://ccnetcore.com:1001 用户cc、密码123456
- [SqlSugar官网](https://www.donet5.com/home/doc)
## 🍅 内置模块简介
- Rbac权限管理系统已上线支持pure、ruoyi前端
- Bbs论坛社区系统已上线
- rbac 权限管理系统已上线支持vben、pure、ruoyi前端
- bbs 论坛社区系统(已上线)
- ai-stock Ai模拟炒股系统已上线
- chat-hub Ai在线聊天室系统已上线
- code-gen 代码生成器工具系统(已上线)
- digital-collectibles 数字藏品小程序系统(已上线)
> 重复的东西,无需再写一遍,这也是优雅的体现之一
@@ -120,14 +161,6 @@ C# Asp.NetCore 8.0
- [x] 分布式缓存Abp.vNext
- [x] 事件总线Abp.vNext
#### 前端
js Vue3
- [x] 异步请求axios
- [x] 图表echarts
- [x] uielement-plus
- [x] 存储pinia
- [x] 路由vue-router
- [x] 打包vite
#### 运维
- [x] 部署nginx
@@ -154,24 +187,16 @@ js Vue3
- 缓存列表
- 服务监控
#### 🍐 BBS社区论坛系统持续更新
采用vue3前端
- 文章功能
- 板块功能
- 主题功能
- 个人中心
- 授权中心
- 权限管理
#### 🍉 演示截图:
<table>
<tr>
<td><img src="readme/101.png"/></td>
<td><img src="readme/102.png"/></td>
<td><img src="readme/303.png"/></td>
<td><img src="readme/304.png"/></td>
</tr>
<tr>
<td><img src="readme/103.png"/></td>
<td><img src="readme/104.png"/></td>
<td><img src="readme/301.png"/></td>
<td><img src="readme/302.png"/></td>
</tr>
</table>
@@ -242,14 +267,16 @@ js Vue3
[Furion百小僧]https://furion.baiqian.ltd/
[du白]https://gitee.com/vichen2021
****
## 🌽 联系我们:
作者QQ`454313500`2029年之前作者24小时在线时刻保持活跃更新。
QQ交流群官方一群已满、官方二群已满、官方三群`786308927`(已满)、官方四群:`498310311`基本已满)、官方五群:`981136525`(新群)
QQ交流群官方一群已满、官方二群已满、官方三群`786308927`(已满)、官方四群:`498310311`(已满)、官方五群:`981136525`
微信交流群:官方微信一群(已满)、官方微信二群
微信交流群:官方微信一群(已满)、官方微信二群(已满)、官方微信三群(已满)、官方微信四群(即满)
微信交流群:加作者微信 chengzilaoge520 橙子老哥520备注拉群
@@ -260,6 +287,12 @@ QQ交流群官方一群已满、官方二群已满、官方三群
****
## 🍄 FQA:
前往官网查看留言区
如何修改仓库代码请直接pr即可官方团队会第一时间处理目前前端都是pr来的
gitee仓库官方地址https://gitee.com/ccnetcore/Yi
github仓库官方地址https://github.com/ccnetcore/Yi.Abp.Admin
前往官网查看留言区:
[留言区](https://ccnetcore.com/discuss/1641030787056930818)

View File

@@ -27,4 +27,7 @@ README.md
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**
!.git/refs/heads/**
appsettings.Development.json
appsettings.Production.json
appsettings.Staging.json

22
Yi.Abp.Net8/Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER root
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" > /etc/timezone
WORKDIR /app
EXPOSE 19001
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /main
COPY . .
WORKDIR "/main/src/Yi.Abp.Web"
RUN dotnet restore "Yi.Abp.Web.csproj"
FROM build AS publish
WORKDIR "/main/src/Yi.Abp.Web"
RUN dotnet publish "Yi.Abp.Web.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Yi.Abp.Web.dll"]

View File

@@ -0,0 +1,11 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER root
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" > /etc/timezone
WORKDIR /app
EXPOSE 19001
FROM base AS final
WORKDIR /app
COPY ["./publish","."]
ENTRYPOINT ["dotnet", "Yi.Abp.Web.dll"]

View File

@@ -36,6 +36,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
version.props = version.props
publish.bat = publish.bat
publish_Demo.bat = publish_Demo.bat
Dockerfile = Dockerfile
DockerfileFast = DockerfileFast
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.SqlSugarCore.Abstractions", "framework\Yi.Framework.SqlSugarCore.Abstractions\Yi.Framework.SqlSugarCore.Abstractions.csproj", "{FD6D6860-3753-4747-8A26-977E4A3001F9}"
@@ -62,18 +64,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Rbac.Domain.Sh
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Rbac.SqlSugarCore", "module\rbac\Yi.Framework.Rbac.SqlSugarCore\Yi.Framework.Rbac.SqlSugarCore.csproj", "{4503A2F9-139D-4CBC-AF11-689C34F0D77B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bbs", "bbs", "{E902A945-4F41-4E96-A0DA-9F66CDA22261}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.Domain.Shared", "module\bbs\Yi.Framework.Bbs.Domain.Shared\Yi.Framework.Bbs.Domain.Shared.csproj", "{EB9349E2-256D-41EB-A345-21635A1361B3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.Domain", "module\bbs\Yi.Framework.Bbs.Domain\Yi.Framework.Bbs.Domain.csproj", "{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.Application.Contracts", "module\bbs\Yi.Framework.Bbs.Application.Contracts\Yi.Framework.Bbs.Application.Contracts.csproj", "{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.Application", "module\bbs\Yi.Framework.Bbs.Application\Yi.Framework.Bbs.Application.csproj", "{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.SqlSugarCore", "module\bbs\Yi.Framework.Bbs.SqlSugarCore\Yi.Framework.Bbs.SqlSugarCore.csproj", "{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "audit-logging", "audit-logging", "{73CCF2C4-B9FD-44AB-8D4B-0A421805B094}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.AuditLogging.SqlSugarCore", "module\audit-logging\Yi.Framework.AuditLogging.SqlSugarCore\Yi.Framework.AuditLogging.SqlSugarCore.csproj", "{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}"
@@ -96,20 +86,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.TenantManageme
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.TenantManagement.Application.Contracts", "module\tenant-management\Yi.Framework.TenantManagement.Application.Contracts\Yi.Framework.TenantManagement.Application.Contracts.csproj", "{FA735055-CBDD-4EFD-B84B-85810DA1425E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "code-gen", "code-gen", "{4FFE7212-21F2-476D-B628-3C65E6C5075E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.Application", "module\code-gen\Yi.Framework.CodeGen.Application\Yi.Framework.CodeGen.Application.csproj", "{97EC40D7-DBFA-467A-98CB-221AF27B14F2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.Application.Contracts", "module\code-gen\Yi.Framework.CodeGen.Application.Contracts\Yi.Framework.CodeGen.Application.Contracts.csproj", "{882BC563-2F75-4B95-AC96-F4BF23F5E69D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.Domain", "module\code-gen\Yi.Framework.CodeGen.Domain\Yi.Framework.CodeGen.Domain.csproj", "{85CB8517-2B80-42D8-B954-081079AC9BA0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.Domain.Shared", "module\code-gen\Yi.Framework.CodeGen.Domain.Shared\Yi.Framework.CodeGen.Domain.Shared.csproj", "{EEFF0F05-2709-4151-A8CE-667935CEAE0B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Caching.FreeRedis", "framework\Yi.Framework.Caching.FreeRedis\Yi.Framework.Caching.FreeRedis.csproj", "{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.SqlSugarCore", "module\code-gen\Yi.Framework.CodeGen.SqlSugarCore\Yi.Framework.CodeGen.SqlSugarCore.csproj", "{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "client", "client", "{8B27846A-043D-4F2F-8140-5CEC9D1863B5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.HttpApi.Client", "client\Yi.Abp.HttpApi.Client\Yi.Abp.HttpApi.Client.csproj", "{6B554DCC-3A81-4624-9141-4E39365ADA35}"
@@ -124,18 +102,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.SettingManagem
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.SettingManagement.SqlSugarCore", "module\setting-management\Yi.Framework.SettingManagement.SqlSugarCore\Yi.Framework.SettingManagement.SqlSugarCore.csproj", "{495C4643-39D4-46E7-BDC8-237589627BE4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "chat-hub", "chat-hub", "{D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.Application.Contracts", "module\chat-hub\Yi.Framework.ChatHub.Application.Contracts\Yi.Framework.ChatHub.Application.Contracts.csproj", "{65D4D033-5504-44B9-B152-0172ACD64CE6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.Domain.Shared", "module\chat-hub\Yi.Framework.ChatHub.Domain.Shared\Yi.Framework.ChatHub.Domain.Shared.csproj", "{DEEC0B15-190C-4464-B469-C45C6563C592}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.SqlSugarCore", "module\chat-hub\Yi.Framework.ChatHub.SqlSugarCore\Yi.Framework.ChatHub.SqlSugarCore.csproj", "{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.Domain", "module\chat-hub\Yi.Framework.ChatHub.Domain\Yi.Framework.ChatHub.Domain.csproj", "{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.Application", "module\chat-hub\Yi.Framework.ChatHub.Application\Yi.Framework.ChatHub.Application.csproj", "{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Rbac.Test", "test\Yi.Framework.Rbac.Test\Yi.Framework.Rbac.Test.csproj", "{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tool", "tool", "{084CBEEC-5D37-4716-B9C7-D80D6960DFF4}"
@@ -156,6 +122,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.Tool.HttpApi.Client"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.SettingManagement.Application", "module\setting-management\Yi.Framework.SettingManagement.Application\Yi.Framework.SettingManagement.Application.csproj", "{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.WeChat.MiniProgram", "framework\Yi.Framework.WeChat.MiniProgram\Yi.Framework.WeChat.MiniProgram.csproj", "{81CEA2ED-917B-41D8-BE0D-39A785B050C0}"
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
Global
@@ -236,26 +204,6 @@ Global
{4503A2F9-139D-4CBC-AF11-689C34F0D77B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4503A2F9-139D-4CBC-AF11-689C34F0D77B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4503A2F9-139D-4CBC-AF11-689C34F0D77B}.Release|Any CPU.Build.0 = Release|Any CPU
{EB9349E2-256D-41EB-A345-21635A1361B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB9349E2-256D-41EB-A345-21635A1361B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB9349E2-256D-41EB-A345-21635A1361B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB9349E2-256D-41EB-A345-21635A1361B3}.Release|Any CPU.Build.0 = Release|Any CPU
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}.Release|Any CPU.Build.0 = Release|Any CPU
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}.Release|Any CPU.Build.0 = Release|Any CPU
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}.Release|Any CPU.Build.0 = Release|Any CPU
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}.Release|Any CPU.Build.0 = Release|Any CPU
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -292,30 +240,10 @@ Global
{FA735055-CBDD-4EFD-B84B-85810DA1425E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA735055-CBDD-4EFD-B84B-85810DA1425E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA735055-CBDD-4EFD-B84B-85810DA1425E}.Release|Any CPU.Build.0 = Release|Any CPU
{97EC40D7-DBFA-467A-98CB-221AF27B14F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{97EC40D7-DBFA-467A-98CB-221AF27B14F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{97EC40D7-DBFA-467A-98CB-221AF27B14F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{97EC40D7-DBFA-467A-98CB-221AF27B14F2}.Release|Any CPU.Build.0 = Release|Any CPU
{882BC563-2F75-4B95-AC96-F4BF23F5E69D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{882BC563-2F75-4B95-AC96-F4BF23F5E69D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{882BC563-2F75-4B95-AC96-F4BF23F5E69D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{882BC563-2F75-4B95-AC96-F4BF23F5E69D}.Release|Any CPU.Build.0 = Release|Any CPU
{85CB8517-2B80-42D8-B954-081079AC9BA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{85CB8517-2B80-42D8-B954-081079AC9BA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{85CB8517-2B80-42D8-B954-081079AC9BA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{85CB8517-2B80-42D8-B954-081079AC9BA0}.Release|Any CPU.Build.0 = Release|Any CPU
{EEFF0F05-2709-4151-A8CE-667935CEAE0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EEFF0F05-2709-4151-A8CE-667935CEAE0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EEFF0F05-2709-4151-A8CE-667935CEAE0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EEFF0F05-2709-4151-A8CE-667935CEAE0B}.Release|Any CPU.Build.0 = Release|Any CPU
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Release|Any CPU.Build.0 = Release|Any CPU
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}.Release|Any CPU.Build.0 = Release|Any CPU
{6B554DCC-3A81-4624-9141-4E39365ADA35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B554DCC-3A81-4624-9141-4E39365ADA35}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B554DCC-3A81-4624-9141-4E39365ADA35}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -336,26 +264,6 @@ Global
{495C4643-39D4-46E7-BDC8-237589627BE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{495C4643-39D4-46E7-BDC8-237589627BE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{495C4643-39D4-46E7-BDC8-237589627BE4}.Release|Any CPU.Build.0 = Release|Any CPU
{65D4D033-5504-44B9-B152-0172ACD64CE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65D4D033-5504-44B9-B152-0172ACD64CE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65D4D033-5504-44B9-B152-0172ACD64CE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65D4D033-5504-44B9-B152-0172ACD64CE6}.Release|Any CPU.Build.0 = Release|Any CPU
{DEEC0B15-190C-4464-B469-C45C6563C592}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DEEC0B15-190C-4464-B469-C45C6563C592}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DEEC0B15-190C-4464-B469-C45C6563C592}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DEEC0B15-190C-4464-B469-C45C6563C592}.Release|Any CPU.Build.0 = Release|Any CPU
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}.Release|Any CPU.Build.0 = Release|Any CPU
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}.Release|Any CPU.Build.0 = Release|Any CPU
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}.Release|Any CPU.Build.0 = Release|Any CPU
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -392,6 +300,10 @@ Global
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Release|Any CPU.Build.0 = Release|Any CPU
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Release|Any CPU.Build.0 = Release|Any CPU
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -420,12 +332,6 @@ Global
{C04D3F71-1557-46D0-B810-97B1FBB6AB73} = {9CC7A457-1236-40BA-B47B-E7B710A3F061}
{A2BB899D-4F9A-4184-81BD-94B938E2AB03} = {9CC7A457-1236-40BA-B47B-E7B710A3F061}
{4503A2F9-139D-4CBC-AF11-689C34F0D77B} = {9CC7A457-1236-40BA-B47B-E7B710A3F061}
{E902A945-4F41-4E96-A0DA-9F66CDA22261} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{EB9349E2-256D-41EB-A345-21635A1361B3} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
{73CCF2C4-B9FD-44AB-8D4B-0A421805B094} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3} = {73CCF2C4-B9FD-44AB-8D4B-0A421805B094}
{791AC2FA-50D3-4408-8D68-31DA72F608BE} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
@@ -437,25 +343,13 @@ Global
{9C8C3C53-3DCE-4516-867E-228858E61B26} = {73CCF2C4-B9FD-44AB-8D4B-0A421805B094}
{17816837-E53B-486B-B796-53C601FE6CD9} = {499A8C71-7892-42D0-A77E-48756E1EFF16}
{FA735055-CBDD-4EFD-B84B-85810DA1425E} = {499A8C71-7892-42D0-A77E-48756E1EFF16}
{4FFE7212-21F2-476D-B628-3C65E6C5075E} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{97EC40D7-DBFA-467A-98CB-221AF27B14F2} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
{882BC563-2F75-4B95-AC96-F4BF23F5E69D} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
{85CB8517-2B80-42D8-B954-081079AC9BA0} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
{EEFF0F05-2709-4151-A8CE-667935CEAE0B} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
{6B554DCC-3A81-4624-9141-4E39365ADA35} = {8B27846A-043D-4F2F-8140-5CEC9D1863B5}
{2D23B44A-DFA3-4C36-8516-4F5AE442403C} = {8B27846A-043D-4F2F-8140-5CEC9D1863B5}
{00E49781-C6A0-491C-86A1-46F685C90915} = {8B27846A-043D-4F2F-8140-5CEC9D1863B5}
{8C68059E-F3B1-4D28-A1C9-A5830F53E5D3} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{6FEE0EB3-EAD2-47F8-B6FC-3D0FD3CCABFF} = {8C68059E-F3B1-4D28-A1C9-A5830F53E5D3}
{495C4643-39D4-46E7-BDC8-237589627BE4} = {8C68059E-F3B1-4D28-A1C9-A5830F53E5D3}
{D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{65D4D033-5504-44B9-B152-0172ACD64CE6} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
{DEEC0B15-190C-4464-B469-C45C6563C592} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07} = {0D10EEF2-FBAE-4C72-B816-A52823FC299B}
{4FEBBDD9-E4F4-4BAF-8599-E2D57C08A74F} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
{2CE51D4C-1EF9-462B-BA14-7EA01A7E4AF1} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
@@ -465,6 +359,7 @@ Global
{4AE84CDE-2A47-4D68-8E93-86193F72E4E8} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
{C8F97775-D903-4365-A4FF-3DA97E318CD2} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956} = {8C68059E-F3B1-4D28-A1C9-A5830F53E5D3}
{81CEA2ED-917B-41D8-BE0D-39A785B050C0} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
{862CA181-BEE6-4870-82D2-B662E527ED8C} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution

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

@@ -4,13 +4,23 @@ using Yi.Framework.AspNetCore.Microsoft.AspNetCore.Middlewares;
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Builder
{
/// <summary>
/// 提供API信息处理的应用程序构建器扩展方法
/// </summary>
public static class ApiInfoBuilderExtensions
{
public static IApplicationBuilder UseYiApiHandlinge([NotNull] this IApplicationBuilder app)
/// <summary>
/// 使用Yi框架的API信息处理中间件
/// </summary>
/// <param name="builder">应用程序构建器实例</param>
/// <returns>配置后的应用程序构建器实例</returns>
/// <exception cref="ArgumentNullException">当builder参数为null时抛出</exception>
public static IApplicationBuilder UseApiInfoHandling([NotNull] this IApplicationBuilder builder)
{
app.UseMiddleware<ApiInfoMiddleware>();
return app;
// 添加API信息处理中间件到请求管道
builder.UseMiddleware<ApiInfoMiddleware>();
return builder;
}
}
}

View File

@@ -5,49 +5,101 @@ using Volo.Abp.AspNetCore.Mvc;
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Builder
{
public static class SwaggerBuilderExtensons
/// <summary>
/// Swagger构建器扩展类
/// </summary>
public static class SwaggerBuilderExtensions
{
public static IApplicationBuilder UseYiSwagger(this IApplicationBuilder app, params SwaggerModel[] swaggerModels)
/// <summary>
/// 配置并使用Yi框架的Swagger中间件
/// </summary>
/// <param name="app">应用程序构建器</param>
/// <param name="swaggerConfigs">Swagger配置模型数组</param>
/// <returns>应用程序构建器</returns>
public static IApplicationBuilder UseYiSwagger(
this IApplicationBuilder app,
params SwaggerConfiguration[] swaggerConfigs)
{
var mvcOptions = app.ApplicationServices.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>().Value;
app.UseSwagger();
app.UseSwaggerUI(c =>
if (app == null)
{
foreach (var setting in mvcOptions.ConventionalControllers.ConventionalControllerSettings)
throw new ArgumentNullException(nameof(app));
}
var mvcOptions = app.ApplicationServices
.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>()
.Value;
// 启用Swagger中间件
app.UseSwagger();
// 配置SwaggerUI
app.UseSwaggerUI(options =>
{
// 添加约定控制器的Swagger终结点
var conventionalSettings = mvcOptions.ConventionalControllers.ConventionalControllerSettings;
foreach (var setting in conventionalSettings)
{
c.SwaggerEndpoint($"/swagger/{setting.RemoteServiceName}/swagger.json", setting.RemoteServiceName);
options.SwaggerEndpoint(
$"/swagger/{setting.RemoteServiceName}/swagger.json",
setting.RemoteServiceName);
}
if (mvcOptions.ConventionalControllers.ConventionalControllerSettings.Count==0&&swaggerModels.Length == 0)
// 如果没有配置任何终结点,使用默认配置
if (!conventionalSettings.Any() && (swaggerConfigs == null || !swaggerConfigs.Any()))
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Yi.Framework");
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Yi.Framework");
return;
}
else
// 添加自定义Swagger配置的终结点
if (swaggerConfigs != null)
{
foreach (var k in swaggerModels)
foreach (var config in swaggerConfigs)
{
c.SwaggerEndpoint(k.Url, k.Name);
options.SwaggerEndpoint(config.Url, config.Name);
}
}
});
return app;
}
}
public class SwaggerModel
/// <summary>
/// Swagger配置模型
/// </summary>
public class SwaggerConfiguration
{
public SwaggerModel(string name)
private const string DefaultSwaggerUrl = "/swagger/v1/swagger.json";
/// <summary>
/// Swagger JSON文档的URL
/// </summary>
public string Url { get; }
/// <summary>
/// Swagger文档的显示名称
/// </summary>
public string Name { get; }
/// <summary>
/// 使用默认URL创建Swagger配置
/// </summary>
/// <param name="name">文档显示名称</param>
public SwaggerConfiguration(string name)
: this(DefaultSwaggerUrl, name)
{
this.Name = name;
this.Url = "/swagger/v1/swagger.json";
}
public SwaggerModel(string url, string name)
/// <summary>
/// 创建自定义Swagger配置
/// </summary>
/// <param name="url">Swagger JSON文档URL</param>
/// <param name="name">文档显示名称</param>
public SwaggerConfiguration(string url, string name)
{
this.Url = url;
this.Name = name;
Url = url ?? throw new ArgumentNullException(nameof(url));
Name = name ?? throw new ArgumentNullException(nameof(name));
}
public string Url { get; set; }
public string Name { get; set; }
}
}

View File

@@ -1,39 +1,61 @@
using System.Diagnostics;
using System.Net.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Json;
using Yi.Framework.Core.Extensions;
using static System.Net.WebRequestMethods;
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Middlewares
{
/// <summary>
/// API响应信息处理中间件
/// 主要用于处理特定文件类型的响应头信息
/// </summary>
[DebuggerStepThrough]
public class ApiInfoMiddleware : IMiddleware, ITransientDependency
{
/// <summary>
/// 处理HTTP请求的中间件方法
/// </summary>
/// <param name="context">HTTP上下文</param>
/// <param name="next">请求处理委托</param>
/// <returns>异步任务</returns>
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
context.Response.OnStarting([DebuggerStepThrough] () =>
{
if (context.Response.StatusCode == StatusCodes.Status200OK
&& context.Response.Headers["Content-Type"].ToString() == "application/vnd.ms-excel")
{
context.FileAttachmentHandle($"{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.xlsx");
}
if (context.Response.StatusCode == StatusCodes.Status200OK &&
context.Response.Headers["Content-Type"].ToString() == "application/x-zip-compressed")
{
context.FileAttachmentHandle($"{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.zip");
}
return Task.CompletedTask;
});
// // 在响应开始时处理文件下载相关的响应头
// context.Response.OnStarting(() =>
// {
// HandleFileDownloadResponse(context);
// return Task.CompletedTask;
// });
// 继续处理管道中的下一个中间件
await next(context);
}
/// <summary>
/// 处理文件下载响应的响应头信息
/// </summary>
/// <param name="context">HTTP上下文</param>
private static void HandleFileDownloadResponse(HttpContext context)
{
// 仅处理状态码为200的响应
if (context.Response.StatusCode != StatusCodes.Status200OK)
{
return;
}
var contentType = context.Response.Headers["Content-Type"].ToString();
var timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
// 处理Excel文件下载
if (contentType == "application/vnd.ms-excel")
{
context.FileAttachmentHandle($"{timestamp}.xlsx");
}
// 处理ZIP文件下载
else if (contentType == "application/x-zip-compressed")
{
context.FileAttachmentHandle($"{timestamp}.zip");
}
}
}
}

View File

@@ -9,149 +9,226 @@ using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Options;
namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Swagger生成器扩展类
/// </summary>
public static class SwaggerAddExtensions
{
public static IServiceCollection AddYiSwaggerGen<Program>(this IServiceCollection services, Action<SwaggerGenOptions>? action=null)
/// <summary>
/// 添加Yi框架的Swagger生成器服务
/// </summary>
/// <typeparam name="TProgram">程序入口类型</typeparam>
/// <param name="services">服务集合</param>
/// <param name="setupAction">自定义配置动作</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddYiSwaggerGen<TProgram>(
this IServiceCollection services,
Action<SwaggerGenOptions>? setupAction = null)
{
var serviceProvider = services.BuildServiceProvider();
var mvcOptions = serviceProvider.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>();
var mvcSettings = mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName);
// 获取MVC配置选项
var mvcOptions = services.GetPreConfigureActions<AbpAspNetCoreMvcOptions>().Configure();
// 获取并去重远程服务名称
var remoteServiceSettings = mvcOptions.ConventionalControllers
.ConventionalControllerSettings
.DistinctBy(x => x.RemoteServiceName);
services.AddAbpSwaggerGen(
options =>
{
if (action is not null)
options =>
{
action.Invoke(options);
// 应用外部配置
setupAction?.Invoke(options);
// 配置API文档分组
ConfigureApiGroups(options, remoteServiceSettings);
// 配置API文档过滤器
ConfigureApiFilter(options, remoteServiceSettings);
// 配置Schema ID生成规则
options.CustomSchemaIds(type => type.FullName);
// 包含XML注释文档
IncludeXmlComments<TProgram>(options);
// 配置JWT认证
ConfigureJwtAuthentication(options);
// 添加自定义过滤器
ConfigureCustomFilters(options);
}
// 配置分组,还需要去重,支持重写,如果外部传入后,将以外部为准
foreach (var setting in mvcSettings.OrderBy(x => x.RemoteServiceName))
{
if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName))
{
options.SwaggerDoc(setting.RemoteServiceName, new OpenApiInfo { Title = setting.RemoteServiceName, Version = "v1" });
}
}
// 根据分组名称过滤 API 文档
options.DocInclusionPredicate((docName, apiDesc) =>
{
if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
var settingOrNull = mvcSettings.Where(x => x.Assembly == controllerActionDescriptor.ControllerTypeInfo.Assembly).FirstOrDefault();
if (settingOrNull is not null)
{
return docName == settingOrNull.RemoteServiceName;
}
}
return false;
});
options.CustomSchemaIds(type => type.FullName);
var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);
if (basePath is not null)
{
foreach (var item in Directory.GetFiles(basePath, "*.xml"))
{
options.IncludeXmlComments(item, true);
}
}
options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
{
Description = "直接输入Token即可",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer"
});
var scheme = new OpenApiSecurityScheme()
{
Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
};
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
[scheme] = new string[0]
});
options.OperationFilter<AddRequiredHeaderParameter>();
options.SchemaFilter<EnumSchemaFilter>();
}
);
);
return services;
}
/// <summary>
/// 配置API分组
/// </summary>
private static void ConfigureApiGroups(
SwaggerGenOptions options,
IEnumerable<ConventionalControllerSetting> settings)
{
foreach (var setting in settings.OrderBy(x => x.RemoteServiceName))
{
if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName))
{
options.SwaggerDoc(setting.RemoteServiceName, new OpenApiInfo
{
Title = setting.RemoteServiceName,
Version = "v1"
});
}
}
}
/// <summary>
/// 配置API文档过滤器
/// </summary>
private static void ConfigureApiFilter(
SwaggerGenOptions options,
IEnumerable<ConventionalControllerSetting> settings)
{
options.DocInclusionPredicate((docName, apiDesc) =>
{
if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerDesc)
{
var matchedSetting = settings
.FirstOrDefault(x => x.Assembly == controllerDesc.ControllerTypeInfo.Assembly);
return matchedSetting?.RemoteServiceName == docName;
}
return false;
});
}
/// <summary>
/// 包含XML注释文档
/// </summary>
private static void IncludeXmlComments<TProgram>(SwaggerGenOptions options)
{
var basePath = Path.GetDirectoryName(typeof(TProgram).Assembly.Location);
if (basePath is not null)
{
foreach (var xmlFile in Directory.GetFiles(basePath, "*.xml"))
{
options.IncludeXmlComments(xmlFile, true);
}
}
}
/// <summary>
/// 配置JWT认证
/// </summary>
private static void ConfigureJwtAuthentication(SwaggerGenOptions options)
{
options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme
{
Description = "请在此输入JWT Token",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer"
});
var scheme = new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "JwtBearer"
}
};
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
[scheme] = Array.Empty<string>()
});
}
/// <summary>
/// 配置自定义过滤器
/// </summary>
private static void ConfigureCustomFilters(SwaggerGenOptions options)
{
options.OperationFilter<TenantHeaderOperationFilter>();
options.SchemaFilter<EnumSchemaFilter>();
}
}
/// <summary>
/// Swagger文档枚举字段显示枚举属性和枚举值,以及枚举描述
/// Swagger文档枚举字段显示过滤器
/// </summary>
public class EnumSchemaFilter : ISchemaFilter
{
/// <summary>
/// 实现接口
/// 应用枚举架构过滤器
/// </summary>
/// <param name="model"></param>
/// <param name="context"></param>
public void Apply(OpenApiSchema model, SchemaFilterContext context)
/// <param name="schema">OpenAPI架构</param>
/// <param name="context">架构过滤器上下文</param>
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.Type.IsEnum)
{
model.Enum.Clear();
model.Type = "string";
model.Format = null;
if (!context.Type.IsEnum) return;
StringBuilder stringBuilder = new StringBuilder();
Enum.GetNames(context.Type)
.ToList()
.ForEach(name =>
{
Enum e = (Enum)Enum.Parse(context.Type, name);
var descrptionOrNull = GetEnumDescription(e);
model.Enum.Add(new OpenApiString(name));
stringBuilder.Append($"【枚举:{name}{(descrptionOrNull is null ? string.Empty : $"({descrptionOrNull})")}={Convert.ToInt64(Enum.Parse(context.Type, name))}】<br />");
});
model.Description= stringBuilder.ToString();
schema.Enum.Clear();
schema.Type = "string";
schema.Format = null;
var enumDescriptions = new StringBuilder();
foreach (var enumName in Enum.GetNames(context.Type))
{
var enumValue = (Enum)Enum.Parse(context.Type, enumName);
var description = GetEnumDescription(enumValue);
var enumIntValue = Convert.ToInt64(enumValue);
schema.Enum.Add(new OpenApiString(enumName));
enumDescriptions.AppendLine(
$"【枚举:{enumName}{(description is null ? string.Empty : $"({description})")}={enumIntValue}】");
}
schema.Description = enumDescriptions.ToString();
}
/// <summary>
/// 获取枚举描述特性值
/// </summary>
private static string? GetEnumDescription(Enum value)
{
var fieldInfo = value.GetType().GetField(value.ToString());
var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : null;
}
}
public class AddRequiredHeaderParameter : IOperationFilter
/// <summary>
/// 租户头部参数过滤器
/// </summary>
public class TenantHeaderOperationFilter : IOperationFilter
{
public static string HeaderKey { get; set; } = "__tenant";
/// <summary>
/// 租户标识键名
/// </summary>
private const string TenantHeaderKey = "__tenant";
/// <summary>
/// 应用租户头部参数过滤器
/// </summary>
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
operation.Parameters ??= new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = HeaderKey,
Name = TenantHeaderKey,
In = ParameterLocation.Header,
Required = false,
AllowEmptyValue = true,
Description="租户id或者租户名称(可空为默认租户)"
Description = "租户ID或租户名称(留空表示默认租户)"
});
}
}
}
}

View File

@@ -9,65 +9,129 @@ using Volo.Abp.Reflection;
namespace Yi.Framework.AspNetCore.Mvc
{
/// <summary>
/// 自定义路由构建器用于生成API路由规则
/// </summary>
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
[ExposeServices(typeof(IConventionalRouteBuilder))]
public class YiConventionalRouteBuilder : ConventionalRouteBuilder
{
public YiConventionalRouteBuilder(IOptions<AbpConventionalControllerOptions> options) : base(options)
/// <summary>
/// 构造函数
/// </summary>
/// <param name="options">ABP约定控制器配置选项</param>
public YiConventionalRouteBuilder(IOptions<AbpConventionalControllerOptions> options)
: base(options)
{
}
/// <summary>
/// 构建API路由
/// </summary>
/// <param name="rootPath">根路径</param>
/// <param name="controllerName">控制器名称</param>
/// <param name="action">Action模型</param>
/// <param name="httpMethod">HTTP方法</param>
/// <param name="configuration">控制器配置</param>
/// <returns>构建的路由URL</returns>
public override string Build(
string rootPath,
string controllerName,
ActionModel action,
string httpMethod,
[CanBeNull] ConventionalControllerSetting configuration)
string rootPath,
string controllerName,
ActionModel action,
string httpMethod,
[CanBeNull] ConventionalControllerSetting configuration)
{
// 获取API路由前缀
var apiRoutePrefix = GetApiRoutePrefix(action, configuration);
var controllerNameInUrl =
NormalizeUrlControllerName(rootPath, controllerName, action, httpMethod, configuration);
// 规范化控制器名称
var normalizedControllerName = NormalizeUrlControllerName(
rootPath,
controllerName,
action,
httpMethod,
configuration);
var url = $"{rootPath}/{NormalizeControllerNameCase(controllerNameInUrl, configuration)}";
// 构建基础URL
var url = $"{rootPath}/{NormalizeControllerNameCase(normalizedControllerName, configuration)}";
//Add {id} path if needed
var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id");
if (idParameterModel != null)
{
if (TypeHelper.IsPrimitiveExtended(idParameterModel.ParameterType, includeEnums: true))
{
url += "/{id}";
}
else
{
var properties = idParameterModel
.ParameterType
.GetProperties(BindingFlags.Instance | BindingFlags.Public);
// 处理ID参数路由
url = BuildIdParameterRoute(url, action, configuration);
foreach (var property in properties)
{
url += "/{" + NormalizeIdPropertyNameCase(property, configuration) + "}";
}
}
}
//Add action name if needed
var actionNameInUrl = NormalizeUrlActionName(rootPath, controllerName, action, httpMethod, configuration);
if (!actionNameInUrl.IsNullOrEmpty())
{
url += $"/{NormalizeActionNameCase(actionNameInUrl, configuration)}";
//Add secondary Id
var secondaryIds = action.Parameters
.Where(p => p.ParameterName.EndsWith("Id", StringComparison.Ordinal)).ToList();
if (secondaryIds.Count == 1)
{
url += $"/{{{NormalizeSecondaryIdNameCase(secondaryIds[0], configuration)}}}";
}
}
// 处理Action名称路由
url = BuildActionNameRoute(url, rootPath, controllerName, action, httpMethod, configuration);
return url;
}
/// <summary>
/// 构建ID参数路由部分
/// </summary>
private string BuildIdParameterRoute(
string baseUrl,
ActionModel action,
ConventionalControllerSetting configuration)
{
var idParameter = action.Parameters.FirstOrDefault(p => p.ParameterName == "id");
if (idParameter == null)
{
return baseUrl;
}
// 处理原始类型ID
if (TypeHelper.IsPrimitiveExtended(idParameter.ParameterType, includeEnums: true))
{
return $"{baseUrl}/{{id}}";
}
// 处理复杂类型ID
var properties = idParameter.ParameterType
.GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var property in properties)
{
baseUrl += $"/{{{NormalizeIdPropertyNameCase(property, configuration)}}}";
}
return baseUrl;
}
/// <summary>
/// 构建Action名称路由部分
/// </summary>
private string BuildActionNameRoute(
string baseUrl,
string rootPath,
string controllerName,
ActionModel action,
string httpMethod,
ConventionalControllerSetting configuration)
{
var actionNameInUrl = NormalizeUrlActionName(
rootPath,
controllerName,
action,
httpMethod,
configuration);
if (actionNameInUrl.IsNullOrEmpty())
{
return baseUrl;
}
baseUrl += $"/{NormalizeActionNameCase(actionNameInUrl, configuration)}";
// 处理次要ID参数
var secondaryIds = action.Parameters
.Where(p => p.ParameterName.EndsWith("Id", StringComparison.Ordinal))
.ToList();
if (secondaryIds.Count == 1)
{
baseUrl += $"/{{{NormalizeSecondaryIdNameCase(secondaryIds[0], configuration)}}}";
}
return baseUrl;
}
}
}

View File

@@ -13,24 +13,46 @@ using Volo.Abp.Reflection;
namespace Yi.Framework.AspNetCore.Mvc
{
/// <summary>
/// 自定义服务约定实现,用于处理API路由和HTTP方法约束
/// </summary>
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
[ExposeServices(typeof(IAbpServiceConvention))]
public class YiServiceConvention : AbpServiceConvention
{
public YiServiceConvention(IOptions<AbpAspNetCoreMvcOptions> options, IConventionalRouteBuilder conventionalRouteBuilder) : base(options, conventionalRouteBuilder)
/// <summary>
/// 初始化服务约定的新实例
/// </summary>
/// <param name="options">ABP AspNetCore MVC 配置选项</param>
/// <param name="conventionalRouteBuilder">约定路由构建器</param>
public YiServiceConvention(
IOptions<AbpAspNetCoreMvcOptions> options,
IConventionalRouteBuilder conventionalRouteBuilder)
: base(options, conventionalRouteBuilder)
{
}
protected override void ConfigureSelector(string rootPath, string controllerName, ActionModel action, ConventionalControllerSetting? configuration)
/// <summary>
/// 配置选择器,处理路由和HTTP方法约束
/// </summary>
protected override void ConfigureSelector(
string rootPath,
string controllerName,
ActionModel action,
ConventionalControllerSetting? configuration)
{
// 移除空选择器
RemoveEmptySelectors(action.Selectors);
var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(action.ActionMethod);
if (remoteServiceAtt != null && !remoteServiceAtt.IsEnabledFor(action.ActionMethod))
// 检查远程服务特性
var remoteServiceAttr = ReflectionHelper
.GetSingleAttributeOrDefault<RemoteServiceAttribute>(action.ActionMethod);
if (remoteServiceAttr != null && !remoteServiceAttr.IsEnabledFor(action.ActionMethod))
{
return;
}
// 根据选择器是否存在执行不同的配置
if (!action.Selectors.Any())
{
AddAbpServiceSelector(rootPath, controllerName, action, configuration);
@@ -41,56 +63,92 @@ namespace Yi.Framework.AspNetCore.Mvc
}
}
protected override void AddAbpServiceSelector(string rootPath, string controllerName, ActionModel action, ConventionalControllerSetting? configuration)
{
base.AddAbpServiceSelector(rootPath, controllerName, action, configuration);
}
protected override void NormalizeSelectorRoutes(string rootPath, string controllerName, ActionModel action, ConventionalControllerSetting? configuration)
/// <summary>
/// 规范化选择器路由
/// </summary>
protected override void NormalizeSelectorRoutes(
string rootPath,
string controllerName,
ActionModel action,
ConventionalControllerSetting? configuration)
{
foreach (var selector in action.Selectors)
{
var httpMethod = selector.ActionConstraints
.OfType<HttpMethodActionConstraint>()
.FirstOrDefault()?
.HttpMethods?
.FirstOrDefault();
if (httpMethod == null)
{
httpMethod = SelectHttpMethod(action, configuration);
}
if (selector.AttributeRouteModel == null)
{
selector.AttributeRouteModel = CreateAbpServiceAttributeRouteModel(rootPath, controllerName, action, httpMethod, configuration);
}
else
{
var template = selector.AttributeRouteModel.Template;
if (!template.StartsWith("/"))
{
var route = $"{rootPath}/{template}";
selector.AttributeRouteModel.Template = route;
}
}
if (!selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Any())
{
selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { httpMethod }));
}
// 获取HTTP方法约束
var httpMethod = GetOrCreateHttpMethod(selector, action, configuration);
// 处理路由模板
ConfigureRouteTemplate(selector, rootPath, controllerName, action, httpMethod, configuration);
// 确保HTTP方法约束存在
EnsureHttpMethodConstraint(selector, httpMethod);
}
}
/// <summary>
/// 获取或创建HTTP方法
/// </summary>
private string GetOrCreateHttpMethod(
SelectorModel selector,
ActionModel action,
ConventionalControllerSetting? configuration)
{
return selector.ActionConstraints
.OfType<HttpMethodActionConstraint>()
.FirstOrDefault()?
.HttpMethods?
.FirstOrDefault()
?? SelectHttpMethod(action, configuration);
}
/// <summary>
/// 配置路由模板
/// </summary>
private void ConfigureRouteTemplate(
SelectorModel selector,
string rootPath,
string controllerName,
ActionModel action,
string httpMethod,
ConventionalControllerSetting? configuration)
{
if (selector.AttributeRouteModel == null)
{
selector.AttributeRouteModel = CreateAbpServiceAttributeRouteModel(
rootPath,
controllerName,
action,
httpMethod,
configuration);
}
else
{
NormalizeAttributeRouteTemplate(selector, rootPath);
}
}
/// <summary>
/// 规范化特性路由模板
/// </summary>
private void NormalizeAttributeRouteTemplate(SelectorModel selector, string rootPath)
{
var template = selector.AttributeRouteModel.Template;
if (!template.StartsWith("/"))
{
selector.AttributeRouteModel.Template = $"{rootPath}/{template}";
}
}
/// <summary>
/// 确保HTTP方法约束存在
/// </summary>
private void EnsureHttpMethodConstraint(SelectorModel selector, string httpMethod)
{
if (!selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Any())
{
selector.ActionConstraints.Add(
new HttpMethodActionConstraint(new[] { httpMethod }));
}
}
}
}

View File

@@ -5,32 +5,53 @@ using Volo.Abp.AspNetCore.WebClientInfo;
namespace Yi.Framework.AspNetCore;
/// <summary>
/// 真实IP地址提供程序,支持代理服务器场景
/// </summary>
public class RealIpHttpContextWebClientInfoProvider : HttpContextWebClientInfoProvider
{
public RealIpHttpContextWebClientInfoProvider(ILogger<HttpContextWebClientInfoProvider> logger,
IHttpContextAccessor httpContextAccessor) : base(logger, httpContextAccessor)
private const string XForwardedForHeader = "X-Forwarded-For";
/// <summary>
/// 初始化真实IP地址提供程序的新实例
/// </summary>
public RealIpHttpContextWebClientInfoProvider(
ILogger<HttpContextWebClientInfoProvider> logger,
IHttpContextAccessor httpContextAccessor)
: base(logger, httpContextAccessor)
{
}
/// <summary>
/// 获取客户端IP地址,优先从X-Forwarded-For头部获取
/// </summary>
/// <returns>客户端IP地址</returns>
protected override string? GetClientIpAddress()
{
try
{
var httpContext = HttpContextAccessor.HttpContext;
var headers = httpContext?.Request?.Headers;
if (headers != null && headers.ContainsKey("X-Forwarded-For"))
if (httpContext == null)
{
httpContext.Connection.RemoteIpAddress =
IPAddress.Parse(headers["X-Forwarded-For"].FirstOrDefault());
return null;
}
return httpContext?.Connection?.RemoteIpAddress?.ToString();
var headers = httpContext.Request?.Headers;
if (headers != null && headers.ContainsKey(XForwardedForHeader))
{
// 从X-Forwarded-For获取真实客户端IP
var forwardedIp = headers[XForwardedForHeader].FirstOrDefault();
if (!string.IsNullOrEmpty(forwardedIp))
{
httpContext.Connection.RemoteIpAddress = IPAddress.Parse(forwardedIp);
}
}
return httpContext.Connection?.RemoteIpAddress?.ToString();
}
catch (Exception ex)
{
Logger.LogException(ex, LogLevel.Warning);
Logger.LogWarning(ex, "获取客户端IP地址时发生异常");
return null;
}
}

View File

@@ -1,49 +1,55 @@
namespace Yi.Framework.AspNetCore
{
/// <summary>
/// 远程服务成功响应信息
/// </summary>
[Serializable]
public class RemoteServiceSuccessInfo
{
/// <summary>
/// Creates a new instance of <see cref="RemoteServiceSuccessInfo"/>.
/// 获取或设置响应代码
/// </summary>
public string? Code { get; private set; }
/// <summary>
/// 获取或设置响应消息
/// </summary>
public string? Message { get; private set; }
/// <summary>
/// 获取或设置详细信息
/// </summary>
public string? Details { get; private set; }
/// <summary>
/// 获取或设置响应数据
/// </summary>
public object? Data { get; private set; }
/// <summary>
/// 初始化远程服务成功响应信息的新实例
/// </summary>
public RemoteServiceSuccessInfo()
{
}
/// <summary>
/// Creates a new instance of <see cref="RemoteServiceSuccessInfo"/>.
/// 使用指定参数初始化远程服务成功响应信息的新实例
/// </summary>
/// <param name="code">Error code</param>
/// <param name="details">Error details</param>
/// <param name="message">Error message</param>
/// <param name="data">Error data</param>
public RemoteServiceSuccessInfo(string message, string? details = null, string? code = null, object? data = null)
/// <param name="message">响应消息</param>
/// <param name="details">详细信息</param>
/// <param name="code">响应代码</param>
/// <param name="data">响应数据</param>
public RemoteServiceSuccessInfo(
string message,
string? details = null,
string? code = null,
object? data = null)
{
Message = message;
Details = details;
Code = code;
Data = data;
}
/// <summary>
/// code.
/// </summary>
public string? Code { get; set; }
/// <summary>
/// message.
/// </summary>
public string? Message { get; set; }
/// <summary>
/// details.
/// </summary>
public string? Details { get; set; }
/// <summary>
/// data.
/// </summary>
public object? Data { get; set; }
}
}

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

@@ -19,15 +19,24 @@ using Yi.Framework.Core;
namespace Yi.Framework.AspNetCore
{
[DependsOn(typeof(YiFrameworkCoreModule)
)]
/// <summary>
/// Yi框架ASP.NET Core模块
/// </summary>
[DependsOn(typeof(YiFrameworkCoreModule))]
public class YiFrameworkAspNetCoreModule : AbpModule
{
/// <summary>
/// 配置服务后的处理
/// </summary>
public override void PostConfigureServices(ServiceConfigurationContext context)
{
var services = context.Services;
services.Replace(new ServiceDescriptor(typeof(IWebClientInfoProvider),
typeof(RealIpHttpContextWebClientInfoProvider), ServiceLifetime.Transient));
// 替换默认的WebClientInfoProvider为支持代理的实现
services.Replace(new ServiceDescriptor(
typeof(IWebClientInfoProvider),
typeof(RealIpHttpContextWebClientInfoProvider),
ServiceLifetime.Transient));
}
}
}

View File

@@ -0,0 +1,77 @@
using Hangfire.Server;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
namespace Yi.Framework.BackgroundWorkers.Hangfire;
/// <summary>
/// Hangfire 工作单元过滤器
/// 用于管理后台任务的事务处理
/// </summary>
public sealed class UnitOfWorkHangfireFilter : IServerFilter, ISingletonDependency
{
private const string UnitOfWorkItemKey = "HangfireUnitOfWork";
private readonly IUnitOfWorkManager _unitOfWorkManager;
/// <summary>
/// 初始化工作单元过滤器
/// </summary>
/// <param name="unitOfWorkManager">工作单元管理器</param>
public UnitOfWorkHangfireFilter(IUnitOfWorkManager unitOfWorkManager)
{
_unitOfWorkManager = unitOfWorkManager;
}
/// <summary>
/// 任务执行前的处理
/// </summary>
/// <param name="context">执行上下文</param>
public void OnPerforming(PerformingContext context)
{
// 开启一个工作单元并存储到上下文中
var uow = _unitOfWorkManager.Begin();
context.Items.Add(UnitOfWorkItemKey, uow);
}
/// <summary>
/// 任务执行后的处理
/// </summary>
/// <param name="context">执行上下文</param>
public void OnPerformed(PerformedContext context)
{
AsyncHelper.RunSync(() => OnPerformedAsync(context));
}
/// <summary>
/// 任务执行后的异步处理
/// </summary>
/// <param name="context">执行上下文</param>
private async Task OnPerformedAsync(PerformedContext context)
{
if (!context.Items.TryGetValue(UnitOfWorkItemKey, out var obj) ||
obj is not IUnitOfWork uow)
{
return;
}
try
{
// 如果没有异常且工作单元未完成,则提交事务
if (context.Exception == null && !uow.IsCompleted)
{
await uow.CompleteAsync();
}
else
{
// 否则回滚事务
await uow.RollbackAsync();
}
}
finally
{
// 确保工作单元被释放
uow.Dispose();
}
}
}

View File

@@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="Volo.Abp.BackgroundJobs.Hangfire" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.BackgroundWorkers.Hangfire" Version="$(AbpVersion)" />
</ItemGroup>

View File

@@ -1,27 +1,84 @@
using Microsoft.Extensions.DependencyInjection;
using System.Linq.Expressions;
using Hangfire;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundJobs.Hangfire;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.BackgroundWorkers.Hangfire;
using Volo.Abp.DynamicProxy;
namespace Yi.Framework.BackgroundWorkers.Hangfire;
[DependsOn(typeof(AbpBackgroundWorkersHangfireModule))]
public class YiFrameworkBackgroundWorkersHangfireModule:AbpModule
/// <summary>
/// Hangfire 后台任务模块
/// </summary>
[DependsOn(typeof(AbpBackgroundWorkersHangfireModule),
typeof(AbpBackgroundJobsHangfireModule))]
public sealed class YiFrameworkBackgroundWorkersHangfireModule : AbpModule
{
/// <summary>
/// 配置服务前的预处理
/// </summary>
/// <param name="context">服务配置上下文</param>
public override void PreConfigureServices(ServiceConfigurationContext context)
{
// 添加 Hangfire 后台任务约定注册器
context.Services.AddConventionalRegistrar(new YiHangfireConventionalRegistrar());
}
/// <summary>
/// 应用程序初始化
/// </summary>
/// <param name="context">应用程序初始化上下文</param>
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
//定时任务自动注入Abp默认只有在Quartz才实现
// 获取后台任务管理器和所有 Hangfire 后台任务
var backgroundWorkerManager = context.ServiceProvider.GetRequiredService<IBackgroundWorkerManager>();
var works = context.ServiceProvider.GetServices<IHangfireBackgroundWorker>();
foreach (var work in works)
{
await backgroundWorkerManager.AddAsync(work);
}
var workers = context.ServiceProvider.GetServices<IHangfireBackgroundWorker>();
// 获取配置
var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>();
// 检查是否启用 Redis
var isRedisEnabled = configuration.GetValue<bool>("Redis:IsEnabled");
foreach (var worker in workers)
{
// 设置时区为本地时区(上海)
worker.TimeZone = TimeZoneInfo.Local;
if (isRedisEnabled)
{
// Redis 模式:使用 ABP 后台任务管理器
await backgroundWorkerManager.AddAsync(worker);
}
else
{
// 内存模式:直接使用 Hangfire
var unProxyWorker = ProxyHelper.UnProxy(worker);
// 添加或更新循环任务
RecurringJob.AddOrUpdate(
worker.RecurringJobId,
(Expression<Func<Task>>)(() =>
((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(default)),
worker.CronExpression,
new RecurringJobOptions
{
TimeZone = worker.TimeZone
});
}
}
}
/// <summary>
/// 应用程序初始化前的预处理
/// </summary>
/// <param name="context">应用程序初始化上下文</param>
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
{
// 添加工作单元过滤器
var services = context.ServiceProvider;
GlobalJobFilters.Filters.Add(services.GetRequiredService<UnitOfWorkHangfireFilter>());
}
}

View File

@@ -3,18 +3,32 @@ using Volo.Abp.DependencyInjection;
namespace Yi.Framework.BackgroundWorkers.Hangfire;
public class YiHangfireConventionalRegistrar : DefaultConventionalRegistrar
/// <summary>
/// Hangfire 后台任务约定注册器
/// </summary>
public sealed class YiHangfireConventionalRegistrar : DefaultConventionalRegistrar
{
/// <summary>
/// 检查类型是否禁用约定注册
/// </summary>
/// <param name="type">要检查的类型</param>
/// <returns>如果类型不是 IHangfireBackgroundWorker 或已被禁用则返回 true</returns>
protected override bool IsConventionalRegistrationDisabled(Type type)
{
return !typeof(IHangfireBackgroundWorker).IsAssignableFrom(type) || base.IsConventionalRegistrationDisabled(type);
return !typeof(IHangfireBackgroundWorker).IsAssignableFrom(type) ||
base.IsConventionalRegistrationDisabled(type);
}
/// <summary>
/// 获取要暴露的服务类型列表
/// </summary>
/// <param name="type">实现类型</param>
/// <returns>服务类型列表</returns>
protected override List<Type> GetExposedServiceTypes(Type type)
{
return new List<Type>()
{
typeof(IHangfireBackgroundWorker)
};
return new List<Type>
{
typeof(IHangfireBackgroundWorker)
};
}
}

View File

@@ -0,0 +1,148 @@
using Hangfire.Dashboard;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Users;
namespace Yi.Framework.BackgroundWorkers.Hangfire;
/// <summary>
/// Hangfire 仪表盘的令牌认证过滤器
/// </summary>
public sealed class YiTokenAuthorizationFilter : IDashboardAsyncAuthorizationFilter, ITransientDependency
{
private const string BearerPrefix = "Bearer ";
private const string TokenCookieKey = "Token";
private const string HtmlContentType = "text/html";
private readonly IServiceProvider _serviceProvider;
private string _requiredUsername = "cc";
private TimeSpan _tokenExpiration = TimeSpan.FromMinutes(10);
/// <summary>
/// 初始化令牌认证过滤器
/// </summary>
/// <param name="serviceProvider">服务提供者</param>
public YiTokenAuthorizationFilter(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
/// <summary>
/// 设置需要的用户名
/// </summary>
/// <param name="username">允许访问的用户名</param>
/// <returns>当前实例,支持链式调用</returns>
public YiTokenAuthorizationFilter SetRequiredUsername(string username)
{
_requiredUsername = username ?? throw new ArgumentNullException(nameof(username));
return this;
}
/// <summary>
/// 设置令牌过期时间
/// </summary>
/// <param name="expiration">过期时间间隔</param>
/// <returns>当前实例,支持链式调用</returns>
public YiTokenAuthorizationFilter SetTokenExpiration(TimeSpan expiration)
{
_tokenExpiration = expiration;
return this;
}
/// <summary>
/// 授权验证
/// </summary>
/// <param name="context">仪表盘上下文</param>
/// <returns>是否通过授权</returns>
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
var currentUser = _serviceProvider.GetRequiredService<ICurrentUser>();
if (!currentUser.IsAuthenticated)
{
SetChallengeResponse(httpContext);
return false;
}
// 如果验证通过,设置 cookie
var authorization = httpContext.Request.Headers.Authorization.ToString();
if (!string.IsNullOrWhiteSpace(authorization) && authorization.StartsWith(BearerPrefix))
{
var token = authorization[BearerPrefix.Length..];
SetTokenCookie(httpContext, token);
}
return currentUser.UserName == _requiredUsername;
}
/// <summary>
/// 设置认证挑战响应
/// 当用户未认证时返回一个包含令牌输入表单的HTML页面
/// </summary>
/// <param name="httpContext">HTTP 上下文</param>
private void SetChallengeResponse(HttpContext httpContext)
{
httpContext.Response.StatusCode = 401;
httpContext.Response.ContentType = HtmlContentType;
var html = @"
<html>
<head>
<title>Hangfire Dashboard Authorization</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.container { max-width: 400px; margin: 0 auto; }
.form-group { margin-bottom: 15px; }
input[type='text'] { width: 100%; padding: 8px; }
button { background: #337ab7; color: white; border: none; padding: 10px 15px; cursor: pointer; }
button:hover { background: #286090; }
</style>
</head>
<body>
<div class='container'>
<h2>Authorization Required</h2>
<div class='form-group'>
<input type='text' id='token' placeholder='Enter your Bearer token...' />
</div>
<button onclick='authorize()'>Authorize</button>
</div>
<script>
function authorize() {
var token = document.getElementById('token').value;
if (token) {
document.cookie = 'Token=' + token + '; path=/';
window.location.reload();
}
}
</script>
</body>
</html>";
httpContext.Response.WriteAsync(html);
}
/// <summary>
/// 设置令牌 Cookie
/// </summary>
/// <param name="httpContext">HTTP 上下文</param>
/// <param name="token">令牌值</param>
private void SetTokenCookie(HttpContext httpContext, string token)
{
var cookieOptions = new CookieOptions
{
Expires = DateTimeOffset.Now.Add(_tokenExpiration),
HttpOnly = true,
Secure = httpContext.Request.IsHttps,
SameSite = SameSiteMode.Lax
};
httpContext.Response.Cookies.Append(TokenCookieKey, token, cookieOptions);
}
public Task<bool> AuthorizeAsync(DashboardContext context)
{
return Task.FromResult(Authorize(context));
}
}

View File

@@ -10,28 +10,43 @@ using Volo.Abp.MultiTenancy;
namespace Yi.Framework.Caching.FreeRedis
{
[Dependency(ReplaceServices =true)]
/// <summary>
/// 缓存键标准化处理器
/// 用于处理缓存键的格式化和多租户支持
/// </summary>
[Dependency(ReplaceServices = true)]
public class YiDistributedCacheKeyNormalizer : IDistributedCacheKeyNormalizer, ITransientDependency
{
protected ICurrentTenant CurrentTenant { get; }
protected AbpDistributedCacheOptions DistributedCacheOptions { get; }
private readonly ICurrentTenant _currentTenant;
private readonly AbpDistributedCacheOptions _distributedCacheOptions;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="currentTenant">当前租户服务</param>
/// <param name="distributedCacheOptions">分布式缓存配置选项</param>
public YiDistributedCacheKeyNormalizer(
ICurrentTenant currentTenant,
IOptions<AbpDistributedCacheOptions> distributedCacheOptions)
{
CurrentTenant = currentTenant;
DistributedCacheOptions = distributedCacheOptions.Value;
_currentTenant = currentTenant;
_distributedCacheOptions = distributedCacheOptions.Value;
}
/// <summary>
/// 标准化缓存键
/// </summary>
/// <param name="args">缓存键标准化参数</param>
/// <returns>标准化后的缓存键</returns>
public virtual string NormalizeKey(DistributedCacheKeyNormalizeArgs args)
{
var normalizedKey = $"{DistributedCacheOptions.KeyPrefix}{args.Key}";
// 添加全局缓存前缀
var normalizedKey = $"{_distributedCacheOptions.KeyPrefix}{args.Key}";
//if (!args.IgnoreMultiTenancy && CurrentTenant.Id.HasValue)
//todo 多租户支持已注释,如需启用取消注释即可
//if (!args.IgnoreMultiTenancy && _currentTenant.Id.HasValue)
//{
// normalizedKey = $"t:{CurrentTenant.Id.Value},{normalizedKey}";
// normalizedKey = $"t:{_currentTenant.Id.Value},{normalizedKey}";
//}
return normalizedKey;

View File

@@ -1,5 +1,6 @@
using FreeRedis;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Caching;
@@ -7,26 +8,57 @@ using Volo.Abp.Caching;
namespace Yi.Framework.Caching.FreeRedis
{
/// <summary>
/// 此模块得益于FreeRedis作者支持IDistributedCache使用湿滑
/// FreeRedis缓存模块
/// 提供基于FreeRedis的分布式缓存实现
/// </summary>
[DependsOn(typeof(AbpCachingModule))]
public class YiFrameworkCachingFreeRedisModule : AbpModule
{
private const string RedisEnabledKey = "Redis:IsEnabled";
private const string RedisConfigurationKey = "Redis:Configuration";
/// <summary>
/// 配置服务
/// </summary>
/// <param name="context">服务配置上下文</param>
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var redisEnabled = configuration["Redis:IsEnabled"];
if (redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled))
// 检查Redis是否启用
if (!IsRedisEnabled(configuration))
{
var redisConfiguration = configuration["Redis:Configuration"];
RedisClient redisClient = new RedisClient(redisConfiguration);
context.Services.AddSingleton<IRedisClient>(redisClient);
context.Services.Replace(ServiceDescriptor.Singleton<IDistributedCache>(new
DistributedCache(redisClient)));
return;
}
// 注册Redis服务
RegisterRedisServices(context, configuration);
}
/// <summary>
/// 检查Redis是否启用
/// </summary>
/// <param name="configuration">配置</param>
/// <returns>是否启用Redis</returns>
private static bool IsRedisEnabled(IConfiguration configuration)
{
var redisEnabled = configuration[RedisEnabledKey];
return redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled);
}
/// <summary>
/// 注册Redis相关服务
/// </summary>
/// <param name="context">服务配置上下文</param>
/// <param name="configuration">配置</param>
private static void RegisterRedisServices(ServiceConfigurationContext context, IConfiguration configuration)
{
var redisConfiguration = configuration[RedisConfigurationKey];
var redisClient = new RedisClient(redisConfiguration);
context.Services.AddSingleton<IRedisClient>(redisClient);
context.Services.Replace(ServiceDescriptor.Singleton<IDistributedCache>(
new DistributedCache(redisClient)));
}
}
}

View File

@@ -6,8 +6,21 @@ using System.Threading.Tasks;
namespace Yi.Framework.Core.Data
{
/// <summary>
/// 排序接口
/// </summary>
/// <remarks>
/// 实现此接口的实体类将支持排序功能
/// 通常用于列表数据的展示顺序控制
/// </remarks>
public interface IOrderNum
{
/// <summary>
/// 排序号
/// </summary>
/// <remarks>
/// 数字越小越靠前,默认为0
/// </remarks>
int OrderNum { get; set; }
}
}

View File

@@ -6,8 +6,21 @@ using System.Threading.Tasks;
namespace Yi.Framework.Core.Data
{
/// <summary>
/// 状态接口
/// </summary>
/// <remarks>
/// 实现此接口的实体类将支持启用/禁用状态管理
/// 用于控制数据记录的可用状态
/// </remarks>
public interface IState
{
public bool State { get; set; }
/// <summary>
/// 状态标识
/// </summary>
/// <remarks>
/// true表示启用,false表示禁用
/// </remarks>
bool State { get; set; }
}
}

View File

@@ -7,14 +7,37 @@ using System.Threading.Tasks;
namespace Yi.Framework.Core.Enums
{
/// <summary>
/// 定义公共文件路径
/// 文件类型枚举
/// </summary>
/// <remarks>
/// 用于定义系统支持的文件类型分类
/// 主要用于文件上传和存储时的类型区分
/// </remarks>
public enum FileTypeEnum
{
File,
Image,
Thumbnail,
Excel,
Temp
/// <summary>
/// 普通文件
/// </summary>
file = 0,
/// <summary>
/// 图片文件
/// </summary>
image = 1,
/// <summary>
/// 缩略图文件
/// </summary>
thumbnail = 2,
/// <summary>
/// Excel文件
/// </summary>
excel = 3,
/// <summary>
/// 临时文件
/// </summary>
temp = 4
}
}

View File

@@ -6,9 +6,23 @@ using System.Threading.Tasks;
namespace Yi.Framework.Core.Enums
{
/// <summary>
/// 排序方向枚举
/// </summary>
/// <remarks>
/// 用于定义数据查询时的排序方向
/// 常用于列表数据排序
/// </remarks>
public enum OrderByEnum
{
Asc,
Desc
/// <summary>
/// 升序排列
/// </summary>
Asc = 0,
/// <summary>
/// 降序排列
/// </summary>
Desc = 1
}
}

View File

@@ -6,67 +6,91 @@ using System.Threading.Tasks;
namespace Yi.Framework.Core.Enums
{
/// <summary>
/// 查询操作符枚举
/// </summary>
/// <remarks>
/// 定义查询条件中支持的操作符类型
/// 用于构建动态查询条件
/// </remarks>
public enum QueryOperatorEnum
{
/// <summary>
///
/// 等
/// </summary>
Equal,
Equal = 0,
/// <summary>
/// 匹配
/// 模糊匹配
/// </summary>
Like,
Like = 1,
/// <summary>
/// 大于
/// </summary>
GreaterThan,
GreaterThan = 2,
/// <summary>
/// 大于或等于
/// </summary>
GreaterThanOrEqual,
GreaterThanOrEqual = 3,
/// <summary>
/// 小于
/// </summary>
LessThan,
LessThan = 4,
/// <summary>
/// 小于或等于
/// </summary>
LessThanOrEqual,
LessThanOrEqual = 5,
/// <summary>
/// 等于集合
/// 在指定集合
/// </summary>
In,
In = 6,
/// <summary>
/// 不等于集合
/// 不在指定集合
/// </summary>
NotIn,
NotIn = 7,
/// <summary>
/// 左匹配
/// 左侧模糊匹配
/// </summary>
LikeLeft,
LikeLeft = 8,
/// <summary>
/// 右匹配
/// 右侧模糊匹配
/// </summary>
LikeRight,
LikeRight = 9,
/// <summary>
/// 不
/// 不等
/// </summary>
NoEqual,
NoEqual = 10,
/// <summary>
/// 为或空
/// 为null或空
/// </summary>
IsNullOrEmpty,
IsNullOrEmpty = 11,
/// <summary>
/// 不为
/// 不为null
/// </summary>
IsNot,
IsNot = 12,
/// <summary>
/// 不匹配
/// </summary>
NoLike,
NoLike = 13,
/// <summary>
/// 时间段 值用 "|" 隔开
/// 日期范围
/// </summary>
DateRange
/// <remarks>
/// 使用"|"分隔起始和结束日期
/// </remarks>
DateRange = 14
}
}

View File

@@ -6,26 +6,33 @@ using System.Threading.Tasks;
namespace Yi.Framework.Core.Enums
{
/// <summary>
/// API返回状态码枚举
/// </summary>
/// <remarks>
/// 定义API接口统一的返回状态码
/// 遵循HTTP状态码规范
/// </remarks>
public enum ResultCodeEnum
{
/// <summary>
/// 操作成功
/// 操作成功
/// </summary>
Success = 200,
/// <summary>
/// 操作不成功
/// </summary>
NotSuccess = 500,
/// <summary>
/// 无权限
/// 未授权访问
/// </summary>
NoPermission = 401,
/// <summary>
/// 被拒绝
/// 访问被拒绝
/// </summary>
Denied = 403
Denied = 403,
/// <summary>
/// 操作失败
/// </summary>
NotSuccess = 500
}
}

View File

@@ -4,110 +4,131 @@ using Microsoft.AspNetCore.Http;
namespace Yi.Framework.Core.Extensions
{
/// <summary>
/// HttpContext扩展方法类
/// </summary>
public static class HttpContextExtensions
{
/// <summary>
/// 设置文件下载名称
/// 设置内联文件下载响应头
/// </summary>
/// <param name="httpContext"></param>
/// <param name="fileName"></param>
/// <param name="httpContext">HTTP上下文</param>
/// <param name="fileName">文件名</param>
public static void FileInlineHandle(this HttpContext httpContext, string fileName)
{
string encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));
httpContext.Response.Headers.Add("Content-Disposition", "inline;filename=" + encodeFilename);
var encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.UTF8);
httpContext.Response.Headers.Add("Content-Disposition", $"inline;filename={encodeFilename}");
}
/// <summary>
/// 设置文件附件名称
/// 设置附件下载响应头
/// </summary>
/// <param name="httpContext"></param>
/// <param name="fileName"></param>
/// <param name="httpContext">HTTP上下文</param>
/// <param name="fileName">文件名</param>
public static void FileAttachmentHandle(this HttpContext httpContext, string fileName)
{
string encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));
httpContext.Response.Headers.Add("Content-Disposition", "attachment;filename=" + encodeFilename);
var encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.UTF8);
httpContext.Response.Headers.Add("Content-Disposition", $"attachment;filename={encodeFilename}");
}
/// <summary>
/// 获取语言种类
/// 获取客户端首选语言
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
/// <param name="httpContext">HTTP上下文</param>
/// <returns>语言代码,默认返回zh-CN</returns>
public static string GetLanguage(this HttpContext httpContext)
{
string res = "zh-CN";
var str = httpContext.Request.Headers["Accept-Language"].FirstOrDefault();
if (str is not null)
{
res = str.Split(",")[0];
}
return res;
const string defaultLanguage = "zh-CN";
var acceptLanguage = httpContext.Request.Headers["Accept-Language"].FirstOrDefault();
return string.IsNullOrEmpty(acceptLanguage)
? defaultLanguage
: acceptLanguage.Split(',')[0];
}
/// <summary>
/// 判断是否为异步请求
/// 判断是否为Ajax请求
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
/// <param name="request">HTTP请求</param>
/// <returns>是否为Ajax请求</returns>
public static bool IsAjaxRequest(this HttpRequest request)
{
string header = request.Headers["X-Requested-With"];
return "XMLHttpRequest".Equals(header);
const string ajaxHeader = "XMLHttpRequest";
return ajaxHeader.Equals(request.Headers["X-Requested-With"],
StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// 获取客户端IP
/// 获取客户端IP地址
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
/// <param name="context">HTTP上下文</param>
/// <returns>客户端IP地址</returns>
public static string GetClientIp(this HttpContext context)
{
if (context == null) return "";
var result = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (string.IsNullOrEmpty(result))
const string localhost = "127.0.0.1";
if (context == null) return string.Empty;
// 尝试获取X-Forwarded-For头
var ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
// 如果没有代理头,则获取远程IP
if (string.IsNullOrEmpty(ip))
{
result = context.Connection.RemoteIpAddress?.ToString();
ip = context.Connection.RemoteIpAddress?.ToString();
}
if (string.IsNullOrEmpty(result) || result.Contains("::1"))
result = "127.0.0.1";
result = result.Replace("::ffff:", "127.0.0.1");
//如果有端口号,删除端口号
result = Regex.Replace(result, @":\d{1,5}$", "");
//Ip规则校验
var regResult =
Regex.IsMatch(result, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$")
|| Regex.IsMatch(result, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?):\d{1,5}$");
// 处理特殊IP
if (string.IsNullOrEmpty(ip) || ip.Contains("::1"))
{
return localhost;
}
result = regResult ? result : "127.0.0.1";
return result;
// 清理IPv6格式
ip = ip.Replace("::ffff:", localhost);
// 移除端口号
ip = Regex.Replace(ip, @":\d{1,5}$", "");
// 验证IP格式
var isValidIp = Regex.IsMatch(ip, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$") ||
Regex.IsMatch(ip, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?):\d{1,5}$");
return isValidIp ? ip : localhost;
}
/// <summary>
/// 获取浏览器标识
/// 获取User-Agent信息
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
/// <param name="context">HTTP上下文</param>
/// <returns>User-Agent字符串</returns>
public static string GetUserAgent(this HttpContext context)
{
return context.Request.Headers["User-Agent"];
return context.Request.Headers["User-Agent"].ToString();
}
/// <summary>
/// 获取用户权限声明值
/// </summary>
/// <param name="context">HTTP上下文</param>
/// <param name="permissionsName">权限声明名称</param>
/// <returns>权限值数组</returns>
public static string[]? GetUserPermissions(this HttpContext context, string permissionsName)
{
return context.User.Claims.Where(x => x.Type == permissionsName).Select(x => x.Value).ToArray();
return context.User.Claims
.Where(x => x.Type == permissionsName)
.Select(x => x.Value)
.ToArray();
}
/// <summary>
/// 判断是否WebSocket 请求
/// 判断是否WebSocket请求
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
/// <param name="context">HTTP上下文</param>
/// <returns>是否为WebSocket请求</returns>
public static bool IsWebSocketRequest(this HttpContext context)
{
return context.WebSockets.IsWebSocketRequest || context.Request.Path == "/ws";
return context.WebSockets.IsWebSocketRequest ||
context.Request.Path == "/ws";
}
}
}

View File

@@ -45,8 +45,8 @@ namespace Yi.Framework.Core.Helper
{
var extension = Path.GetExtension(fileName);
if (ImageType.Contains(extension.ToLower()))
return FileTypeEnum.Image;
return FileTypeEnum.File;
return FileTypeEnum.image;
return FileTypeEnum.file;
}

View File

@@ -3,25 +3,48 @@ using System.Text.Json.Serialization;
namespace Yi.Framework.Core.Json;
/// <summary>
/// DateTime JSON序列化转换器
/// </summary>
public class DatetimeJsonConverter : JsonConverter<DateTime>
{
private string _format;
public DatetimeJsonConverter(string format="yyyy-MM-dd HH:mm:ss")
private readonly string _dateFormat;
/// <summary>
/// 初始化DateTime转换器
/// </summary>
/// <param name="format">日期格式化字符串,默认为yyyy-MM-dd HH:mm:ss</param>
public DatetimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{
_format = format;
_dateFormat = format;
}
/// <summary>
/// 从JSON读取DateTime值
/// </summary>
/// <param name="reader">JSON读取器</param>
/// <param name="typeToConvert">目标类型</param>
/// <param name="options">JSON序列化选项</param>
/// <returns>DateTime值</returns>
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if(reader.TokenType==JsonTokenType.String)
if (reader.TokenType == JsonTokenType.String)
{
if (DateTime.TryParse(reader.GetString(), out DateTime dateTime)) return dateTime;
return DateTime.TryParse(reader.GetString(), out DateTime dateTime)
? dateTime
: reader.GetDateTime();
}
return reader.GetDateTime();
}
/// <summary>
/// 将DateTime写入JSON
/// </summary>
/// <param name="writer">JSON写入器</param>
/// <param name="value">DateTime值</param>
/// <param name="options">JSON序列化选项</param>
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(_format));
writer.WriteStringValue(value.ToString(_dateFormat));
}
}

View File

@@ -8,52 +8,81 @@ using Volo.Abp.Modularity;
namespace Yi.Framework.Core.Modularity;
[Dependency(ReplaceServices =true)]
/// <summary>
/// Yi框架模块管理器
/// </summary>
[Dependency(ReplaceServices = true)]
public class YiModuleManager : ModuleManager, IModuleManager, ISingletonDependency
{
private readonly IModuleContainer _moduleContainer;
private readonly IEnumerable<IModuleLifecycleContributor> _lifecycleContributors;
private readonly ILogger<YiModuleManager> _logger;
public YiModuleManager(IModuleContainer moduleContainer, ILogger<YiModuleManager> logger, IOptions<AbpModuleLifecycleOptions> options, IServiceProvider serviceProvider) : base(moduleContainer, logger, options, serviceProvider)
/// <summary>
/// 初始化模块管理器
/// </summary>
public YiModuleManager(
IModuleContainer moduleContainer,
ILogger<YiModuleManager> logger,
IOptions<AbpModuleLifecycleOptions> options,
IServiceProvider serviceProvider)
: base(moduleContainer, logger, options, serviceProvider)
{
_moduleContainer = moduleContainer;
_logger = logger;
_lifecycleContributors = options.Value.Contributors.Select(serviceProvider.GetRequiredService).Cast<IModuleLifecycleContributor>().ToArray();
_lifecycleContributors = options.Value.Contributors
.Select(serviceProvider.GetRequiredService)
.Cast<IModuleLifecycleContributor>()
.ToArray();
}
/// <summary>
/// 初始化所有模块
/// </summary>
/// <param name="context">应用程序初始化上下文</param>
public override async Task InitializeModulesAsync(ApplicationInitializationContext context)
{
_logger.LogDebug("==========模块Initialize初始化统计-跳过0ms模块==========");
var total = 0;
var watch =new Stopwatch();
long totalTime = 0;
var moduleCount = 0;
var stopwatch = new Stopwatch();
var totalTime = 0L;
foreach (var contributor in _lifecycleContributors)
{
foreach (var module in _moduleContainer.Modules)
{
try
{
watch.Restart();
stopwatch.Restart();
await contributor.InitializeAsync(context, module.Instance);
watch.Stop();
totalTime += watch.ElapsedMilliseconds;
total++;
if (watch.ElapsedMilliseconds > 1)
stopwatch.Stop();
totalTime += stopwatch.ElapsedMilliseconds;
moduleCount++;
// 仅记录耗时超过1ms的模块
if (stopwatch.ElapsedMilliseconds > 1)
{
_logger.LogDebug($"耗时-{watch.ElapsedMilliseconds}ms,已加载模块-{module.Assembly.GetName().Name}");
_logger.LogDebug(
"耗时-{Time}ms,已加载模块-{ModuleName}",
stopwatch.ElapsedMilliseconds,
module.Assembly.GetName().Name);
}
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during the initialize {contributor.GetType().FullName} phase of the module {module.Type.AssemblyQualifiedName}: {ex.Message}. See the inner exception for details.", ex);
throw new AbpInitializationException(
$"模块 {module.Type.AssemblyQualifiedName} 在 {contributor.GetType().FullName} 阶段初始化失败: {ex.Message}",
ex);
}
}
}
_logger.LogInformation($"==========【{total}】个模块初始化执行完毕,总耗时【{totalTime}ms】==========");
_logger.LogInformation(
"==========【{Count}】个模块初始化执行完毕,总耗时【{Time}ms】==========",
moduleCount,
totalTime);
}
}

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

@@ -2,8 +2,28 @@
namespace Yi.Framework.Core
{
public class YiFrameworkCoreModule:AbpModule
/// <summary>
/// Yi框架核心模块
/// </summary>
/// <remarks>
/// 提供框架的基础功能和核心服务
/// </remarks>
public class YiFrameworkCoreModule : AbpModule
{
/// <summary>
/// 配置服务
/// </summary>
public override void ConfigureServices(ServiceConfigurationContext context)
{
base.ConfigureServices(context);
}
/// <summary>
/// 应用程序初始化
/// </summary>
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
base.OnApplicationInitialization(context);
}
}
}

View File

@@ -3,8 +3,17 @@ using Volo.Abp.Application.Services;
namespace Yi.Framework.Ddd.Application.Contracts
{
public interface IDeletesAppService<in TKey> : IDeleteAppService< TKey> , IApplicationService, IRemoteService
/// <summary>
/// 批量删除服务接口
/// </summary>
/// <typeparam name="TKey">主键类型</typeparam>
public interface IDeletesAppService<in TKey> : IDeleteAppService<TKey>, IApplicationService, IRemoteService
{
/// <summary>
/// 批量删除实体
/// </summary>
/// <param name="ids">要删除的实体ID集合</param>
/// <returns>删除操作的异步任务</returns>
Task DeleteAsync(IEnumerable<TKey> ids);
}
}

View File

@@ -2,9 +2,19 @@
namespace Yi.Framework.Ddd.Application.Contracts
{
/// <summary>
/// 带时间范围的分页查询请求接口
/// </summary>
public interface IPageTimeResultRequestDto : IPagedAndSortedResultRequest
{
/// <summary>
/// 查询开始时间
/// </summary>
DateTime? StartTime { get; set; }
/// <summary>
/// 查询结束时间
/// </summary>
DateTime? EndTime { get; set; }
}
}

View File

@@ -2,6 +2,9 @@
namespace Yi.Framework.Ddd.Application.Contracts
{
/// <summary>
/// 分页查询请求接口,包含时间范围和排序功能
/// </summary>
public interface IPagedAllResultRequestDto : IPageTimeResultRequestDto, IPagedAndSortedResultRequest
{
}

View File

@@ -7,24 +7,47 @@ using Volo.Abp.Application.Services;
namespace Yi.Framework.Ddd.Application.Contracts
{
/// <summary>
/// Yi框架CRUD服务基础接口
/// </summary>
/// <typeparam name="TEntityDto">实体DTO类型</typeparam>
/// <typeparam name="TKey">主键类型</typeparam>
public interface IYiCrudAppService<TEntityDto, in TKey> : ICrudAppService<TEntityDto, TKey>
{
}
/// <summary>
/// Yi框架CRUD服务接口带查询输入
/// </summary>
/// <typeparam name="TEntityDto">实体DTO类型</typeparam>
/// <typeparam name="TKey">主键类型</typeparam>
/// <typeparam name="TGetListInput">查询输入类型</typeparam>
public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput> : ICrudAppService<TEntityDto, TKey, TGetListInput>
{
}
/// <summary>
/// Yi框架CRUD服务接口带查询输入和创建输入
/// </summary>
/// <typeparam name="TEntityDto">实体DTO类型</typeparam>
/// <typeparam name="TKey">主键类型</typeparam>
/// <typeparam name="TGetListInput">查询输入类型</typeparam>
/// <typeparam name="TCreateInput">创建输入类型</typeparam>
public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput> : ICrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput>
{
}
/// <summary>
/// Yi框架CRUD服务接口带查询、创建和更新输入
/// </summary>
public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput> : ICrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
{
}
/// <summary>
/// Yi框架完整CRUD服务接口包含所有操作和批量删除功能
/// </summary>
public interface IYiCrudAppService<TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput> : ICrudAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>, IDeletesAppService<TKey>
{
}
}

View File

@@ -2,48 +2,50 @@
namespace Yi.Framework.Ddd.Application.Contracts
{
/// <summary>
/// 分页查询请求DTO包含时间范围和自定义排序功能
/// </summary>
public class PagedAllResultRequestDto : PagedAndSortedResultRequestDto, IPagedAllResultRequestDto
{
/// <summary>
/// 查询开始时间条件
/// 查询开始时间
/// </summary>
public DateTime? StartTime { get; set; }
/// <summary>
/// 查询结束时间条件
/// 查询结束时间
/// </summary>
public DateTime? EndTime { get; set; }
/// <summary>
/// 排序列名,字段名对应前端
/// 排序列名
/// </summary>
public string? OrderByColumn { get; set; }
/// <summary>
/// 是否顺序,字段名对应前端
/// 排序方向ascending/descending
/// </summary>
public string? IsAsc { get; set; }
/// <summary>
/// 是否
/// 是否为升序排
/// </summary>
public bool CanAsc => IsAsc?.ToLower() == "ascending" ? true : false;
public bool IsAscending => string.Equals(IsAsc, "ascending", StringComparison.OrdinalIgnoreCase);
private string _sorting;
private string? _sorting;
//排序引用
public new string? Sorting
/// <summary>
/// 排序表达式
/// </summary>
public override string? Sorting
{
get
{
if (!OrderByColumn.IsNullOrWhiteSpace())
if (!string.IsNullOrWhiteSpace(OrderByColumn))
{
return $"{OrderByColumn} {(CanAsc ? "ASC" : "DESC")}";
}
else
{
return _sorting;
return $"{OrderByColumn} {(IsAscending ? "ASC" : "DESC")}";
}
return _sorting;
}
set => _sorting = value;
}

View File

@@ -3,6 +3,9 @@ using Volo.Abp.Modularity;
namespace Yi.Framework.Ddd.Application.Contracts
{
/// <summary>
/// Yi框架DDD应用层契约模块
/// </summary>
[DependsOn(typeof(AbpDddApplicationContractsModule))]
public class YiFrameworkDddApplicationContractsModule : AbpModule
{

View File

@@ -6,11 +6,19 @@ using Volo.Abp.MultiTenancy;
namespace Yi.Framework.Ddd.Application
{
public abstract class YiCacheCrudAppService<TEntity, TEntityDto, TKey> : YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
/// <summary>
/// 带缓存的CRUD应用服务基类
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TEntityDto">实体DTO类型</typeparam>
/// <typeparam name="TKey">主键类型</typeparam>
public abstract class YiCacheCrudAppService<TEntity, TEntityDto, TKey>
: YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected YiCacheCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
protected YiCacheCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
}
@@ -47,73 +55,92 @@ namespace Yi.Framework.Ddd.Application
}
/// <summary>
/// 完整的带缓存CRUD应用服务实现
/// </summary>
public abstract class YiCacheCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
where TEntity : class, IEntity<TKey>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
protected IDistributedCache<TEntity> Cache => LazyServiceProvider.LazyGetRequiredService<IDistributedCache<TEntity>>();
/// <summary>
/// 分布式缓存访问器
/// </summary>
private IDistributedCache<TEntity> EntityCache =>
LazyServiceProvider.LazyGetRequiredService<IDistributedCache<TEntity>>();
protected string GetCacheKey(TKey id) => typeof(TEntity).Name + ":" + CurrentTenant.Id ?? Guid.Empty + ":" + id.ToString();
protected YiCacheCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
/// <summary>
/// 获取缓存键
/// </summary>
protected virtual string GenerateCacheKey(TKey id) =>
$"{typeof(TEntity).Name}:{CurrentTenant.Id ?? Guid.Empty}:{id}";
protected YiCacheCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
/// <summary>
/// 更新实体并清除缓存
/// </summary>
public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
{
var output = await base.UpdateAsync(id, input);
await Cache.RemoveAsync(GetCacheKey(id));
return output;
var result = await base.UpdateAsync(id, input);
await EntityCache.RemoveAsync(GenerateCacheKey(id));
return result;
}
public override async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
/// <summary>
/// 获取实体列表(需要继承实现具体的缓存策略)
/// </summary>
public override Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
{
//两种方式:
//1全表缓存使用缓存直接查询
//2非全部缓存查询到的数据直接添加到缓存
//判断是否该实体为全表缓存
throw new NotImplementedException();
//IDistributedCache 有局限性,条件查询无法进行缓存了
//if (true)
//{
// return await GetListByCacheAsync(input);
//}
//else
//{
// return await GetListByDbAsync(input);
//}
// 建议实现两种缓存策略:
// 1. 全表缓存: 适用于数据量小且变动不频繁的场景
// 2. 按需缓存: 仅缓存常用数据,适用于大数据量场景
throw new NotImplementedException("请实现具体的缓存查询策略");
}
protected virtual async Task<PagedResultDto<TGetListOutputDto>> GetListByDbAsync(TGetListInput input)
/// <summary>
/// 从数据库获取实体列表
/// </summary>
protected virtual Task<PagedResultDto<TGetListOutputDto>> GetListFromDatabaseAsync(
TGetListInput input)
{
//如果不是全表缓存,可以走这个啦
throw new NotImplementedException();
}
protected virtual async Task<PagedResultDto<TGetListOutputDto>> GetListByCacheAsync(TGetListInput input)
{
//如果是全表缓存,可以走这个啦
throw new NotImplementedException();
}
/// <summary>
/// 从缓存获取实体列表
/// </summary>
protected virtual Task<PagedResultDto<TGetListOutputDto>> GetListFromCacheAsync(
TGetListInput input)
{
throw new NotImplementedException();
}
/// <summary>
/// 获取单个实体(优先从缓存获取)
/// </summary>
protected override async Task<TEntity> GetEntityByIdAsync(TKey id)
{
var output = await Cache.GetOrAddAsync(GetCacheKey(id), async () => await base.GetEntityByIdAsync(id));
return output!;
return (await EntityCache.GetOrAddAsync(
GenerateCacheKey(id),
async () => await base.GetEntityByIdAsync(id)))!;
}
public override async Task DeleteAsync(IEnumerable<TKey> id)
/// <summary>
/// 批量删除实体并清除缓存
/// </summary>
public override async Task DeleteAsync(IEnumerable<TKey> ids)
{
await base.DeleteAsync(id);
foreach (var itemId in id)
{
await Cache.RemoveAsync(GetCacheKey(itemId));
}
await base.DeleteAsync(ids);
// 批量清除缓存
var tasks = ids.Select(id =>
EntityCache.RemoveAsync(GenerateCacheKey(id)));
await Task.WhenAll(tasks);
}
}
}

View File

@@ -8,183 +8,258 @@ using Volo.Abp.Domain.Repositories;
namespace Yi.Framework.Ddd.Application
{
public abstract class
YiCrudAppService<TEntity, TEntityDto, TKey> : YiCrudAppService<TEntity, TEntityDto, TKey,
PagedAndSortedResultRequestDto>
/// <summary>
/// CRUD应用服务基类 - 基础版本
/// </summary>
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) : base(repository)
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
}
/// <summary>
/// CRUD应用服务基类 - 支持自定义查询输入
/// </summary>
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput>
: YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TEntityDto>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
}
/// <summary>
/// CRUD应用服务基类 - 支持自定义创建输入
/// </summary>
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput>
: YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
}
/// <summary>
/// CRUD应用服务基类 - 支持自定义更新输入
/// </summary>
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: YiCrudAppService<TEntity, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
}
public abstract class YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput,
TUpdateInput>
/// <summary>
/// CRUD应用服务基类 - 完整实现
/// </summary>
public abstract class YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
/// <summary>
/// 临时文件存储路径
/// </summary>
private const string TempFilePath = "/wwwroot/temp";
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
/// <summary>
/// 更新实体
/// </summary>
/// <param name="id">实体ID</param>
/// <param name="input">更新输入</param>
/// <returns>更新后的实体DTO</returns>
public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
{
// 检查更新权限
await CheckUpdatePolicyAsync();
// 获取并验证实体
var entity = await GetEntityByIdAsync(id);
await CheckUpdateInputDtoAsync(entity,input);
// 检查更新输入
await CheckUpdateInputDtoAsync(entity, input);
// 映射并更新实体
await MapToEntityAsync(input, entity);
await Repository.UpdateAsync(entity, autoSave: true);
return await MapToGetOutputDtoAsync(entity);
}
protected virtual Task CheckUpdateInputDtoAsync(TEntity entity,TUpdateInput input)
/// <summary>
/// 检查更新输入数据的有效性
/// </summary>
protected virtual Task CheckUpdateInputDtoAsync(TEntity entity, TUpdateInput input)
{
return Task.CompletedTask;
}
/// <summary>
/// 创建实体
/// </summary>
/// <param name="input">创建输入</param>
/// <returns>创建后的实体DTO</returns>
public override async Task<TGetOutputDto> CreateAsync(TCreateInput input)
{
// 检查创建权限
await CheckCreatePolicyAsync();
// 检查创建输入
await CheckCreateInputDtoAsync(input);
// 映射到实体
var entity = await MapToEntityAsync(input);
// 设置租户ID
TryToSetTenantId(entity);
// 插入实体
await Repository.InsertAsync(entity, autoSave: true);
return await MapToGetOutputDtoAsync(entity);
}
/// <summary>
/// 检查创建输入数据的有效性
/// </summary>
protected virtual Task CheckCreateInputDtoAsync(TCreateInput input)
{
return Task.CompletedTask;
}
/// <summary>
/// 多查
/// 获取实体列表
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <param name="input">查询输入</param>
/// <returns>分页结果</returns>
public override async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
{
List<TEntity>? entites = null;
//区分多查还是批量查
List<TEntity> entities;
// 根据输入类型决定查询方式
if (input is IPagedResultRequest pagedInput)
{
entites = await Repository.GetPagedListAsync(pagedInput.SkipCount, pagedInput.MaxResultCount,
string.Empty);
// 分页查询
entities = await Repository.GetPagedListAsync(
pagedInput.SkipCount,
pagedInput.MaxResultCount,
string.Empty
);
}
else
{
entites = await Repository.GetListAsync();
// 查询全部
entities = await Repository.GetListAsync();
}
var total = await Repository.GetCountAsync();
var output = await MapToGetListOutputDtosAsync(entites);
return new PagedResultDto<TGetListOutputDto>(total, output);
//throw new NotImplementedException($"【{typeof(TEntity)}】实体的CrudAppService查询为具体业务通用查询几乎无实际场景请重写实现");
// 获取总数并映射结果
var totalCount = await Repository.GetCountAsync();
var dtos = await MapToGetListOutputDtosAsync(entities);
return new PagedResultDto<TGetListOutputDto>(totalCount, dtos);
}
/// <summary>
/// 多删
/// 获取实体动态下拉框列表,子类重写该方法,通过 keywords 进行筛选
/// </summary>
/// <param name="id"></param>
/// <param name="keywords">查询关键字</param>
/// <returns></returns>
[RemoteService(isEnabled: true)]
public virtual async Task DeleteAsync(IEnumerable<TKey> id)
public virtual async Task<List<TGetListOutputDto>> GetSelectDataListAsync(string? keywords = null)
{
await Repository.DeleteManyAsync(id);
List<TEntity> entities = await Repository.GetListAsync();
// 获取总数并映射结果
var dtos = await MapToGetListOutputDtosAsync(entities);
return dtos;
}
/// <summary>
/// 偷梁换柱
/// 批量删除实体
/// </summary>
/// <param name="ids">实体ID集合</param>
[RemoteService(isEnabled: true)]
public virtual async Task DeleteAsync(IEnumerable<TKey> ids)
{
await Repository.DeleteManyAsync(ids);
}
/// <summary>
/// 单个删除实体(禁用远程访问)
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[RemoteService(isEnabled: false)]
public override Task DeleteAsync(TKey id)
{
return base.DeleteAsync(id);
}
/// <summary>
/// 导出excel
/// 导出Excel
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <param name="input">查询条件</param>
/// <returns>Excel文件</returns>
public virtual async Task<IActionResult> GetExportExcelAsync(TGetListInput input)
{
// 重置分页参数以获取全部数据
if (input is IPagedResultRequest paged)
{
paged.SkipCount = 0;
paged.MaxResultCount = LimitedResultRequestDto.MaxMaxResultCount;
}
var output = await this.GetListAsync(input);
var dirPath = $"/wwwroot/temp";
// 获取数据
var output = await GetListAsync(input);
var fileName = $"{typeof(TEntity).Name}_{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}_{Guid.NewGuid()}";
var filePath = $"{dirPath}/{fileName}.xlsx";
if (!Directory.Exists(dirPath))
// 确保临时目录存在
if (!Directory.Exists(TempFilePath))
{
Directory.CreateDirectory(dirPath);
Directory.CreateDirectory(TempFilePath);
}
MiniExcel.SaveAs(filePath, output.Items);
// 生成文件名和路径
var fileName = GenerateExcelFileName();
var filePath = Path.Combine(TempFilePath, fileName);
// 保存Excel文件
await MiniExcel.SaveAsAsync(filePath, output.Items);
return new PhysicalFileResult(filePath, "application/vnd.ms-excel");
}
/// <summary>
/// 导入excle
/// 生成Excel文件名
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public virtual async Task PostImportExcelAsync(List<TCreateInput> input)
private string GenerateExcelFileName()
{
var entities = input.Select(x => MapToEntity(x)).ToList();
//安全起见,该接口需要自己实现
throw new NotImplementedException();
//await Repository.DeleteManyAsync(entities.Select(x => x.Id));
//await Repository.InsertManyAsync(entities);
return $"{typeof(TEntity).Name}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{Guid.NewGuid()}.xlsx";
}
/// <summary>
/// 导入Excel(需要实现类重写此方法)
/// </summary>
public virtual Task PostImportExcelAsync(List<TCreateInput> input)
{
throw new NotImplementedException("请在实现类中重写此方法以支持Excel导入");
}
}
}

View File

@@ -6,14 +6,34 @@ using Yi.Framework.Ddd.Application.Contracts;
namespace Yi.Framework.Ddd.Application
{
[DependsOn(typeof(AbpDddApplicationModule),
typeof(YiFrameworkDddApplicationContractsModule))]
/// <summary>
/// Yi框架DDD应用层模块
/// </summary>
[DependsOn(
typeof(AbpDddApplicationModule),
typeof(YiFrameworkDddApplicationContractsModule)
)]
public class YiFrameworkDddApplicationModule : AbpModule
{
/// <summary>
/// 应用程序初始化配置
/// </summary>
/// <param name="context">应用程序初始化上下文</param>
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
//分页限制
// 配置分页查询的默认值和最大值限制
ConfigureDefaultPagingSettings();
}
/// <summary>
/// 配置默认分页设置
/// </summary>
private void ConfigureDefaultPagingSettings()
{
// 设置默认每页显示记录数
LimitedResultRequestDto.DefaultMaxResultCount = 10;
// 设置最大允许的每页记录数
LimitedResultRequestDto.MaxMaxResultCount = 10000;
}
}

View File

@@ -8,17 +8,37 @@ using Volo.Abp.ObjectMapping;
namespace Yi.Framework.Mapster
{
/// <summary>
/// Mapster自动对象映射提供程序
/// 实现IAutoObjectMappingProvider接口提供对象间的自动映射功能
/// </summary>
public class MapsterAutoObjectMappingProvider : IAutoObjectMappingProvider
{
/// <summary>
/// 将源对象映射到目标类型
/// </summary>
/// <typeparam name="TSource">源类型</typeparam>
/// <typeparam name="TDestination">目标类型</typeparam>
/// <param name="source">源对象</param>
/// <returns>映射后的目标类型实例</returns>
public TDestination Map<TSource, TDestination>(object source)
{
var sss = typeof(TDestination).Name;
// 使用Mapster的Adapt方法进行对象映射
return source.Adapt<TDestination>();
}
/// <summary>
/// 将源对象映射到现有的目标对象
/// </summary>
/// <typeparam name="TSource">源类型</typeparam>
/// <typeparam name="TDestination">目标类型</typeparam>
/// <param name="source">源对象</param>
/// <param name="destination">目标对象</param>
/// <returns>映射后的目标对象</returns>
public TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{
return source.Adapt<TSource, TDestination>(destination);
// 使用Mapster的Adapt方法进行对象映射保留目标对象的实例
return source.Adapt(destination);
}
}
}

View File

@@ -7,18 +7,51 @@ using Volo.Abp.ObjectMapping;
namespace Yi.Framework.Mapster
{
/// <summary>
/// Mapster对象映射器
/// 实现IObjectMapper接口提供对象映射功能
/// </summary>
public class MapsterObjectMapper : IObjectMapper
{
public IAutoObjectMappingProvider AutoObjectMappingProvider => throw new NotImplementedException();
private readonly IAutoObjectMappingProvider _autoObjectMappingProvider;
public TDestination Map<TSource, TDestination>(TSource source)
/// <summary>
/// 构造函数
/// </summary>
/// <param name="autoObjectMappingProvider">自动对象映射提供程序</param>
public MapsterObjectMapper(IAutoObjectMappingProvider autoObjectMappingProvider)
{
throw new NotImplementedException();
_autoObjectMappingProvider = autoObjectMappingProvider;
}
/// <summary>
/// 获取自动对象映射提供程序
/// </summary>
public IAutoObjectMappingProvider AutoObjectMappingProvider => _autoObjectMappingProvider;
/// <summary>
/// 将源对象映射到目标类型
/// </summary>
/// <typeparam name="TSource">源类型</typeparam>
/// <typeparam name="TDestination">目标类型</typeparam>
/// <param name="source">源对象</param>
/// <returns>映射后的目标类型实例</returns>
public TDestination Map<TSource, TDestination>(TSource source)
{
return AutoObjectMappingProvider.Map<TSource, TDestination>(source);
}
/// <summary>
/// 将源对象映射到现有的目标对象
/// </summary>
/// <typeparam name="TSource">源类型</typeparam>
/// <typeparam name="TDestination">目标类型</typeparam>
/// <param name="source">源对象</param>
/// <param name="destination">目标对象</param>
/// <returns>映射后的目标对象</returns>
public TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{
throw new NotImplementedException();
return AutoObjectMappingProvider.Map(source, destination);
}
}
}

View File

@@ -1,21 +1,33 @@
using MapsterMapper;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectMapping;
using Yi.Framework.Core;
using Mapster;
namespace Yi.Framework.Mapster
{
[DependsOn(typeof(YiFrameworkCoreModule),
/// <summary>
/// Yi框架Mapster模块
/// 用于配置和注册Mapster相关服务
/// </summary>
[DependsOn(
typeof(YiFrameworkCoreModule),
typeof(AbpObjectMappingModule)
)]
)]
public class YiFrameworkMapsterModule : AbpModule
{
/// <summary>
/// 配置服务
/// </summary>
/// <param name="context">服务配置上下文</param>
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddTransient<IAutoObjectMappingProvider, MapsterAutoObjectMappingProvider>();
var services = context.Services;
// 扫描并注册所有映射配置
TypeAdapterConfig.GlobalSettings.Scan(AppDomain.CurrentDomain.GetAssemblies());
// 注册Mapster相关服务
services.AddTransient<IAutoObjectMappingProvider, MapsterAutoObjectMappingProvider>();
services.AddTransient<IObjectMapper, MapsterObjectMapper>();
}
}
}

View File

@@ -1,11 +1,16 @@
using SqlSugar;
using ArgumentException = System.ArgumentException;
namespace Yi.Framework.SqlSugarCore.Abstractions
{
/// <summary>
/// 数据库连接配置选项
/// </summary>
public class DbConnOptions
{
/// <summary>
/// 连接字符串(如果开启多租户,也就是默认库了),必填
/// 主数据库连接字符串
/// 如果开启多租户,此为默认租户数据库
/// </summary>
public string? Url { get; set; }
@@ -15,44 +20,48 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
public DbType? DbType { get; set; }
/// <summary>
/// 开启种子数据
/// 是否启用种子数据初始化
/// </summary>
public bool EnabledDbSeed { get; set; } = false;
/// <summary>
/// 开启驼峰转下划线
/// 是否启用驼峰命名转下划线命名
/// </summary>
public bool EnableUnderLine { get; set; } = false;
/// <summary>
/// 开启codefirst
/// 是否启用Code First模式
/// </summary>
public bool EnabledCodeFirst { get; set; } = false;
/// <summary>
/// 开启sql日志
/// 是否启用SQL日志记录
/// </summary>
public bool EnabledSqlLog { get; set; } = true;
/// <summary>
/// 实体程序集
/// 实体类所在程序集名称列表
/// </summary>
public List<string>? EntityAssembly { get; set; }
/// <summary>
/// 开启读写分离
/// 是否启用读写分离
/// </summary>
public bool EnabledReadWrite { get; set; } = false;
/// <summary>
/// 读写分离
/// 只读数据库连接字符串列表
/// </summary>
public List<string>? ReadUrl { get; set; }
/// <summary>
/// 开启Saas多租户
/// 是否启用SaaS多租户
/// </summary>
public bool EnabledSaasMultiTenancy { get; set; } = false;
/// <summary>
/// 是否开启更新并发乐观锁
/// </summary>
public bool EnabledConcurrencyException { get;set; } = false;
}
}
}

View File

@@ -4,10 +4,13 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.SqlSugarCore.Abstractions
namespace Yi.Framework.SqlSugarCore.Abstractions;
/// <summary>
/// 默认租户表特性
/// 标记此特性的实体类将在默认租户数据库中创建表
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class DefaultTenantTableAttribute : Attribute
{
[AttributeUsage(AttributeTargets.Class)]
public class DefaultTenantTableAttribute : Attribute
{
}
}

View File

@@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using SqlSugar;
namespace Yi.Framework.SqlSugarCore.Abstractions
{
public interface ISqlSugarDbConnectionCreator
{
DbConnOptions Options { get; }
Action<ISqlSugarClient> OnSqlSugarClientConfig { get; set; }
Action<object, DataAfterModel> DataExecuted { get; set; }
Action<object, DataFilterModel> DataExecuting { get; set; }
Action<string, SugarParameter[]> OnLogExecuting { get; set; }
Action<string, SugarParameter[]> OnLogExecuted { get; set; }
Action<PropertyInfo, EntityColumnInfo> EntityService { get; set; }
ConnectionConfig Build(Action<ConnectionConfig>? action = null);
void SetDbAop(ISqlSugarClient currentDb);
}
}

View File

@@ -8,16 +8,19 @@ using Volo.Abp.DependencyInjection;
namespace Yi.Framework.SqlSugarCore.Abstractions
{
/// <summary>
/// SqlSugar数据库上下文接口
/// </summary>
public interface ISqlSugarDbContext
{
// IAbpLazyServiceProvider LazyServiceProvider { get; set; }
ISqlSugarClient SqlSugarClient { get; }
DbConnOptions Options { get; }
/// <summary>
/// 数据库备份
/// 获取SqlSugar客户端实例
/// </summary>
ISqlSugarClient SqlSugarClient { get; }
/// <summary>
/// 执行数据库备份
/// </summary>
void BackupDataBase();
void SetSqlSugarClient(ISqlSugarClient sqlSugarClient);
}
}

View File

@@ -0,0 +1,57 @@
using System.Reflection;
using SqlSugar;
namespace Yi.Framework.SqlSugarCore.Abstractions;
/// <summary>
/// SqlSugar数据库上下文依赖接口
/// 定义数据库操作的各个生命周期钩子
/// </summary>
public interface ISqlSugarDbContextDependencies
{
/// <summary>
/// 获取执行顺序
/// </summary>
int ExecutionOrder { get; }
/// <summary>
/// SqlSugar客户端配置时触发
/// </summary>
/// <param name="sqlSugarClient">SqlSugar客户端实例</param>
void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient);
/// <summary>
/// 数据执行后触发
/// </summary>
/// <param name="oldValue">原始值</param>
/// <param name="entityInfo">实体信息</param>
void DataExecuted(object oldValue, DataAfterModel entityInfo);
/// <summary>
/// 数据执行前触发
/// </summary>
/// <param name="oldValue">原始值</param>
/// <param name="entityInfo">实体信息</param>
void DataExecuting(object oldValue, DataFilterModel entityInfo);
/// <summary>
/// SQL执行前触发
/// </summary>
/// <param name="sql">SQL语句</param>
/// <param name="parameters">SQL参数</param>
void OnLogExecuting(string sql, SugarParameter[] parameters);
/// <summary>
/// SQL执行后触发
/// </summary>
/// <param name="sql">SQL语句</param>
/// <param name="parameters">SQL参数</param>
void OnLogExecuted(string sql, SugarParameter[] parameters);
/// <summary>
/// 实体服务配置
/// </summary>
/// <param name="propertyInfo">属性信息</param>
/// <param name="entityColumnInfo">实体列信息</param>
void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo);
}

View File

@@ -2,87 +2,246 @@
using SqlSugar;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Uow;
namespace Yi.Framework.SqlSugarCore.Abstractions
{
public interface ISqlSugarRepository<TEntity>:IRepository<TEntity> where TEntity : class, IEntity,new ()
/// <summary>
/// SqlSugar仓储接口
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
public interface ISqlSugarRepository<TEntity> : IRepository<TEntity>, IUnitOfWorkEnabled
where TEntity : class, IEntity, new()
{
#region 访
/// <summary>
/// 获取SqlSugar客户端实例
/// </summary>
ISqlSugarClient _Db { get; }
/// <summary>
/// 获取查询构造器
/// </summary>
ISugarQueryable<TEntity> _DbQueryable { get; }
/// <summary>
/// 异步获取数据库上下文
/// </summary>
Task<ISqlSugarClient> GetDbContextAsync();
/// <summary>
/// 获取删除操作构造器
/// </summary>
Task<IDeleteable<TEntity>> AsDeleteable();
Task<IInsertable<TEntity>> AsInsertable(List<TEntity> insertObjs);
Task<IInsertable<TEntity>> AsInsertable(TEntity insertObj);
Task<IInsertable<TEntity>> AsInsertable(TEntity[] insertObjs);
/// <summary>
/// 获取插入操作构造器
/// </summary>
Task<IInsertable<TEntity>> AsInsertable(TEntity entity);
/// <summary>
/// 获取批量插入操作构造器
/// </summary>
Task<IInsertable<TEntity>> AsInsertable(List<TEntity> entities);
/// <summary>
/// 获取查询构造器
/// </summary>
Task<ISugarQueryable<TEntity>> AsQueryable();
/// <summary>
/// 获取SqlSugar客户端
/// </summary>
Task<ISqlSugarClient> AsSugarClient();
/// <summary>
/// 获取租户操作接口
/// </summary>
Task<ITenant> AsTenant();
Task<IUpdateable<TEntity>> AsUpdateable(List<TEntity> updateObjs);
Task<IUpdateable<TEntity>> AsUpdateable(TEntity updateObj);
/// <summary>
/// 获取更新操作构造器
/// </summary>
Task<IUpdateable<TEntity>> AsUpdateable();
Task<IUpdateable<TEntity>> AsUpdateable(TEntity[] updateObjs);
#region
//单查
/// <summary>
/// 获取实体更新操作构造器
/// </summary>
Task<IUpdateable<TEntity>> AsUpdateable(TEntity entity);
/// <summary>
/// 获取批量更新操作构造器
/// </summary>
Task<IUpdateable<TEntity>> AsUpdateable(List<TEntity> entities);
#endregion
#region
/// <summary>
/// 根据主键获取实体
/// </summary>
Task<TEntity> GetByIdAsync(dynamic id);
Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> whereExpression);
Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> whereExpression);
Task<bool> IsAnyAsync(Expression<Func<TEntity, bool>> whereExpression);
Task<int> CountAsync(Expression<Func<TEntity, bool>> whereExpression);
#endregion
/// <summary>
/// 获取满足条件的单个实体
/// </summary>
Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> predicate);
/// <summary>
/// 获取满足条件的第一个实体
/// </summary>
Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> predicate);
#region
//多查
/// <summary>
/// 判断是否存在满足条件的实体
/// </summary>
Task<bool> IsAnyAsync(Expression<Func<TEntity, bool>> predicate);
/// <summary>
/// 获取满足条件的实体数量
/// </summary>
Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate);
/// <summary>
/// 获取所有实体
/// </summary>
Task<List<TEntity>> GetListAsync();
Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> whereExpression);
/// <summary>
/// 获取满足条件的所有实体
/// </summary>
Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate);
#endregion
#region
/// <summary>
/// 获取分页数据
/// </summary>
Task<List<TEntity>> GetPageListAsync(
Expression<Func<TEntity, bool>> predicate,
int pageIndex,
int pageSize);
/// <summary>
/// 获取排序的分页数据
/// </summary>
Task<List<TEntity>> GetPageListAsync(
Expression<Func<TEntity, bool>> predicate,
int pageIndex,
int pageSize,
Expression<Func<TEntity, object>>? orderByExpression = null,
OrderByType orderByType = OrderByType.Asc);
#region
//分页查
Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize);
Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize, Expression<Func<TEntity, object>>? orderByExpression = null, OrderByType orderByType = OrderByType.Asc);
#endregion
#region
//插入
Task<bool> InsertAsync(TEntity insertObj);
Task<bool> InsertOrUpdateAsync(TEntity data);
Task<bool> InsertOrUpdateAsync(List<TEntity> datas);
Task<int> InsertReturnIdentityAsync(TEntity insertObj);
Task<long> InsertReturnBigIdentityAsync(TEntity insertObj);
Task<long> InsertReturnSnowflakeIdAsync(TEntity insertObj);
Task<TEntity> InsertReturnEntityAsync(TEntity insertObj);
Task<bool> InsertRangeAsync(List<TEntity> insertObjs);
#region
/// <summary>
/// 插入实体
/// </summary>
Task<bool> InsertAsync(TEntity entity);
/// <summary>
/// 插入或更新实体
/// </summary>
Task<bool> InsertOrUpdateAsync(TEntity entity);
/// <summary>
/// 批量插入或更新实体
/// </summary>
Task<bool> InsertOrUpdateAsync(List<TEntity> entities);
/// <summary>
/// 插入实体并返回自增主键
/// </summary>
Task<int> InsertReturnIdentityAsync(TEntity entity);
/// <summary>
/// 插入实体并返回长整型自增主键
/// </summary>
Task<long> InsertReturnBigIdentityAsync(TEntity entity);
/// <summary>
/// 插入实体并返回雪花ID
/// </summary>
Task<long> InsertReturnSnowflakeIdAsync(TEntity entity);
/// <summary>
/// 插入实体并返回实体
/// </summary>
Task<TEntity> InsertReturnEntityAsync(TEntity entity);
/// <summary>
/// 批量插入实体
/// </summary>
Task<bool> InsertRangeAsync(List<TEntity> entities);
#endregion
#region
/// <summary>
/// 更新实体
/// </summary>
Task<bool> UpdateAsync(TEntity entity);
/// <summary>
/// 批量更新实体
/// </summary>
Task<bool> UpdateRangeAsync(List<TEntity> entities);
/// <summary>
/// 条件更新指定列
/// </summary>
Task<bool> UpdateAsync(
Expression<Func<TEntity, TEntity>> columns,
Expression<Func<TEntity, bool>> predicate);
#region
//更新
Task<bool> UpdateAsync(TEntity updateObj);
Task<bool> UpdateRangeAsync(List<TEntity> updateObjs);
Task<bool> UpdateAsync(Expression<Func<TEntity, TEntity>> columns, Expression<Func<TEntity, bool>> whereExpression);
#endregion
#region
//删除
Task<bool> DeleteAsync(TEntity deleteObj);
Task<bool> DeleteAsync(List<TEntity> deleteObjs);
Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> whereExpression);
#region
/// <summary>
/// 删除实体
/// </summary>
Task<bool> DeleteAsync(TEntity entity);
/// <summary>
/// 批量删除实体
/// </summary>
Task<bool> DeleteAsync(List<TEntity> entities);
/// <summary>
/// 条件删除
/// </summary>
Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> predicate);
/// <summary>
/// 根据主键删除
/// </summary>
Task<bool> DeleteByIdAsync(dynamic id);
Task<bool> DeleteByIdsAsync(dynamic[] ids);
#endregion
/// <summary>
/// 根据主键批量删除
/// </summary>
Task<bool> DeleteByIdsAsync(dynamic[] ids);
#endregion
}
public interface ISqlSugarRepository<TEntity, TKey> : ISqlSugarRepository<TEntity>,IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new()
{
/// <summary>
/// SqlSugar仓储接口(带主键)
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TKey">主键类型</typeparam>
public interface ISqlSugarRepository<TEntity, TKey> :
ISqlSugarRepository<TEntity>,
IRepository<TEntity, TKey>
where TEntity : class, IEntity<TKey>, new()
{
}
}

View File

@@ -6,12 +6,17 @@ using System.Threading.Tasks;
namespace Yi.Framework.SqlSugarCore.Abstractions
{
/// <summary>
/// SqlSugar数据库上下文提供者接口
/// </summary>
/// <typeparam name="TDbContext">数据库上下文类型</typeparam>
public interface ISugarDbContextProvider<TDbContext>
where TDbContext : ISqlSugarDbContext
{
/// <summary>
/// 异步获取数据库上下文实例
/// </summary>
/// <returns>数据库上下文实例</returns>
Task<TDbContext> GetDbContextAsync();
}
}

View File

@@ -6,6 +6,10 @@ using System.Threading.Tasks;
namespace Yi.Framework.SqlSugarCore.Abstractions
{
/// <summary>
/// 忽略CodeFirst特性
/// 标记此特性的实体类将不会被CodeFirst功能扫描
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class IgnoreCodeFirstAttribute : Attribute
{

View File

@@ -3,7 +3,10 @@
<ItemGroup>
<PackageReference Include="SqlSugarCoreNoDrive" Version="$(SqlSugarVersion)" />
<!-- <PackageReference Include="SqlSugarCoreNoDrive" Version="$(SqlSugarVersion)" />-->
<PackageReference Include="SqlSugarCore" Version="$(SqlSugarVersion)" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" />
</ItemGroup>

View File

@@ -3,9 +3,13 @@ using Yi.Framework.Core;
namespace Yi.Framework.SqlSugarCore.Abstractions
{
/// <summary>
/// SqlSugar Core抽象层模块
/// 提供SqlSugar ORM的基础抽象接口和类型定义
/// </summary>
[DependsOn(typeof(YiFrameworkCoreModule))]
public class YiFrameworkSqlSugarCoreAbstractionsModule : AbpModule
{
// 模块配置方法可在此添加
}
}

View File

@@ -2,18 +2,34 @@
namespace Yi.Framework.SqlSugarCore
{
public class AsyncLocalDbContextAccessor
/// <summary>
/// 异步本地数据库上下文访问器
/// 用于在异步流中保存和访问数据库上下文
/// </summary>
public sealed class AsyncLocalDbContextAccessor
{
private readonly AsyncLocal<ISqlSugarDbContext?> _currentScope;
/// <summary>
/// 获取单例实例
/// </summary>
public static AsyncLocalDbContextAccessor Instance { get; } = new();
/// <summary>
/// 获取或设置当前数据库上下文
/// </summary>
public ISqlSugarDbContext? Current
{
get => _currentScope.Value;
set => _currentScope.Value = value;
}
public AsyncLocalDbContextAccessor()
/// <summary>
/// 初始化异步本地数据库上下文访问器
/// </summary>
private AsyncLocalDbContextAccessor()
{
_currentScope = new AsyncLocal<ISqlSugarDbContext?>();
}
private readonly AsyncLocal<ISqlSugarDbContext> _currentScope;
}
}
}

View File

@@ -0,0 +1,362 @@
using System.Collections;
using System.Reflection;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
using Volo.Abp.Users;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore;
/// <summary>
/// 默认SqlSugar数据库上下文实现
/// </summary>
public class DefaultSqlSugarDbContext : SqlSugarDbContext
{
#region Protected Properties
/// <summary>
/// 数据库连接配置选项
/// </summary>
protected DbConnOptions DbOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
/// <summary>
/// 当前用户服务
/// </summary>
protected ICurrentUser CurrentUserService => LazyServiceProvider.GetRequiredService<ICurrentUser>();
/// <summary>
/// GUID生成器
/// </summary>
protected IGuidGenerator GuidGeneratorService => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
/// <summary>
/// 日志工厂
/// </summary>
protected ILoggerFactory LoggerFactory => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
/// <summary>
/// 当前租户服务
/// </summary>
protected ICurrentTenant CurrentTenantService => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
/// <summary>
/// 数据过滤服务
/// </summary>
protected IDataFilter DataFilterService => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
/// <summary>
/// 工作单元管理器
/// </summary>
protected IUnitOfWorkManager UnitOfWorkManagerService => LazyServiceProvider.LazyGetRequiredService<IUnitOfWorkManager>();
/// <summary>
/// 实体变更事件帮助类
/// </summary>
protected IEntityChangeEventHelper EntityChangeEventHelperService =>
LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
/// <summary>
/// 是否启用多租户过滤
/// </summary>
protected virtual bool IsMultiTenantFilterEnabled => DataFilterService?.IsEnabled<IMultiTenant>() ?? false;
/// <summary>
/// 是否启用软删除过滤
/// </summary>
protected virtual bool IsSoftDeleteFilterEnabled => DataFilterService?.IsEnabled<ISoftDelete>() ?? false;
#endregion
/// <summary>
/// 构造函数
/// </summary>
public DefaultSqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
: base(lazyServiceProvider)
{
}
/// <summary>
/// 自定义数据过滤器
/// </summary>
protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{
// 配置软删除过滤器
if (IsSoftDeleteFilterEnabled)
{
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(entity => !entity.IsDeleted);
}
// 配置多租户过滤器
if (IsMultiTenantFilterEnabled)
{
var currentTenantId = CurrentTenantService.Id;
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(entity => entity.TenantId == currentTenantId);
}
}
/// <summary>
/// 数据执行前的处理
/// </summary>
public override void DataExecuting(object oldValue, DataFilterModel entityInfo)
{
HandleAuditFields(oldValue, entityInfo);
HandleEntityEvents(entityInfo);
HandleDomainEvents(entityInfo);
}
#region Private Methods
/// <summary>
/// 处理审计字段
/// </summary>
private void HandleAuditFields(object oldValue, DataFilterModel entityInfo)
{
switch (entityInfo.OperationType)
{
case DataFilterType.UpdateByObject:
HandleUpdateAuditFields(oldValue, entityInfo);
break;
case DataFilterType.InsertByObject:
HandleInsertAuditFields(oldValue, entityInfo);
break;
}
}
/// <summary>
/// 处理更新时的审计字段
/// </summary>
private void HandleUpdateAuditFields(object oldValue, DataFilterModel entityInfo)
{
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
{
entityInfo.SetValue(DateTime.MinValue.Equals(oldValue) ? null : DateTime.Now);
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId))
&& entityInfo.EntityColumnInfo.PropertyInfo.PropertyType == typeof(Guid?))
{
entityInfo.SetValue(Guid.Empty.Equals(oldValue) ? null : CurrentUserService.Id);
}
}
/// <summary>
/// 处理插入时的审计字段
/// </summary>
private void HandleInsertAuditFields(object oldValue, DataFilterModel entityInfo)
{
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
{
if (typeof(Guid) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
if (Guid.Empty.Equals(oldValue))
{
entityInfo.SetValue(GuidGeneratorService.Create());
}
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
{
if (DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
{
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
if (CurrentUserService.Id is not null)
{
entityInfo.SetValue(CurrentUserService.Id);
}
}
}
else if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
{
if (CurrentTenantService.Id is not null)
{
entityInfo.SetValue(CurrentTenantService.Id);
}
}
}
/// <summary>
/// 处理实体变更事件
/// </summary>
private void HandleEntityEvents(DataFilterModel entityInfo)
{
// 实体变更领域事件
switch (entityInfo.OperationType)
{
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
EntityChangeEventHelperService.PublishEntityCreatedEvent(entityInfo.EntityValue);
}
break;
case DataFilterType.UpdateByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
if (entityInfo.EntityValue is ISoftDelete softDelete)
{
if (softDelete.IsDeleted == true)
{
EntityChangeEventHelperService.PublishEntityDeletedEvent(entityInfo.EntityValue);
}
else
{
EntityChangeEventHelperService.PublishEntityUpdatedEvent(entityInfo.EntityValue);
}
}
else
{
EntityChangeEventHelperService.PublishEntityUpdatedEvent(entityInfo.EntityValue);
}
}
break;
case DataFilterType.DeleteByObject:
if (entityInfo.EntityValue is IEnumerable entityValues)
{
foreach (var entityValue in entityValues)
{
EntityChangeEventHelperService.PublishEntityDeletedEvent(entityValue);
}
}
break;
}
}
/// <summary>
/// 处理领域事件
/// </summary>
private void HandleDomainEvents(DataFilterModel entityInfo)
{
// 实体领域事件-所有操作类型
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
var eventReport = CreateEventReport(entityInfo.EntityValue);
PublishEntityEvents(eventReport);
}
}
/// <summary>
/// 创建领域事件报告
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
protected virtual EntityEventReport? CreateEventReport(object entity)
{
var eventReport = new EntityEventReport();
//判断是否为领域事件-聚合根
var generatesDomainEventsEntity = entity as IGeneratesDomainEvents;
if (generatesDomainEventsEntity == null)
{
return eventReport;
}
var localEvents = generatesDomainEventsEntity.GetLocalEvents()?.ToArray();
if (localEvents != null && localEvents.Any())
{
eventReport.DomainEvents.AddRange(
localEvents.Select(
eventRecord => new DomainEventEntry(
entity,
eventRecord.EventData,
eventRecord.EventOrder
)
)
);
generatesDomainEventsEntity.ClearLocalEvents();
}
var distributedEvents = generatesDomainEventsEntity.GetDistributedEvents()?.ToArray();
if (distributedEvents != null && distributedEvents.Any())
{
eventReport.DistributedEvents.AddRange(
distributedEvents.Select(
eventRecord => new DomainEventEntry(
entity,
eventRecord.EventData,
eventRecord.EventOrder)
)
);
generatesDomainEventsEntity.ClearDistributedEvents();
}
return eventReport;
}
/// <summary>
/// 发布领域事件
/// </summary>
/// <param name="changeReport"></param>
private void PublishEntityEvents(EntityEventReport changeReport)
{
foreach (var localEvent in changeReport.DomainEvents)
{
UnitOfWorkManagerService.Current?.AddOrReplaceLocalEvent(
new UnitOfWorkEventRecord(localEvent.EventData.GetType(), localEvent.EventData, localEvent.EventOrder)
);
}
foreach (var distributedEvent in changeReport.DistributedEvents)
{
UnitOfWorkManagerService.Current?.AddOrReplaceDistributedEvent(
new UnitOfWorkEventRecord(distributedEvent.EventData.GetType(), distributedEvent.EventData, distributedEvent.EventOrder)
);
}
}
#endregion
public override void OnLogExecuting(string sql, SugarParameter[] pars)
{
if (DbOptions.EnabledSqlLog)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("==========Yi-SQL执行:==========");
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
sb.AppendLine("===============================");
LoggerFactory.CreateLogger<DefaultSqlSugarDbContext>().LogDebug(sb.ToString());
}
}
public override void OnLogExecuted(string sql, SugarParameter[] pars)
{
if (DbOptions.EnabledSqlLog)
{
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
LoggerFactory.CreateLogger<SqlSugarDbContext>().LogDebug(sqllog.ToString());
}
}
public override void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
{
if (propertyInfo.Name == nameof(IHasConcurrencyStamp.ConcurrencyStamp)) //带版本号并发更新
{
entityColumnInfo.IsEnableUpdateVersionValidation = true;
}
if (propertyInfo.PropertyType == typeof(ExtraPropertyDictionary))
{
entityColumnInfo.IsIgnore = true;
}
if (propertyInfo.Name == nameof(Entity<object>.Id))
{
entityColumnInfo.IsPrimarykey = true;
}
}
}

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,38 +14,48 @@ 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 ISugarDbContextProvider<ISqlSugarDbContext> _sugarDbContextProvider;
private readonly ISugarDbContextProvider<ISqlSugarDbContext> _dbContextProvider;
public IAbpLazyServiceProvider LazyServiceProvider { get; set; }
protected DbConnOptions? Options => LazyServiceProvider?.LazyGetService<IOptions<DbConnOptions>>().Value;
/// <summary>
/// 异步查询执行器
/// </summary>
public IAsyncQueryableExecuter AsyncExecuter { get; }
/// <summary>
/// 是否启用变更追踪
/// </summary>
public bool? IsChangeTrackingEnabled => false;
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> sugarDbContextProvider)
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> dbContextProvider)
{
_sugarDbContextProvider = sugarDbContextProvider;
_dbContextProvider = dbContextProvider;
}
/// <summary>
/// 获取DB
/// 获取数据库上下文
/// </summary>
/// <returns></returns>
public virtual async Task<ISqlSugarClient> GetDbContextAsync()
{
var db = (await _sugarDbContextProvider.GetDbContextAsync()).SqlSugarClient;
return db;
var dbContext = await _dbContextProvider.GetDbContextAsync();
return dbContext.SqlSugarClient;
}
/// <summary>
/// 获取简单Db
/// 获取简单数据库客户端
/// </summary>
/// <returns></returns>
public virtual async Task<SimpleClient<TEntity>> GetDbSimpleClientAsync()
{
var db = await GetDbContextAsync();
return new SimpleClient<TEntity>(db);
var dbContext = await GetDbContextAsync();
return new SimpleClient<TEntity>(dbContext);
}
#region Abp模块
@@ -166,7 +173,7 @@ namespace Yi.Framework.SqlSugarCore.Repositories
{
return (await GetDbSimpleClientAsync()).AsInsertable(insertObj);
}
public virtual async Task<IInsertable<TEntity>> AsInsertable(TEntity[] insertObjs)
{
return (await GetDbSimpleClientAsync()).AsInsertable(insertObjs);
@@ -259,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);
@@ -314,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)
@@ -374,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

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore;
/// <summary>
/// SqlSugar Core扩展方法
/// </summary>
public static class SqlSugarCoreExtensions
{
/// <summary>
/// 添加数据库上下文
/// </summary>
/// <typeparam name="TDbContext">数据库上下文类型</typeparam>
/// <param name="services">服务集合</param>
/// <param name="serviceLifetime">服务生命周期</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddYiDbContext<TDbContext>(
this IServiceCollection services,
ServiceLifetime serviceLifetime = ServiceLifetime.Transient)
where TDbContext : class, ISqlSugarDbContextDependencies
{
services.Add(new ServiceDescriptor(
typeof(ISqlSugarDbContextDependencies),
typeof(TDbContext),
serviceLifetime));
return services;
}
/// <summary>
/// 添加数据库上下文并配置选项
/// </summary>
/// <typeparam name="TDbContext">数据库上下文类型</typeparam>
/// <param name="services">服务集合</param>
/// <param name="configureOptions">配置选项委托</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddYiDbContext<TDbContext>(
this IServiceCollection services,
Action<DbConnOptions> configureOptions)
where TDbContext : class, ISqlSugarDbContextDependencies
{
services.Configure(configureOptions);
services.AddYiDbContext<TDbContext>();
return services;
}
}

View File

@@ -1,123 +0,0 @@
using System.Reflection;
using Microsoft.Extensions.Options;
using SqlSugar;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore
{
public class SqlSugarDbConnectionCreator: ISqlSugarDbConnectionCreator,ITransientDependency
{
public SqlSugarDbConnectionCreator(IOptions<DbConnOptions> options)
{
Options = options.Value;
}
public DbConnOptions Options { get; }
public void SetDbAop(ISqlSugarClient currentDb)
{
currentDb.Aop.OnLogExecuting = this.OnLogExecuting;
currentDb.Aop.OnLogExecuted = this.OnLogExecuted;
currentDb.Aop.DataExecuting = this.DataExecuting;
currentDb.Aop.DataExecuted = this.DataExecuted;
OnSqlSugarClientConfig(currentDb);
}
public ConnectionConfig Build(Action<ConnectionConfig>? action=null)
{
var dbConnOptions = Options;
#region options
if (dbConnOptions.DbType is null)
{
throw new ArgumentException("DbType配置为空");
}
var slavaConFig = new List<SlaveConnectionConfig>();
if (dbConnOptions.EnabledReadWrite)
{
if (dbConnOptions.ReadUrl is null)
{
throw new ArgumentException("读写分离为空");
}
var readCon = dbConnOptions.ReadUrl;
readCon.ForEach(s =>
{
//如果是动态saas分库这里的连接串都不能写死需要动态添加这里只配置共享库的连接
slavaConFig.Add(new SlaveConnectionConfig() { ConnectionString = s });
});
}
#endregion
#region config
var connectionConfig = new ConnectionConfig()
{
ConfigId= ConnectionStrings.DefaultConnectionStringName,
DbType = dbConnOptions.DbType ?? DbType.Sqlite,
ConnectionString = dbConnOptions.Url,
IsAutoCloseConnection = true,
SlaveConnectionConfigs = slavaConFig,
//设置codefirst非空值判断
ConfigureExternalServices = new ConfigureExternalServices
{
// 处理表
EntityNameService = (type, entity) =>
{
if (dbConnOptions.EnableUnderLine && !entity.DbTableName.Contains('_'))
entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName);// 驼峰转下划线
},
EntityService = (c, p) =>
{
if (new NullabilityInfoContext()
.Create(c).WriteState is NullabilityState.Nullable)
{
p.IsNullable = true;
}
if (dbConnOptions.EnableUnderLine && !p.IsIgnore && !p.DbColumnName.Contains('_'))
p.DbColumnName = UtilMethods.ToUnderLine(p.DbColumnName);// 驼峰转下划线
EntityService(c, p);
}
},
//这里多租户有个坑,无效的
AopEvents = new AopEvents
{
DataExecuted = DataExecuted,
DataExecuting = DataExecuting,
OnLogExecuted = OnLogExecuted,
OnLogExecuting = OnLogExecuting
}
};
if (action is not null)
{
action.Invoke(connectionConfig);
}
#endregion
return connectionConfig;
}
[DisablePropertyInjection]
public Action<ISqlSugarClient> OnSqlSugarClientConfig { get; set; }
[DisablePropertyInjection]
public Action<object, DataAfterModel> DataExecuted { get; set; }
[DisablePropertyInjection]
public Action<object, DataFilterModel> DataExecuting { get; set; }
[DisablePropertyInjection]
public Action<string, SugarParameter[]> OnLogExecuting { get; set; }
[DisablePropertyInjection]
public Action<string, SugarParameter[]> OnLogExecuted { get; set; }
[DisablePropertyInjection]
public Action<PropertyInfo, EntityColumnInfo> EntityService { get; set; }
}
}

View File

@@ -1,379 +1,83 @@
using System.Collections;
using System.Reflection;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Reflection;
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Users;
using Yi.Framework.SqlSugarCore.Abstractions;
using Check = Volo.Abp.Check;
namespace Yi.Framework.SqlSugarCore
namespace Yi.Framework.SqlSugarCore;
/// <summary>
/// SqlSugar数据库上下文基类
/// </summary>
public abstract class SqlSugarDbContext : ISqlSugarDbContextDependencies
{
public class SqlSugarDbContext : ISqlSugarDbContext
/// <summary>
/// 服务提供者
/// </summary>
protected IAbpLazyServiceProvider LazyServiceProvider { get; }
/// <summary>
/// 数据库客户端实例
/// </summary>
protected ISqlSugarClient SqlSugarClient { get; private set; }
/// <summary>
/// 执行顺序
/// </summary>
public virtual int ExecutionOrder => 0;
protected SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
{
/// <summary>
/// SqlSugar 客户端
/// </summary>
public ISqlSugarClient SqlSugarClient { get; private set; }
LazyServiceProvider = lazyServiceProvider;
}
protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
private IAbpLazyServiceProvider LazyServiceProvider { get; }
/// <summary>
/// 配置SqlSugar客户端
/// </summary>
public virtual void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
{
SqlSugarClient = sqlSugarClient;
CustomDataFilter(sqlSugarClient);
}
private IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
private ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
/// <summary>
/// 自定义数据过滤器
/// </summary>
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{
}
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
/// <summary>
/// 数据执行后事件
/// </summary>
public virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
{
}
private IEntityChangeEventHelper EntityChangeEventHelper =>
LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
/// <summary>
/// 数据执行前事件
/// </summary>
public virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
{
}
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
/// <summary>
/// SQL执行前事件
/// </summary>
public virtual void OnLogExecuting(string sql, SugarParameter[] pars)
{
}
private ISerializeService SerializeService => LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
/// <summary>
/// SQL执行后事件
/// </summary>
public virtual void OnLogExecuted(string sql, SugarParameter[] pars)
{
}
public void SetSqlSugarClient(ISqlSugarClient sqlSugarClient)
{
SqlSugarClient = sqlSugarClient;
}
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
{
LazyServiceProvider = lazyServiceProvider;
var connectionCreator = LazyServiceProvider.LazyGetRequiredService<ISqlSugarDbConnectionCreator>();
connectionCreator.OnSqlSugarClientConfig = OnSqlSugarClientConfig;
connectionCreator.EntityService = EntityService;
connectionCreator.DataExecuting = DataExecuting;
connectionCreator.DataExecuted = DataExecuted;
connectionCreator.OnLogExecuting = OnLogExecuting;
connectionCreator.OnLogExecuted = OnLogExecuted;
SqlSugarClient = new SqlSugarClient(connectionCreator.Build(action: options =>
{
options.ConnectionString = GetCurrentConnectionString();
options.DbType = GetCurrentDbType();
}));
//统一使用aop处理
connectionCreator.SetDbAop(SqlSugarClient);
//替换默认序列化器
SqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
}
/// <summary>
/// db切换多库支持
/// </summary>
/// <returns></returns>
protected virtual string GetCurrentConnectionString()
{
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
var connectionString =
connectionStringResolver.ResolveAsync().ConfigureAwait(false).GetAwaiter().GetResult();
if (string.IsNullOrWhiteSpace(connectionString))
{
Check.NotNull(Options.Url, "dbUrl未配置");
}
return connectionString!;
}
protected virtual DbType GetCurrentDbType()
{
if (CurrentTenant.Name is not null)
{
var dbTypeFromTenantName = GetDbTypeFromTenantName(CurrentTenant.Name);
if (dbTypeFromTenantName is not null)
{
return dbTypeFromTenantName.Value;
}
}
Check.NotNull(Options.DbType, "默认DbType未配置");
return Options.DbType!.Value;
}
//根据租户name进行匹配db类型: Test_Sqlite[来自AI]
private DbType? GetDbTypeFromTenantName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return null;
}
// 查找下划线的位置
int underscoreIndex = name.LastIndexOf('_');
if (underscoreIndex == -1 || underscoreIndex == name.Length - 1)
{
return null;
}
// 提取 枚举 部分
string enumString = name.Substring(underscoreIndex + 1);
// 尝试将 尾缀 转换为枚举
if (Enum.TryParse<DbType>(enumString, out DbType result))
{
return result;
}
// 条件不满足时返回 null
return null;
}
/// <summary>
/// 上下文对象扩展
/// </summary>
/// <param name="sqlSugarClient"></param>
protected virtual void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
{
//需自定义扩展
if (IsSoftDeleteFilterEnabled)
{
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(u => u.IsDeleted == false);
}
if (IsMultiTenantFilterEnabled)
{
//表达式里只能有具体值,不能运算
var expressionCurrentTenant = CurrentTenant.Id ?? null;
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == expressionCurrentTenant);
}
CustomDataFilter(sqlSugarClient);
}
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{
}
protected virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
{
}
/// <summary>
/// 数据
/// </summary>
/// <param name="oldValue"></param>
/// <param name="entityInfo"></param>
protected virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
{
//审计日志
switch (entityInfo.OperationType)
{
case DataFilterType.UpdateByObject:
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
{
if (!DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
{
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
if (CurrentUser.Id != null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
}
break;
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
{
//类型为guid
if (typeof(Guid) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
//主键为空或者为默认最小值
if (Guid.Empty.Equals(oldValue))
{
entityInfo.SetValue(GuidGenerator.Create());
}
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
{
//为空或者为默认最小值
if (DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
{
//类型为guid
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
if (CurrentUser.Id is not null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
}
else if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
{
if (CurrentTenant.Id is not null)
{
entityInfo.SetValue(CurrentTenant.Id);
}
}
break;
}
//领域事件
switch (entityInfo.OperationType)
{
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue);
}
break;
case DataFilterType.UpdateByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
//软删除,发布的是删除事件
if (entityInfo.EntityValue is ISoftDelete softDelete)
{
if (softDelete.IsDeleted == true)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue);
}
}
else
{
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue);
}
}
break;
case DataFilterType.DeleteByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
//这里sqlsugar有个特殊删除会返回批量的结果
if (entityInfo.EntityValue is IEnumerable entityValues)
{
foreach (var entityValue in entityValues)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue);
}
}
}
break;
}
}
/// <summary>
/// 日志
/// </summary>
/// <param name="sql"></param>
/// <param name="pars"></param>
protected virtual void OnLogExecuting(string sql, SugarParameter[] pars)
{
if (Options.EnabledSqlLog)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("==========Yi-SQL执行:==========");
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
sb.AppendLine("===============================");
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sb.ToString());
}
}
/// <summary>
/// 日志
/// </summary>
/// <param name="sql"></param>
/// <param name="pars"></param>
protected virtual void OnLogExecuted(string sql, SugarParameter[] pars)
{
if (Options.EnabledSqlLog)
{
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sqllog.ToString());
}
}
/// <summary>
/// 实体配置
/// </summary>
/// <param name="property"></param>
/// <param name="column"></param>
protected virtual void EntityService(PropertyInfo property, EntityColumnInfo column)
{
if (property.Name == nameof(IHasConcurrencyStamp.ConcurrencyStamp)) //带版本号并发更新
{
column.IsEnableUpdateVersionValidation = true;
}
if (property.PropertyType == typeof(ExtraPropertyDictionary))
{
column.IsIgnore = true;
}
if (property.Name == nameof(Entity<object>.Id))
{
column.IsPrimarykey = true;
}
}
public void BackupDataBase()
{
string directoryName = "database_backup";
string fileName = DateTime.Now.ToString($"yyyyMMdd_HHmmss") + $"_{SqlSugarClient.Ado.Connection.Database}";
if (!Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
switch (Options.DbType)
{
case DbType.MySql:
//MySql
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
$"{Path.Combine(directoryName, fileName)}.sql"); //mysql 只支持.net core
break;
case DbType.Sqlite:
//Sqlite
SqlSugarClient.DbMaintenance.BackupDataBase(null, $"{fileName}.db"); //sqlite 只支持.net core
break;
case DbType.SqlServer:
//SqlServer
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
$"{Path.Combine(directoryName, fileName)}.bak" /*服务器路径*/); //第一个参数库名
break;
default:
throw new NotImplementedException("其他数据库备份未实现");
}
}
/// <summary>
/// 实体服务配置
/// </summary>
public virtual void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
{
}
}

View File

@@ -4,26 +4,52 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore;
/// <summary>
/// SqlSugar数据库上下文创建上下文
/// </summary>
public class SqlSugarDbContextCreationContext
{
public static SqlSugarDbContextCreationContext Current => _current.Value;
private static readonly AsyncLocal<SqlSugarDbContextCreationContext> _current = new AsyncLocal<SqlSugarDbContextCreationContext>();
private static readonly AsyncLocal<SqlSugarDbContextCreationContext> CurrentContextHolder =
new AsyncLocal<SqlSugarDbContextCreationContext>();
/// <summary>
/// 获取当前上下文
/// </summary>
public static SqlSugarDbContextCreationContext Current => CurrentContextHolder.Value!;
/// <summary>
/// 连接字符串名称
/// </summary>
public string ConnectionStringName { get; }
/// <summary>
/// 连接字符串
/// </summary>
public string ConnectionString { get; }
public DbConnection ExistingConnection { get; internal set; }
/// <summary>
/// 现有数据库连接
/// </summary>
public DbConnection? ExistingConnection { get; internal set; }
public SqlSugarDbContextCreationContext(string connectionStringName, string connectionString)
/// <summary>
/// 构造函数
/// </summary>
public SqlSugarDbContextCreationContext(
string connectionStringName,
string connectionString)
{
ConnectionStringName = connectionStringName;
ConnectionString = connectionString;
}
/// <summary>
/// 使用指定的上下文
/// </summary>
public static IDisposable Use(SqlSugarDbContextCreationContext context)
{
var previousValue = Current;
_current.Value = context;
return new DisposeAction(() => _current.Value = previousValue);
var previousContext = Current;
CurrentContextHolder.Value = context;
return new DisposeAction(() => CurrentContextHolder.Value = previousContext);
}
}

View File

@@ -0,0 +1,290 @@
using System.Collections.Concurrent;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using SqlSugar;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Users;
using Yi.Framework.SqlSugarCore.Abstractions;
using Check = Volo.Abp.Check;
namespace Yi.Framework.SqlSugarCore
{
/// <summary>
/// SqlSugar数据库上下文工厂类
/// 负责创建和配置SqlSugar客户端实例
/// </summary>
public class SqlSugarDbContextFactory : ISqlSugarDbContext
{
#region Properties
/// <summary>
/// SqlSugar客户端实例
/// </summary>
public ISqlSugarClient SqlSugarClient { get; private set; }
/// <summary>
/// 延迟服务提供者
/// </summary>
private IAbpLazyServiceProvider LazyServiceProvider { get; }
/// <summary>
/// 租户配置包装器
/// </summary>
private TenantConfigurationWrapper TenantConfigurationWrapper =>
LazyServiceProvider.LazyGetRequiredService<TenantConfigurationWrapper>();
/// <summary>
/// 当前租户信息
/// </summary>
private ICurrentTenant CurrentTenant =>
LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
/// <summary>
/// 数据库连接配置选项
/// </summary>
private DbConnOptions DbConnectionOptions =>
LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
/// <summary>
/// 序列化服务
/// </summary>
private ISerializeService SerializeService =>
LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
/// <summary>
/// SqlSugar上下文依赖项集合
/// </summary>
private IEnumerable<ISqlSugarDbContextDependencies> SqlSugarDbContextDependencies =>
LazyServiceProvider.LazyGetRequiredService<IEnumerable<ISqlSugarDbContextDependencies>>();
/// <summary>
/// 连接配置缓存字典
/// </summary>
private static readonly ConcurrentDictionary<string, ConnectionConfig> ConnectionConfigCache = new();
#endregion
/// <summary>
/// 构造函数
/// </summary>
/// <param name="lazyServiceProvider">延迟服务提供者</param>
public SqlSugarDbContextFactory(IAbpLazyServiceProvider lazyServiceProvider)
{
LazyServiceProvider = lazyServiceProvider;
// 异步获取租户配置
var tenantConfiguration = AsyncHelper.RunSync(async () => await TenantConfigurationWrapper.GetAsync());
// 构建数据库连接配置
var connectionConfig = BuildConnectionConfig(options =>
{
options.ConnectionString = tenantConfiguration.GetCurrentConnectionString();
options.DbType = GetCurrentDbType(tenantConfiguration.GetCurrentConnectionName());
});
// 创建SqlSugar客户端实例
SqlSugarClient = new SqlSugarClient(connectionConfig);
// 配置数据库AOP
ConfigureDbAop(SqlSugarClient);
}
/// <summary>
/// 配置数据库AOP操作
/// </summary>
/// <param name="sqlSugarClient">SqlSugar客户端实例</param>
protected virtual void ConfigureDbAop(ISqlSugarClient sqlSugarClient)
{
// 配置序列化服务
sqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
// 初始化AOP事件处理器
Action<string, SugarParameter[]> onLogExecuting = null;
Action<string, SugarParameter[]> onLogExecuted = null;
Action<object, DataFilterModel> dataExecuting = null;
Action<object, DataAfterModel> dataExecuted = null;
Action<ISqlSugarClient> onClientConfig = null;
// 按执行顺序聚合所有依赖项的AOP处理器
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
{
onLogExecuting += dependency.OnLogExecuting;
onLogExecuted += dependency.OnLogExecuted;
dataExecuting += dependency.DataExecuting;
dataExecuted += dependency.DataExecuted;
onClientConfig += dependency.OnSqlSugarClientConfig;
}
// 配置SqlSugar客户端
onClientConfig?.Invoke(sqlSugarClient);
// 设置AOP事件
sqlSugarClient.Aop.OnLogExecuting = onLogExecuting;
sqlSugarClient.Aop.OnLogExecuted = onLogExecuted;
sqlSugarClient.Aop.DataExecuting = dataExecuting;
sqlSugarClient.Aop.DataExecuted = dataExecuted;
}
/// <summary>
/// 构建数据库连接配置
/// </summary>
/// <param name="configAction">配置操作委托</param>
/// <returns>连接配置对象</returns>
protected virtual ConnectionConfig BuildConnectionConfig(Action<ConnectionConfig> configAction = null)
{
var dbConnOptions = DbConnectionOptions;
// 验证数据库类型配置
if (dbConnOptions.DbType is null)
{
throw new ArgumentException("未配置数据库类型(DbType)");
}
// 配置读写分离
var slaveConfigs = new List<SlaveConnectionConfig>();
if (dbConnOptions.EnabledReadWrite)
{
if (dbConnOptions.ReadUrl is null)
{
throw new ArgumentException("启用读写分离但未配置读库连接字符串");
}
slaveConfigs.AddRange(dbConnOptions.ReadUrl.Select(url =>
new SlaveConnectionConfig { ConnectionString = url }));
}
// 创建连接配置
var connectionConfig = new ConnectionConfig
{
ConfigId = ConnectionStrings.DefaultConnectionStringName,
DbType = dbConnOptions.DbType ?? DbType.Sqlite,
ConnectionString = dbConnOptions.Url,
IsAutoCloseConnection = true,
SlaveConnectionConfigs = slaveConfigs,
ConfigureExternalServices = CreateExternalServices(dbConnOptions)
};
// 应用额外配置
configAction?.Invoke(connectionConfig);
return connectionConfig;
}
/// <summary>
/// 创建外部服务配置
/// </summary>
private ConfigureExternalServices CreateExternalServices(DbConnOptions dbConnOptions)
{
return new ConfigureExternalServices
{
EntityNameService = (type, entity) =>
{
if (dbConnOptions.EnableUnderLine && !entity.DbTableName.Contains('_'))
{
entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName);
}
},
EntityService = (propertyInfo, columnInfo) =>
{
// 配置空值处理
if (new NullabilityInfoContext().Create(propertyInfo).WriteState
is NullabilityState.Nullable)
{
columnInfo.IsNullable = true;
}
// 处理下划线命名
if (dbConnOptions.EnableUnderLine && !columnInfo.IsIgnore
&& !columnInfo.DbColumnName.Contains('_'))
{
columnInfo.DbColumnName = UtilMethods.ToUnderLine(columnInfo.DbColumnName);
}
// 聚合所有依赖项的实体服务
Action<PropertyInfo, EntityColumnInfo> entityService = null;
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
{
entityService += dependency.EntityService;
}
entityService?.Invoke(propertyInfo, columnInfo);
}
};
}
/// <summary>
/// 获取当前数据库类型
/// </summary>
/// <param name="tenantName">租户名称</param>
/// <returns>数据库类型</returns>
protected virtual DbType GetCurrentDbType(string tenantName)
{
return tenantName == ConnectionStrings.DefaultConnectionStringName
? DbConnectionOptions.DbType!.Value
: GetDbTypeFromTenantName(tenantName)
?? throw new ArgumentException($"无法从租户名称{tenantName}中解析数据库类型");
}
/// <summary>
/// 从租户名称解析数据库类型
/// 格式TenantName@DbType
/// </summary>
private DbType? GetDbTypeFromTenantName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return null;
}
var atIndex = name.LastIndexOf('@');
if (atIndex == -1 || atIndex == name.Length - 1)
{
return null;
}
var dbTypeString = name[(atIndex + 1)..];
return Enum.TryParse<DbType>(dbTypeString, out var dbType)
? dbType
: throw new ArgumentException($"不支持的数据库类型: {dbTypeString}");
}
/// <summary>
/// 备份数据库
/// </summary>
public virtual void BackupDataBase()
{
const string backupDirectory = "database_backup";
var fileName = $"{DateTime.Now:yyyyMMdd_HHmmss}_{SqlSugarClient.Ado.Connection.Database}";
Directory.CreateDirectory(backupDirectory);
switch (DbConnectionOptions.DbType)
{
case DbType.MySql:
SqlSugarClient.DbMaintenance.BackupDataBase(
SqlSugarClient.Ado.Connection.Database,
Path.Combine(backupDirectory, $"{fileName}.sql"));
break;
case DbType.Sqlite:
SqlSugarClient.DbMaintenance.BackupDataBase(
null,
$"{fileName}.db");
break;
case DbType.SqlServer:
SqlSugarClient.DbMaintenance.BackupDataBase(
SqlSugarClient.Ado.Connection.Database,
Path.Combine(backupDirectory, $"{fileName}.bak"));
break;
default:
throw new NotImplementedException($"数据库类型 {DbConnectionOptions.DbType} 的备份操作尚未实现");
}
}
}
}

View File

@@ -63,14 +63,14 @@ public class SqlSugarNonPublicSerializer : ISerializeService
// 调用 SerializeObject 方法序列化对象
T json = (T)methods.MakeGenericMethod(typeof(T))
.Invoke(null, new object[] { value, null });
return json;
.Invoke(null, new object[] { value, null! });
return json!;
}
var jSetting = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver =new NonPublicPropertiesResolver() //替换默认解析器使能支持protect
};
return JsonConvert.DeserializeObject<T>(value, jSetting);
return JsonConvert.DeserializeObject<T>(value, jSetting)!;
}
}

View File

@@ -1,37 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore
{
public static class SqlsugarCoreExtensions
{
public static IServiceCollection AddYiDbContext<DbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where DbContext : class, ISqlSugarDbContext
{
service.Replace(new ServiceDescriptor(typeof(ISqlSugarDbContext), typeof(DbContext), serviceLifetime));
return service;
}
public static IServiceCollection TryAddYiDbContext<DbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where DbContext : class, ISqlSugarDbContext
{
service.TryAdd(new ServiceDescriptor(typeof(ISqlSugarDbContext), typeof(DbContext), serviceLifetime));
return service;
}
public static IServiceCollection AddYiDbContext<DbContext>(this IServiceCollection service, Action<DbConnOptions> options) where DbContext : class, ISqlSugarDbContext
{
service.Configure<DbConnOptions>(ops =>
{
options.Invoke(ops);
});
service.AddYiDbContext<DbContext>();
return service;
}
}
}

View File

@@ -0,0 +1,112 @@
using Microsoft.Extensions.Options;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore;
/// <summary>
/// 租户配置包装器
/// </summary>
public class TenantConfigurationWrapper : ITransientDependency
{
private readonly IAbpLazyServiceProvider _serviceProvider;
private ICurrentTenant CurrentTenantService =>
_serviceProvider.LazyGetRequiredService<ICurrentTenant>();
private ITenantStore TenantStoreService =>
_serviceProvider.LazyGetRequiredService<ITenantStore>();
private DbConnOptions DbConnectionOptions =>
_serviceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
/// <summary>
/// 构造函数
/// </summary>
public TenantConfigurationWrapper(IAbpLazyServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
/// <summary>
/// 获取租户配置信息
/// </summary>
public async Task<TenantConfiguration?> GetAsync()
{
if (!DbConnectionOptions.EnabledSaasMultiTenancy)
{
return await TenantStoreService.FindAsync(ConnectionStrings.DefaultConnectionStringName);
}
return await GetTenantConfigurationByCurrentTenant();
}
private async Task<TenantConfiguration?> GetTenantConfigurationByCurrentTenant()
{
// 通过租户ID查找
if (CurrentTenantService.Id.HasValue)
{
var config = await TenantStoreService.FindAsync(CurrentTenantService.Id.Value);
if (config == null)
{
throw new ApplicationException($"未找到租户信息,租户Id:{CurrentTenantService.Id}");
}
return config;
}
// 通过租户名称查找
if (!string.IsNullOrEmpty(CurrentTenantService.Name))
{
var config = await TenantStoreService.FindAsync(CurrentTenantService.Name);
if (config == null)
{
throw new ApplicationException($"未找到租户信息,租户名称:{CurrentTenantService.Name}");
}
return config;
}
// 返回默认配置
return await TenantStoreService.FindAsync(ConnectionStrings.DefaultConnectionStringName);
}
/// <summary>
/// 获取当前连接字符串
/// </summary>
/// <returns></returns>
public async Task<string> GetCurrentConnectionStringAsync()
{
return (await GetAsync()).ConnectionStrings.Default!;
}
/// <summary>
/// 获取当前连接名
/// </summary>
/// <returns></returns>
public async Task<string> GetCurrentConnectionNameAsync()
{
return (await GetAsync()).Name;
}
}
public static class TenantConfigurationExtensions
{
/// <summary>
/// 获取当前连接字符串
/// </summary>
/// <returns></returns>
public static string GetCurrentConnectionString(this TenantConfiguration tenantConfiguration)
{
return tenantConfiguration.ConnectionStrings.Default!;
}
/// <summary>
/// 获取当前连接名
/// </summary>
/// <returns></returns>
public static string GetCurrentConnectionName(this TenantConfiguration tenantConfiguration)
{
return tenantConfiguration.Name;
}
}

View File

@@ -8,13 +8,23 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore.Uow
{
/// <summary>
/// SqlSugar数据库API实现
/// </summary>
public class SqlSugarDatabaseApi : IDatabaseApi
{
/// <summary>
/// 数据库上下文
/// </summary>
public ISqlSugarDbContext DbContext { get; }
/// <summary>
/// 初始化SqlSugar数据库API
/// </summary>
/// <param name="dbContext">数据库上下文</param>
public SqlSugarDatabaseApi(ISqlSugarDbContext dbContext)
{
DbContext = dbContext;
}
}
}
}

View File

@@ -3,33 +3,48 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore.Uow
{
/// <summary>
/// SqlSugar事务API实现
/// </summary>
public class SqlSugarTransactionApi : ITransactionApi, ISupportsRollback
{
private ISqlSugarDbContext _sqlsugarDbContext;
private readonly ISqlSugarDbContext _dbContext;
public SqlSugarTransactionApi(ISqlSugarDbContext sqlsugarDbContext)
public SqlSugarTransactionApi(ISqlSugarDbContext dbContext)
{
_sqlsugarDbContext = sqlsugarDbContext;
_dbContext = dbContext;
}
/// <summary>
/// 获取数据库上下文
/// </summary>
public ISqlSugarDbContext GetDbContext()
{
return _sqlsugarDbContext;
return _dbContext;
}
/// <summary>
/// 提交事务
/// </summary>
public async Task CommitAsync(CancellationToken cancellationToken = default)
{
await _sqlsugarDbContext.SqlSugarClient.Ado.CommitTranAsync();
}
public void Dispose()
{
_sqlsugarDbContext.SqlSugarClient.Ado.Dispose();
await _dbContext.SqlSugarClient.Ado.CommitTranAsync();
}
/// <summary>
/// 回滚事务
/// </summary>
public async Task RollbackAsync(CancellationToken cancellationToken = default)
{
await _sqlsugarDbContext.SqlSugarClient.Ado.RollbackTranAsync();
await _dbContext.SqlSugarClient.Ado.RollbackTranAsync();
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
_dbContext.SqlSugarClient.Ado.Dispose();
}
}
}

View File

@@ -13,138 +13,121 @@ namespace Yi.Framework.SqlSugarCore.Uow
{
public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext
{
private readonly ISqlSugarDbConnectionCreator _dbConnectionCreator;
/// <summary>
/// 日志记录器
/// </summary>
public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; }
/// <summary>
/// 服务提供者
/// </summary>
public IServiceProvider ServiceProvider { get; set; }
/// <summary>
/// 数据库上下文访问器实例
/// </summary>
private static AsyncLocalDbContextAccessor ContextInstance => AsyncLocalDbContextAccessor.Instance;
protected readonly IUnitOfWorkManager UnitOfWorkManager;
protected readonly IConnectionStringResolver ConnectionStringResolver;
protected readonly ICancellationTokenProvider CancellationTokenProvider;
protected readonly ICurrentTenant CurrentTenant;
private readonly TenantConfigurationWrapper _tenantConfigurationWrapper;
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IConnectionStringResolver _connectionStringResolver;
private readonly ICancellationTokenProvider _cancellationTokenProvider;
private readonly ICurrentTenant _currentTenant;
public UnitOfWorkSqlsugarDbContextProvider(
IUnitOfWorkManager unitOfWorkManager,
IConnectionStringResolver connectionStringResolver,
ICancellationTokenProvider cancellationTokenProvider,
ICurrentTenant currentTenant,
ISqlSugarDbConnectionCreator dbConnectionCreator
)
ICurrentTenant currentTenant,
TenantConfigurationWrapper tenantConfigurationWrapper)
{
UnitOfWorkManager = unitOfWorkManager;
ConnectionStringResolver = connectionStringResolver;
CancellationTokenProvider = cancellationTokenProvider;
CurrentTenant = currentTenant;
_unitOfWorkManager = unitOfWorkManager;
_connectionStringResolver = connectionStringResolver;
_cancellationTokenProvider = cancellationTokenProvider;
_currentTenant = currentTenant;
_tenantConfigurationWrapper = tenantConfigurationWrapper;
Logger = NullLogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>>.Instance;
_dbConnectionCreator = dbConnectionCreator;
}
//private static object _databaseApiLock = new object();
/// <summary>
/// 获取数据库上下文
/// </summary>
public virtual async Task<TDbContext> GetDbContextAsync()
{
var connectionStringName = ConnectionStrings.DefaultConnectionStringName;
//获取当前连接字符串,未多租户时,默认为空
var connectionString = await ResolveConnectionStringAsync(connectionStringName);
// 获取当前租户配置
var tenantConfiguration = await _tenantConfigurationWrapper.GetAsync();
// 获取连接字符串信息
var connectionStringName = tenantConfiguration.GetCurrentConnectionName();
var connectionString = tenantConfiguration.GetCurrentConnectionString();
var dbContextKey = $"{this.GetType().Name}_{connectionString}";
var unitOfWork = UnitOfWorkManager.Current;
if (unitOfWork == null /*|| unitOfWork.Options.IsTransactional == false*/)
var unitOfWork = _unitOfWorkManager.Current;
if (unitOfWork == null)
{
var dbContext = (TDbContext)ServiceProvider.GetRequiredService<ISqlSugarDbContext>();
//提高体验,取消工作单元强制性
//throw new AbpException("A DbContext can only be created inside a unit of work!");
//如果不启用工作单元创建一个新的db不开启事务即可
return dbContext;
throw new AbpException(
"DbContext 只能在工作单元内工作当前DbContext没有工作单元如需创建新线程并发操作请手动创建工作单元");
}
//尝试当前工作单元获取db
// 尝试从当前工作单元获取数据库API
var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey);
//当前没有db创建一个新的db
// 当前没有数据库API则创建新的
if (databaseApi == null)
{
//db根据连接字符串来创建
databaseApi = new SqlSugarDatabaseApi(
await CreateDbContextAsync(unitOfWork, connectionStringName, connectionString)
await CreateDbContextAsync(unitOfWork, connectionStringName, connectionString)
);
//await Console.Out.WriteLineAsync(">>>----------------实例化了db"+ ((SqlSugarDatabaseApi)databaseApi).DbContext.SqlSugarClient.ContextID.ToString());
//创建的db加入到当前工作单元中
unitOfWork.AddDatabaseApi(dbContextKey, databaseApi);
}
return (TDbContext)((SqlSugarDatabaseApi)databaseApi).DbContext;
}
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork, string connectionStringName, string connectionString)
/// <summary>
/// 创建数据库上下文
/// </summary>
protected virtual async Task<TDbContext> CreateDbContextAsync(
IUnitOfWork unitOfWork,
string connectionStringName,
string connectionString)
{
var creationContext = new SqlSugarDbContextCreationContext(connectionStringName, connectionString);
//将连接key进行传值
using (SqlSugarDbContextCreationContext.Use(creationContext))
{
var dbContext = await CreateDbContextAsync(unitOfWork);
return dbContext;
return await CreateDbContextAsync(unitOfWork);
}
}
/// <summary>
/// 根据工作单元创建数据库上下文
/// </summary>
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork)
{
return unitOfWork.Options.IsTransactional
? await CreateDbContextWithTransactionAsync(unitOfWork)
: unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
}
/// <summary>
/// 创建带事务的数据库上下文
/// </summary>
protected virtual async Task<TDbContext> CreateDbContextWithTransactionAsync(IUnitOfWork unitOfWork)
{
//事务key
var transactionApiKey = $"SqlsugarCore_{SqlSugarDbContextCreationContext.Current.ConnectionString}";
//尝试查找事务
var transactionApiKey = $"SqlSugarCore_{SqlSugarDbContextCreationContext.Current.ConnectionString}";
var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as SqlSugarTransactionApi;
//该db还没有进行开启事务
if (activeTransaction == null)
{
//获取到db添加事务即可
var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
var transaction = new SqlSugarTransactionApi(
dbContext
);
var transaction = new SqlSugarTransactionApi(dbContext);
unitOfWork.AddTransactionApi(transactionApiKey, transaction);
await dbContext.SqlSugarClient.Ado.BeginTranAsync();
return dbContext;
}
else
{
return (TDbContext)activeTransaction.GetDbContext();
}
return (TDbContext)activeTransaction.GetDbContext();
}
protected virtual async Task<string> ResolveConnectionStringAsync(string connectionStringName)
{
if (typeof(TDbContext).IsDefined(typeof(IgnoreMultiTenancyAttribute), false))
{
using (CurrentTenant.Change(null))
{
return await ConnectionStringResolver.ResolveAsync(connectionStringName);
}
}
return await ConnectionStringResolver.ResolveAsync(connectionStringName);
}
}
}

View File

@@ -6,111 +6,189 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.Domain;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Modularity;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.MultiTenancy.ConfigurationStore;
using Yi.Framework.SqlSugarCore.Abstractions;
using Yi.Framework.SqlSugarCore.Repositories;
using Yi.Framework.SqlSugarCore.Uow;
namespace Yi.Framework.SqlSugarCore
{
/// <summary>
/// SqlSugar Core模块
/// </summary>
[DependsOn(typeof(AbpDddDomainModule))]
public class YiFrameworkSqlSugarCoreModule : AbpModule
{
public override Task ConfigureServicesAsync(ServiceConfigurationContext context)
{
var service = context.Services;
var configuration = service.GetConfiguration();
var section = configuration.GetSection("DbConnOptions");
Configure<DbConnOptions>(section);
var services = context.Services;
var configuration = services.GetConfiguration();
service.TryAddScoped<ISqlSugarDbContext, SqlSugarDbContext>();
// 配置数据库连接选项
ConfigureDbOptions(services, configuration);
//不开放sqlsugarClient
//service.AddTransient<ISqlSugarClient>(x => x.GetRequiredService<ISqlsugarDbContext>().SqlSugarClient);
// 配置GUID生成器
ConfigureGuidGenerator(services);
service.AddTransient(typeof(IRepository<>), typeof(SqlSugarRepository<>));
service.AddTransient(typeof(IRepository<,>), typeof(SqlSugarRepository<,>));
service.AddTransient(typeof(ISqlSugarRepository<>), typeof(SqlSugarRepository<>));
service.AddTransient(typeof(ISqlSugarRepository<,>), typeof(SqlSugarRepository<,>));
service.AddTransient(typeof(ISugarDbContextProvider<>), typeof(UnitOfWorkSqlsugarDbContextProvider<>));
//替换Sqlsugar默认序列化器用来解决.Select()不支持嵌套对象/匿名对象的非公有访问器 值无法绑定,如Id属性
context.Services.AddSingleton<ISerializeService, SqlSugarNonPublicSerializer>();
var dbConfig = section.Get<DbConnOptions>();
//将默认db传递给abp连接字符串模块
Configure<AbpDbConnectionOptions>(x => { x.ConnectionStrings.Default = dbConfig.Url; });
// 注册仓储和服务
RegisterRepositories(services);
return Task.CompletedTask;
}
private void ConfigureDbOptions(IServiceCollection services, IConfiguration configuration)
{
var section = configuration.GetSection("DbConnOptions");
Configure<DbConnOptions>(section);
var dbConnOptions = new DbConnOptions();
section.Bind(dbConnOptions);
// 配置默认连接字符串
Configure<AbpDbConnectionOptions>(options =>
{
options.ConnectionStrings.Default = dbConnOptions.Url;
});
// 配置默认租户
ConfigureDefaultTenant(services, dbConnOptions);
}
private void ConfigureGuidGenerator(IServiceCollection services)
{
var dbConnOptions = services.GetConfiguration()
.GetSection("DbConnOptions")
.Get<DbConnOptions>();
var guidType = GetSequentialGuidType(dbConnOptions?.DbType);
Configure<AbpSequentialGuidGeneratorOptions>(options =>
{
options.DefaultSequentialGuidType = guidType;
});
}
private void RegisterRepositories(IServiceCollection services)
{
services.TryAddTransient<ISqlSugarDbContext, SqlSugarDbContextFactory>();
services.AddTransient(typeof(IRepository<>), typeof(SqlSugarRepository<>));
services.AddTransient(typeof(IRepository<,>), typeof(SqlSugarRepository<,>));
services.AddTransient(typeof(ISqlSugarRepository<>), typeof(SqlSugarRepository<>));
services.AddTransient(typeof(ISqlSugarRepository<,>), typeof(SqlSugarRepository<,>));
services.AddTransient(typeof(ISugarDbContextProvider<>), typeof(UnitOfWorkSqlsugarDbContextProvider<>));
services.AddSingleton<ISerializeService, SqlSugarNonPublicSerializer>();
services.AddYiDbContext<DefaultSqlSugarDbContext>();
}
private void ConfigureDefaultTenant(IServiceCollection services, DbConnOptions dbConfig)
{
Configure<AbpDefaultTenantStoreOptions>(options =>
{
var tenants = options.Tenants.ToList();
// 规范化租户名称
foreach (var tenant in tenants)
{
tenant.NormalizedName = tenant.Name.Contains("@")
? tenant.Name.Substring(0, tenant.Name.LastIndexOf("@"))
: tenant.Name;
}
// 添加默认租户
tenants.Insert(0, new TenantConfiguration
{
Id = Guid.Empty,
Name = ConnectionStrings.DefaultConnectionStringName,
NormalizedName = ConnectionStrings.DefaultConnectionStringName,
ConnectionStrings = new ConnectionStrings
{
{ ConnectionStrings.DefaultConnectionStringName, dbConfig.Url }
},
IsActive = true
});
options.Tenants = tenants.ToArray();
});
}
private SequentialGuidType GetSequentialGuidType(DbType? dbType)
{
return dbType switch
{
DbType.MySql or DbType.PostgreSQL => SequentialGuidType.SequentialAsString,
DbType.SqlServer => SequentialGuidType.SequentialAtEnd,
DbType.Oracle => SequentialGuidType.SequentialAsBinary,
_ => SequentialGuidType.SequentialAtEnd
};
}
public override async Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context)
{
//进行CodeFirst
var service = context.ServiceProvider;
var options = service.GetRequiredService<IOptions<DbConnOptions>>().Value;
var serviceProvider = context.ServiceProvider;
var options = serviceProvider.GetRequiredService<IOptions<DbConnOptions>>().Value;
var logger = serviceProvider.GetRequiredService<ILogger<YiFrameworkSqlSugarCoreModule>>();
var logger = service.GetRequiredService<ILogger<YiFrameworkSqlSugarCoreModule>>();
StringBuilder sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("==========Yi-SQL配置:==========");
sb.AppendLine($"数据库连接字符串:{options.Url}");
sb.AppendLine($"数据库类型:{options.DbType.ToString()}");
sb.AppendLine($"是否开启种子数据:{options.EnabledDbSeed}");
sb.AppendLine($"是否开启CodeFirst{options.EnabledCodeFirst}");
sb.AppendLine($"是否开启Saas多租户{options.EnabledSaasMultiTenancy}");
sb.AppendLine("===============================");
logger.LogInformation(sb.ToString());
//Todo准备支持多租户种子数据及CodeFirst
// 记录配置信息
LogConfiguration(logger, options);
// 初始化数据库
if (options.EnabledCodeFirst)
{
CodeFirst(service);
await InitializeDatabase(serviceProvider);
}
// 初始化种子数据
if (options.EnabledDbSeed)
{
await DataSeedAsync(service);
await InitializeSeedData(serviceProvider);
}
}
private void CodeFirst(IServiceProvider service)
private void LogConfiguration(ILogger logger, DbConnOptions options)
{
var moduleContainer = service.GetRequiredService<IModuleContainer>();
var db = service.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient;
var logMessage = new StringBuilder()
.AppendLine()
.AppendLine("==========Yi-SQL配置:==========")
.AppendLine($"数据库连接字符串:{options.Url}")
.AppendLine($"数据库类型:{options.DbType}")
.AppendLine($"是否开启种子数据:{options.EnabledDbSeed}")
.AppendLine($"是否开启CodeFirst{options.EnabledCodeFirst}")
.AppendLine($"是否开启Saas多租户{options.EnabledSaasMultiTenancy}")
.AppendLine("===============================")
.ToString();
//尝试创建数据库
logger.LogInformation(logMessage);
}
private async Task InitializeDatabase(IServiceProvider serviceProvider)
{
var moduleContainer = serviceProvider.GetRequiredService<IModuleContainer>();
var db = serviceProvider.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient;
// 创建数据库
db.DbMaintenance.CreateDatabase();
List<Type> types = new List<Type>();
foreach (var module in moduleContainer.Modules)
{
types.AddRange(module.Assembly.GetTypes()
.Where(x => x.GetCustomAttribute<IgnoreCodeFirstAttribute>() == null)
.Where(x => x.GetCustomAttribute<SugarTable>() != null)
.Where(x => x.GetCustomAttribute<SplitTableAttribute>() is null));
}
// 获取需要创建表的实体类型
var entityTypes = moduleContainer.Modules
.SelectMany(m => m.Assembly.GetTypes())
.Where(t => t.GetCustomAttribute<IgnoreCodeFirstAttribute>() == null
&& t.GetCustomAttribute<SugarTable>() != null
&& t.GetCustomAttribute<SplitTableAttribute>() == null)
.ToList();
if (types.Count > 0)
if (entityTypes.Any())
{
db.CopyNew().CodeFirst.InitTables(types.ToArray());
db.CopyNew().CodeFirst.InitTables(entityTypes.ToArray());
}
}
private async Task DataSeedAsync(IServiceProvider service)
private async Task InitializeSeedData(IServiceProvider serviceProvider)
{
var dataSeeder = service.GetRequiredService<IDataSeeder>();
var dataSeeder = serviceProvider.GetRequiredService<IDataSeeder>();
await dataSeeder.SeedAsync();
}
}

View File

@@ -0,0 +1,6 @@
namespace Yi.Framework.WeChat.MiniProgram.Abstract;
public interface IErrorObjct: IHasErrcode, IHasErrmsg
{
}

View File

@@ -0,0 +1,6 @@
namespace Yi.Framework.WeChat.MiniProgram.Abstract;
public interface IHasErrcode
{
public int errcode { get; set; }
}

View File

@@ -0,0 +1,6 @@
namespace Yi.Framework.WeChat.MiniProgram.Abstract;
public interface IHasErrmsg
{
string errmsg { get; set; }
}

View File

@@ -0,0 +1,15 @@
namespace Yi.Framework.WeChat.MiniProgram.HttpModels;
public class AccessTokenResponse
{
public string access_token { get; set; }
public int expires_in { get; set; }
}
public class AccessTokenRequest
{
public string grant_type { get; set; }
public string appid { get; set; }
public string secret { get; set; }
}

View File

@@ -0,0 +1,31 @@
using Yi.Framework.WeChat.MiniProgram.Abstract;
namespace Yi.Framework.WeChat.MiniProgram.HttpModels;
public class Code2SessionResponse: IErrorObjct
{
public string openid { get; set; }
public string session_key { get; set; }
public string unionid { get; set; }
public int errcode { get; set; }
public string errmsg { get; set; }
}
public class Code2SessionRequest
{
public string appid { get; set; }
public string secret { get; set; }
public string js_code { get; set; }
public string grant_type => "authorization_code";
}
public class Code2SessionInput
{
public Code2SessionInput(string js_code)
{
this.js_code=js_code;
}
public string js_code { get; set; }
}

View File

@@ -0,0 +1,79 @@
using Yi.Framework.WeChat.MiniProgram.Abstract;
namespace Yi.Framework.WeChat.MiniProgram.HttpModels;
public class SubscribeNoticeRequest
{
/// <summary>
///用户openid可以是小程序的openid也可以是mp_template_msg.appid对应的公众号的openid
/// </summary>
public string touser { get; set; }
/// <summary>
/// 小程序模板id
/// </summary>
public string template_id { get; set; }
/// <summary>
/// 跳转页面
/// </summary>
public string page { get; set; }
/// <summary>
/// 小程序模板消息的数据
/// </summary>
public Dictionary<string, keyValueItem> data { get; set; }
/// <summary>
/// 默认为正式版
/// </summary>
public string miniprogram_state { get; set; } = "formal";
/// <summary>
/// 默认为中文
/// </summary>
public string lang { get; set; } = "zh_CN";
}
public class SubscribeNoticeInput
{
/// <summary>
///用户openid可以是小程序的openid也可以是mp_template_msg.appid对应的公众号的openid
/// </summary>
public string touser { get; set; }
/// <summary>
/// 小程序模板id
/// </summary>
public string template_id { get; set; }
/// <summary>
/// 跳转页面
/// </summary>
public string page { get; set; }
/// <summary>
/// 公众号模板消息的数据
/// </summary>
public Dictionary<string, keyValueItem> data { get; set; }
}
public class SubscribeNoticeResponse : IErrorObjct
{
public int errcode { get; set; }
public string errmsg { get; set; }
}
public class keyValueItem
{
public keyValueItem(string value)
{
this.value = value;
}
public string value { get; set; }
}

View File

@@ -0,0 +1,20 @@
using Yi.Framework.WeChat.MiniProgram.HttpModels;
namespace Yi.Framework.WeChat.MiniProgram;
public interface IWeChatMiniProgramManager
{
/// <summary>
/// 获取用户openid
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<Code2SessionResponse> Code2SessionAsync(Code2SessionInput input);
/// <summary>
/// 向用户发送订阅消息要openid
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task SendSubscribeNoticeAsync(SubscribeNoticeInput input);
}

View File

@@ -0,0 +1,28 @@
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using Volo.Abp.Caching;
namespace Yi.Framework.WeChat.MiniProgram.Token;
internal class CacheMiniProgramToken : DefaultMinProgramToken, IMiniProgramToken
{
private IDistributedCache<string> _cache;
private const string CacheKey = "MiniProgramToken";
public CacheMiniProgramToken(IOptions<WeChatMiniProgramOptions> options, IDistributedCache<string> cache) :
base(options)
{
_cache = cache;
}
public async Task<string> GetTokenAsync()
{
return await _cache.GetOrAddAsync("MiniProgramToken", async () => { return await base.GetTokenAsync(); }, () =>
{
return new DistributedCacheEntryOptions()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2) - TimeSpan.FromMinutes(1)
};
});
}
}

View File

@@ -0,0 +1,40 @@
using System.Net.Http.Json;
using Microsoft.Extensions.Options;
using Yi.Framework.Core.Extensions;
using Yi.Framework.WeChat.MiniProgram.HttpModels;
namespace Yi.Framework.WeChat.MiniProgram.Token;
internal class DefaultMinProgramToken:IMiniProgramToken
{
private const string Url = "https://api.weixin.qq.com/cgi-bin/token";
private WeChatMiniProgramOptions _options;
public DefaultMinProgramToken(IOptions<WeChatMiniProgramOptions> options)
{
_options = options.Value;
}
public async Task<string> GetTokenAsync()
{
var token = await this.GetAccessToken();
return token.access_token;
}
public async Task<AccessTokenResponse> GetAccessToken()
{
var req = new AccessTokenRequest();
req.appid = _options.AppID;
req.secret = _options.AppSecret;
req.grant_type = "client_credential";
using (HttpClient httpClient = new HttpClient())
{
string queryString = req.ToQueryString();
var builder = new UriBuilder(Url);
builder.Query = queryString;
HttpResponseMessage response = await httpClient.GetAsync(builder.ToString());
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadFromJsonAsync<AccessTokenResponse>();
return responseBody;
}
}
}

View File

@@ -0,0 +1,6 @@
namespace Yi.Framework.WeChat.MiniProgram.Token;
public interface IMiniProgramToken
{
public Task<string> GetTokenAsync();
}

View File

@@ -0,0 +1,27 @@
namespace Yi.Framework.WeChat.MiniProgram;
public class WeChatMiniProgramException: Exception
{
public override string Message
{
get
{
// 加上前缀
return "微信Api异常: " + base.Message;
}
}
public WeChatMiniProgramException()
{
}
public WeChatMiniProgramException(string message)
: base(message)
{
}
public WeChatMiniProgramException(string message, Exception innerException)
: base(message, innerException)
{
}
}

View File

@@ -0,0 +1,50 @@
using System.Reflection;
using System.Web;
using Yi.Framework.WeChat.MiniProgram.Abstract;
namespace Yi.Framework.WeChat.MiniProgram;
public static class WeChatMiniProgramExtensions
{
/// <summary>
/// 效验请求是否成功
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
internal static void ValidateSuccess(this IErrorObjct response)
{
if (response.errcode != 0)
{
throw new WeChatMiniProgramException(response.errmsg);
}
}
internal static string ToQueryString<T>(this T obj)
{
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
var queryParams = new List<string>();
foreach (var prop in properties)
{
var value = prop.GetValue(obj, null);
if (value != null)
{
// 处理集合
if (value is IEnumerable<object> enumerable)
{
foreach (var item in enumerable)
{
queryParams.Add($"{HttpUtility.UrlEncode(prop.Name)}={HttpUtility.UrlEncode(item.ToString())}");
}
}
else
{
queryParams.Add($"{HttpUtility.UrlEncode(prop.Name)}={HttpUtility.UrlEncode(value.ToString())}");
}
}
}
return string.Join("&", queryParams);
}
}

View File

@@ -0,0 +1,77 @@
using System.Net.Http.Json;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Volo.Abp.DependencyInjection;
using Yi.Framework.Core.Extensions;
using Yi.Framework.WeChat.MiniProgram.HttpModels;
using Yi.Framework.WeChat.MiniProgram.Token;
namespace Yi.Framework.WeChat.MiniProgram;
public class WeChatMiniProgramManager : IWeChatMiniProgramManager, ISingletonDependency
{
private IMiniProgramToken _weChatToken;
private WeChatMiniProgramOptions _options;
public WeChatMiniProgramManager(IMiniProgramToken weChatToken, IOptions<WeChatMiniProgramOptions> options)
{
_weChatToken = weChatToken;
_options = options.Value;
}
/// <summary>
/// 获取用户openid
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<Code2SessionResponse> Code2SessionAsync(Code2SessionInput input)
{
string url = "https://api.weixin.qq.com/sns/jscode2session";
var req = new Code2SessionRequest();
req.js_code = input.js_code;
req.secret = _options.AppSecret;
req.appid = _options.AppID;
using (HttpClient httpClient = new HttpClient())
{
string queryString = req.ToQueryString();
var builder = new UriBuilder(url);
builder.Query = queryString;
HttpResponseMessage response = await httpClient.GetAsync(builder.ToString());
var responseBody = await response.Content.ReadFromJsonAsync<Code2SessionResponse>();
responseBody.ValidateSuccess();
return responseBody;
}
}
/// <summary>
/// 发送模板订阅消息
/// </summary>
/// <param name="input"></param>
public async Task SendSubscribeNoticeAsync(SubscribeNoticeInput input)
{
var token = await _weChatToken.GetTokenAsync();
string url = $"https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={token}";
var req = new SubscribeNoticeRequest
{
touser = input.touser,
template_id = input.template_id,
page = input.page,
data = input.data,
miniprogram_state = _options.Notice?.State??"formal"
};
req.template_id=req.template_id?? _options.Notice?.TemplateId;
using (HttpClient httpClient = new HttpClient())
{
var body =new StringContent(JsonConvert.SerializeObject(req));
HttpResponseMessage response = await httpClient.PostAsync(url, body);
var responseBody = await response.Content.ReadFromJsonAsync<SubscribeNoticeResponse>();
responseBody.ValidateSuccess();
}
}
}

View File

@@ -0,0 +1,30 @@
namespace Yi.Framework.WeChat.MiniProgram;
public class WeChatMiniProgramOptions
{
/// <summary>
/// AppId
/// </summary>
public string AppID { get; set; }
/// <summary>
/// App密钥
/// </summary>
public string AppSecret { get; set; }
/// <summary>
/// 消息
/// </summary>
public WeChatMiniProgramNoticeItem Notice { get; set; }
}
public class WeChatMiniProgramNoticeItem
{
/// <summary>
/// 模板id
/// </summary>
public string TemplateId { get; set; }
public string State { get; set; }
}

View File

@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\common.props" />
<ItemGroup>
<ProjectReference Include="..\Yi.Framework.Core\Yi.Framework.Core.csproj" />
<PackageReference Include="Volo.Abp.Caching" Version="$(AbpVersion)" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,19 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Caching;
using Yi.Framework.Core;
using Yi.Framework.WeChat.MiniProgram.Token;
namespace Yi.Framework.WeChat.MiniProgram;
[DependsOn(typeof(YiFrameworkCoreModule),
typeof(AbpCachingModule))]
public class YiFrameworkWeChatMiniProgramModule: AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var services = context.Services;
var configuration = context.Services.GetConfiguration();
Configure<WeChatMiniProgramOptions>(configuration.GetSection("WeChatMiniProgram"));
services.AddSingleton<IMiniProgramToken, CacheMiniProgramToken>();
}
}

View File

@@ -53,10 +53,10 @@ public class AuditingStore : IAuditingStore, ITransientDependency
protected virtual async Task SaveLogAsync(AuditLogInfo auditInfo)
{
Logger.LogDebug("Yi-请求追踪:" + JsonHelper.ObjToStr(auditInfo, "yyyy-MM-dd HH:mm:ss"));
// using (var uow = UnitOfWorkManager.Begin(true,isTransactional:false))
// {
await AuditLogRepository.InsertAsync(await Converter.ConvertAsync(auditInfo));
// await uow.CompleteAsync();
// }
using (var uow = UnitOfWorkManager.Begin())
{
await AuditLogRepository.InsertAsync(await Converter.ConvertAsync(auditInfo));
await uow.CompleteAsync();
}
}
}

View File

@@ -1,9 +0,0 @@
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.AccessLog
{
public class AccessLogDto
{
public long Number { get; set; }
public DateTime? LastModificationTime { get; set; }
public DateTime CreationTime { get; set; }
}
}

View File

@@ -1,20 +0,0 @@
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Analyse;
public class RegisterAnalyseDto
{
public RegisterAnalyseDto(DateTime time, int number)
{
Time = time;
Number = number;
}
/// <summary>
/// 时间
/// </summary>
public DateTime Time { get; set; }
/// <summary>
/// 人数
/// </summary>
public int Number { get; set; }
}

View File

@@ -1,24 +0,0 @@
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Argee
{
public class AgreeDto
{
public AgreeDto(bool isAgree)
{
IsAgree = isAgree;
if (isAgree)
{
Message = "点赞成功,点赞+1";
}
else
{
Message = "取消点赞,点赞-1";
}
}
public bool IsAgree { get; set; }
public string Message { get; set; }
}
}

View File

@@ -1,16 +0,0 @@
using Volo.Abp.Application.Dtos;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article
{
public class ArticleAllOutputDto : EntityDto<Guid>
{
//批量查询,不给内容,性能考虑
//public string Content { get; set; }
public string Name { get; set; }
public Guid DiscussId { get; set; }
public Guid ParentId { get; set; }
public List<ArticleAllOutputDto>? Children { get; set; }
}
}

View File

@@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article
{
/// <summary>
/// Article输入创建对象
/// </summary>
public class ArticleCreateInputVo
{
public string Content { get; set; }
public string Name { get; set; }
public Guid DiscussId { get; set; }
public Guid ParentId { get; set; }
}
}

View File

@@ -1,13 +0,0 @@
using Volo.Abp.Application.Dtos;
using Yi.Framework.Ddd.Application.Contracts;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article
{
public class ArticleGetListInputVo : PagedAllResultRequestDto
{
public string? Content { get; set; }
public string? Name { get; set; }
public Guid? DiscussId { get; set; }
public Guid? ParentId { get; set; }
}
}

View File

@@ -1,16 +0,0 @@
using Volo.Abp.Application.Dtos;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article
{
public class ArticleGetListOutputDto : EntityDto<Guid>
{
//批量查询,不给内容,性能考虑
//public string Content { get; set; }
public string Name { get; set; }
public Guid DiscussId { get; set; }
public List<ArticleGetListOutputDto>? Children { get; set; }
public DateTime CreationTime { get; set; }
}
}

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