Compare commits

..

372 Commits

Author SHA1 Message Date
橙子
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
chenchun
6b47ae232d feat:完成hangfire接入 2024-11-15 18:17:53 +08:00
chenchun
536c3cc56b feat: 支持store 缓存和redis切换 2024-11-15 17:01:39 +08:00
chenchun
b75a8cb60d feat: 全量quarzt到hangfire任务 2024-11-15 16:45:01 +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
橙子
17bc4ade84 fix: 修复审计日志错误信息 2024-11-10 17:33:20 +08:00
橙子
aea0896356 fix: 修复GetUserAsync报错问题 2024-11-10 17:23:00 +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
ae82a2d1cf style: 修改描述 2024-11-08 13:56:43 +08:00
chenchun
2412bc1da4 style: 修改开始描述 2024-11-08 13:41:03 +08:00
chenchun
42b00515eb Merge branch 'refs/heads/tool-dev' into abp 2024-11-08 12:36:26 +08:00
chenchun
f3c5d0862b feat: 完成tool 2024-11-08 12:35:54 +08:00
chenchun
e832921edf chorm: 构建 2024-11-08 11:21:10 +08:00
chenchun
0c0ead26c0 style: 删除多余 2024-11-08 11:11:27 +08:00
chenchun
f9a018638b feat: 去除sample 2024-11-08 11:10:45 +08:00
chenchun
d5ca8ddf1e feat: 完成模板从gitee上获取 2024-11-07 17:35:22 +08:00
橙子
650c29e75a !77 修正Ruoyi查询时间条件为时间的错误问题
Merge pull request !77 from Po/N/A
2024-11-06 15:51:24 +00:00
橙子
ed5c20c612 feat: 支持版本号 2024-11-06 00:05:29 +08:00
橙子
49f1d1a8fa test: 补充测试 2024-11-05 22:36:22 +08:00
橙子
a87d6345c2 feat: 完成2.0重构 2024-11-05 22:22:42 +08:00
橙子
d83db53acb feat: 完成tool搭建 2024-11-05 22:12:30 +08:00
chenchun
c944bd3b0e feat: 搭建tool 2024-11-05 18:50:15 +08:00
chenchun
751cc3cadb feat: 支持不同类型的用户id、主键 2024-11-05 16:45:30 +08:00
Po
80fe1116a8 修正Ruoyi查询时间条件为时间的错误问题
Signed-off-by: Po <448443959@qq.com>
2024-11-05 00:26:59 +00:00
chenchun
81f9fd7473 fix:修复矿池刷新数量 2024-11-04 18:54:35 +08:00
橙子
9aaa88ef51 refactor: 重构tool工具 2024-11-03 23:15:55 +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
橙子
ef2d00a254 feat: 新增bbs商城领域 2024-11-02 21:06:14 +08:00
橙子
d38159f68b chorm: 构建 2024-11-02 19:52:41 +08:00
橙子
dd29c9a2fa feat: bbs新增商城页面初版 2024-11-02 19:50:01 +08:00
橙子
ca1b8a728d feat: bbs支持滚动主题 2024-11-02 17:34:48 +08:00
橙子
6b647cf4ea feat: 合并pure pr 2024-11-02 16:17:48 +08:00
橙子
a21f2342d8 Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-02 15:48:40 +08:00
橙子
0e6f79c28e feat: 调整抽奖概率,提高 2024-11-02 15:48:28 +08:00
橙子
ab6563899c Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-02 15:35:10 +08:00
橙子
894d4eb051 fix: 修复时间 2024-11-02 15:34:53 +08:00
橙子
f82122edf0 Merge branch 'refs/heads/abp' into digital-collectibles 2024-11-02 15:29:07 +08:00
橙子
8d9c5bb762 feat: bbs完善钱钱实时变化 2024-11-02 15:28:45 +08:00
橙子
76d94c0bc9 fix: 修复定时任务 2024-11-02 13:31:18 +08:00
志福
7a916fc78e 添加Pure Config页面 2024-11-01 21:59:22 +08:00
志福
73db2a202a 增加Pure Config页面 2024-11-01 21:58:08 +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
橙子
f9890bdc7f feat: 提高审计日志记录 2024-10-31 21:21:31 +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
8a0c0de8a1 style: 增加评论长度 2024-10-30 11:49:02 +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
橙子
8c940126b5 feat: 新增yarn 2024-10-29 22:08:31 +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
橙子
71b7b7cc79 feat:api格式从newtonjson改为微软 2024-10-27 21:48:39 +08:00
橙子
77c423f421 Merge branch 'refs/heads/abp' into digital-collectibles 2024-10-26 20:27:11 +08:00
橙子
f6be4ad7ac feat: bbs支持富文本 2024-10-26 20:26:54 +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
橙子
f6cbe899c6 fix: 修复NewtonsoftJson问题 2024-10-26 15:40:45 +08:00
橙子
7598c8319f feat: 去除多余引用 2024-10-26 15:17:19 +08:00
橙子
a798f36529 feat: 补充租户引用 2024-10-26 15:15:09 +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
59d9674aeb Merge branch 'refs/heads/pr_74' into abp
# Conflicts:
#	Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/DictionaryService.cs
2024-10-25 17:31:01 +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
橙子
36ed5400be !73 字典详情前端页面解决排序不显示
Merge pull request !73 from 呆呆0518/Staging
2024-10-25 06:47:56 +00:00
hao
6378c69764 字典详情crud实体增加OrderNum,列表增加倒序 2024-10-25 14:46:26 +08:00
呆呆0518
b44db20938 前端页面解决排序不显示 2024-10-25 14:36:05 +08:00
橙子
61cd7b42d4 !72 岗位排序无效问题修复
Merge pull request !72 from 窗外的麻雀/abp
2024-10-25 02:03:22 +00:00
hao
77d64796e0 岗位排序无效 2024-10-25 09:36:56 +08:00
hao
a4001c21b1 岗位修改排序失败 2024-10-25 09:36:04 +08:00
橙子
4fadba27dc style: 优化样式 2024-10-24 21:39:20 +08:00
橙子
9e1d01774f !71 Ruoyi底部分页栏太靠右了,显示不全
Merge pull request !71 from Po/N/A
2024-10-22 06:51:13 +00:00
chenchun
7eab4dd5b1 feat: 完成账号逻辑处理 2024-10-22 12:20:53 +08:00
Po
be4f0a2a90 Ruoyi底部分页栏太靠左了,显示不全
Signed-off-by: Po <448443959@qq.com>
2024-10-22 02:23:00 +00: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
橙子
c45c17748e feat: 支持代理转发ip功能后去 2024-10-21 23:28:52 +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
橙子
57ad7ae1a3 feat: 完善请求日志过滤 2024-10-21 23:07:44 +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
chenchun
998d97b669 fix: 修复审计日志问题 2024-10-21 16:58:19 +08:00
chenchun
453d95a460 feat: 新增验证 2024-10-21 10:39:16 +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
chenchun
d55545849a fix: 修复人数访问 2024-10-18 13:14:51 +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
橙子
22ba44c271 feat: 完善部门编号字段 2024-10-15 23:26:18 +08:00
橙子
ae2cc7ad9b feat: 优化访问数量统计,采用本地缓存+分布式缓存+数据库 2024-10-15 23:07:12 +08:00
橙子
3383e86064 feat: 完成基础接口搭建 2024-10-15 22:14:14 +08:00
橙子
a0ef3af155 feat: 完成基础框架搭建 2024-10-15 21:50:31 +08:00
橙子
c880f32d33 !70 修复菜单编辑新增bug
Merge pull request !70 from fuxing168/Fyun168
2024-10-15 10:38:30 +00:00
chenchun
e8fcab4c6b feat: 新增矿池机制 2024-10-15 18:32:50 +08:00
志福
7b20b68b6a 修复菜单管理里面,编辑后或者新增没有增加routerName,会导致主菜单导航时无法显示 2024-10-15 18:31:14 +08:00
橙子
974f264272 !67 修复字段名驼峰转下划线导致查询日志列表失败的兼容问题
Merge pull request !67 from 凤凰/abp
2024-10-15 01:26:04 +00:00
凤凰
bcbb2b5139 fix: 修复字段名驼峰转下划线导致查询日志列表失败的兼容问题 2024-10-15 00:25:43 +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
橙子
e09aaa2dc7 !65 增加表单编辑器
Merge pull request !65 from 李大饼/abp
2024-10-14 08:58:56 +00:00
橙子
e3178d7579 !66 Ruoyi是否启用验证码由后台appsettings.json决定
Merge pull request !66 from Po/abp
2024-10-14 08:57:55 +00:00
Po
b59dfbc3fd Ruoyi是否启用验证码由后台appsettings.json决定 2024-10-14 16:49:33 +08:00
simiyu
0f21688b3c feat:添加表单生成器 2024-10-14 15:54:47 +08:00
橙子
801e30c1dc !64 修复Ruoyi启动的系列警告信息。
Merge pull request !64 from Po/abp
2024-10-13 10:34:46 +00:00
Po
7049175827 修复ruoyi调试警告信息 2024-10-13 18:29:02 +08:00
橙子
f27a5a135b fix: 修复找回密码问题 2024-10-13 01:04:58 +08:00
橙子
82ad9e249a !63 总算可以管理角色的用户了
Merge pull request !63 from 李大饼/abp
2024-10-12 17:01:41 +00:00
李大饼
fcff711a04 处理冲突 2024-10-12 13:42:48 +00:00
橙子
0c78b8d868 !61 fix:修复部分菜单功能下下拉菜单宽度过窄导致选中内容看不到
Merge pull request !61 from 李大饼/abp
2024-10-12 13:22:33 +00:00
李大饼
b6b54164a8 feat:添加角色用户管理,修复一堆相关问题 2024-10-12 17:16:48 +08:00
李大饼
983daddebc refactor:用户菜单界面取消默认的用户显示(主要是单元格有点宽,对于实际使用意义不大),性别列显示出来(主要是看到字典请求了,但是前端没有这一列) 2024-10-12 02:00:21 +00:00
李大饼
605db9340c refactor:用户菜单界面取消默认的用户显示(主要是单元格有点宽,对于实际使用意义不大),性别列显示出来(主要是看到字典请求了,但是前端没有这一列) 2024-10-12 09:55:39 +08:00
李大饼
36ea04bd70 fix:修复部分菜单功能下下拉菜单宽度过窄导致选中内容看不到 2024-10-12 09:39:00 +08:00
橙子
6f691e45d8 Merge remote-tracking branch 'origin/abp' into abp 2024-10-11 19:58:30 +08:00
chenchun
2c6558874d feat: 恢复备案 2024-10-11 19:58:21 +08:00
chenchun
d60b432f0c style: 恢复备案 2024-10-11 16:48:11 +08:00
橙子
adb9849650 !58 update Yi.Abp.Net8/framework/Yi.Framework.Core/Extensions/HttpContextExtensions.cs.
Merge pull request !58 from 凯明/N/A
2024-10-10 07:09:22 +00:00
橙子
48abbbf83e !60 update Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/LoginLogAggregateRoot.cs.
Merge pull request !60 from 凯明/N/A
2024-10-10 07:09:06 +00:00
橙子
9e5361338c !59 update Yi.Abp.Net8/framework/Yi.Framework.Core/Extensions/HttpContextExtensions.cs.
Merge pull request !59 from 凯明/N/A
2024-10-10 07:08:48 +00:00
凯明
c5ecd71c6e update Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/LoginLogAggregateRoot.cs.
IpTool.Search(ipAddr); 可能搜索失败报错,加入try catch

Signed-off-by: 凯明 <120665461@qq.com>
2024-10-10 06:57:13 +00:00
凯明
b09bbba21b update Yi.Abp.Net8/framework/Yi.Framework.Core/Extensions/HttpContextExtensions.cs.
//Ip规则校验 ,内网使用时,前端传递的IP会包含端口号造成校验失败    

Signed-off-by: 凯明 <120665461@qq.com>
2024-10-10 06:53:43 +00:00
凯明
2db543573c update Yi.Abp.Net8/framework/Yi.Framework.Core/Extensions/HttpContextExtensions.cs.
//Ip规则校验 ,内网使用时,前端传递的IP会包含端口号造成校验失败

Signed-off-by: 凯明 <120665461@qq.com>
2024-10-10 06:48:14 +00:00
橙子
cadd4df5d0 !57 update Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Operlog/OperLogGlobalAttribute.cs.
Merge pull request !57 from 凯明/N/A
2024-10-10 03:54:04 +00:00
凯明
427de4b42f update Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Operlog/OperLogGlobalAttribute.cs.
搜索IP归属地失败时会直接报错,改为搜索失败时保存IP地址。

Signed-off-by: 凯明 <120665461@qq.com>
2024-10-10 03:39:58 +00:00
橙子
79cb82ea24 !56 rbac/Yi.Framework.Rbac.Application.Contracts/Dtos/Menu/MenuCreateInputVo.cs.
Merge pull request !56 from 凯明/N/A
2024-10-09 12:15:15 +00:00
橙子
1b472c4ad7 !55 update Yi.Pure.Vue3/src/views/system/menu/utils/hook.tsx.
Merge pull request !55 from 凯明/N/A
2024-10-09 12:14:42 +00:00
橙子
21ab4950c8 !54 fix:修复前端表格状态列tag显示异常问题
Merge pull request !54 from 李大饼/abp
2024-10-09 12:14:16 +00:00
凯明
1b2977d591 rbac/Yi.Framework.Rbac.Application.Contracts/Dtos/Menu/MenuCreateInputVo.cs.
根目录时传入空ID会报异常

Signed-off-by: 凯明 <120665461@qq.com>
2024-10-09 09:27:32 +00:00
凯明
c54cf0bca2 update Yi.Pure.Vue3/src/views/system/menu/utils/hook.tsx.
根目录下创建菜单时,parentId为空,应该传入"00000000-0000-0000-0000-000000000000",而不是0. 

Signed-off-by: 凯明 <120665461@qq.com>
2024-10-09 09:07:08 +00:00
李大饼
935c990fe4 fix:修复前端表格状态列tag显示异常问题 2024-10-08 16:13:51 +08:00
橙子
44f94f8398 fix: 修复更新用户密码处理 2024-10-07 22:24:02 +08:00
橙子
8380cb1084 feat: 新增一键备案切换模式功能 2024-10-07 15:10:05 +08:00
橙子
83fc4f46b2 !49 pure补充头像上传逻辑
Merge pull request !49 from tyjctl/abp
2024-10-07 07:02:36 +00:00
橙子
9a97134a37 !52 解决Pure新建用户赋予权限菜单不显示问题Bug
Merge pull request !52 from Po/abp
2024-10-07 06:58:58 +00:00
橙子
912010ed70 !51 添加 PostgreSQL 数据库的配置,并新增驼峰转下划线功能
Merge pull request !51 from 凤凰/abp
2024-10-07 06:54:04 +00:00
Po
09b0bd8b09 修正Pure新建用户赋予权限菜单不显示问题 2024-10-06 22:40:17 +08:00
凤凰
c0dece8936 update Yi.RuoYi.Vue3/src/views/system/tenant/index.vue. 2024-10-05 19:40:41 +00:00
Your Name
571b610417 添加 PostgreSQL 数据库的配置,并新增驼峰转下划线功能 2024-10-06 03:32:34 +08:00
Dev Po
658339047c .gitignore排除日志目录和db文件 2024-10-05 20:19:35 +08:00
橙子
13120712b1 feat: 优化聊天室功能,修复复制等问题 2024-10-04 00:37:36 +08:00
橙子
10e1fad7f3 feat: 新增找回密码功能 2024-10-04 00:00:44 +08:00
橙子
d7629763ef feat: 新增找回密码功能接口 2024-10-03 01:10:32 +08:00
橙子
94ee0fb058 feat: 支持注册带入昵称 2024-10-02 23:25:29 +08:00
橙子
d4e8ce9c89 feat: 优化异步操作 2024-09-30 22:34:12 +08:00
橙子
707e241f25 feat: 主题展示图片 2024-09-30 00:54:28 +08:00
橙子
58fa94e8b8 feat: 新增主题图片功能 2024-09-30 00:40:47 +08:00
橙子
72d307503e style: 修改站点样式 2024-09-29 00:55:30 +08:00
橙子
d9fd9163e4 feat: 优化前端请求加载 2024-09-29 00:47:48 +08:00
chenchun
21807c3a66 Merge remote-tracking branch 'origin/abp' into abp 2024-09-25 12:09:47 +08:00
chenchun
f499d2d8a9 feat: 优化db连接字符串获取 2024-09-25 12:09:34 +08:00
tyjctl
0f9958bb26 update Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/AccountService.cs.
Signed-off-by: tyjctl <419999127@qq.com>
2024-09-24 07:13:14 +00:00
tyjctl
8e66a9880c 补充pure用户头像上传功能 2024-09-24 14:44:06 +08:00
橙子
38e112fb06 update README-en.md.
Signed-off-by: 橙子 <454313500@qq.com>
2024-09-24 03:47:34 +00:00
chenchun
bb0e48cd41 style: 新增英文readme 2024-09-24 11:45:51 +08:00
chenchun
fd0edd93ea feat: 优化rbac结构 2024-09-24 11:16:19 +08:00
chenchun
6359696bde chorm: 合并pr:https://gitee.com/ccnetcore/Yi/pulls/44/files 2024-09-24 10:24:38 +08:00
橙子
1fee392989 !45 update Yi.RuoYi.Vue3/src/views/system/user/index.vue.
Merge pull request !45 from ゞ↘絟℡℃ツ/N/A
2024-09-24 02:18:08 +00:00
橙子
dfdced9644 !46 update Yi.RuoYi.Vue3/src/views/code/field/components/TableList.vue.
Merge pull request !46 from ゞ↘絟℡℃ツ/N/A
2024-09-24 02:17:29 +00:00
ゞ↘絟℡℃ツ
e50c1c374a update Yi.RuoYi.Vue3/src/views/code/field/components/TableList.vue.
增加简单分页

Signed-off-by: ゞ↘絟℡℃ツ <137586129@qq.com>
2024-09-19 02:12:46 +00:00
ゞ↘絟℡℃ツ
dbcd051aae update Yi.RuoYi.Vue3/src/views/system/user/index.vue.
修复 用户信息编辑框, 状态单选框, 因缺少 :label="dict.value", 导致的,  无法选择单选框问题

Signed-off-by: ゞ↘絟℡℃ツ <137586129@qq.com>
2024-09-19 02:02:04 +00:00
橙子
96571bb999 !43 update Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Operlog/OperLogGlobalAttribute.cs.
Merge pull request !43 from tyjctl/N/A
2024-09-18 14:21:04 +00:00
橙子
0620f6b6e3 Merge remote-tracking branch 'origin/abp' into abp 2024-09-18 22:17:55 +08:00
橙子
ae163167b6 feat: 兼容nuget 2024-09-18 22:17:45 +08:00
tyjctl
ba0bb32b5f update Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Operlog/OperLogGlobalAttribute.cs.
OperLog保存请求参数

Signed-off-by: tyjctl <419999127@qq.com>
2024-09-18 13:41:38 +00:00
橙子
b15e789b0b !42 使用linux自带命令获取内存信息,解决在docker容器内无法监控内存信息
Merge pull request !42 from Bi8bo/abp
2024-09-18 13:40:15 +00:00
橙子
8c7afa2e7a feat: 补充缺少文件 2024-09-18 21:38:22 +08:00
Bi8bo
87e30b9edf 修复在docker 容器内无法获取内存相关信息的问题 2024-09-18 09:51:02 +08:00
Bi8bo
887ebe6f2f 修复系统监控模块 cpu核心数的bug,调整接口响应模型结构 2024-09-14 14:12:35 +08:00
橙子
099581dddc update README.md.
Signed-off-by: 橙子 <454313500@qq.com>
2024-09-09 06:14:48 +00:00
橙子
3e84890765 style: 修改readme样式 2024-09-07 15:12:05 +08:00
橙子
d2fb0791d9 style: 新增readme 说明 2024-09-07 15:09:36 +08:00
橙子
a2f22007cf chorm: 新增发布文件 2024-09-07 14:41:15 +08:00
橙子
d4dd531ac4 Merge branch 'refs/heads/pure-dev' into abp 2024-09-07 13:49:43 +08:00
橙子
8374c81860 feat: 完成ruoyi、pure、yi、bbs 三位一体 2024-09-07 13:49:25 +08:00
橙子
579f60e789 feat: 菜单种子数据 2024-09-07 13:43:28 +08:00
橙子
40b5f33c4e feat: 完成菜单接入 2024-09-07 13:43:00 +08:00
橙子
978a7fab4c feat: 完成ruoyi、pure菜单兼容 2024-09-07 02:17:07 +08:00
橙子
f7790c46d2 Merge branch 'refs/heads/pure-dev' into abp 2024-09-06 22:06:26 +08:00
chenchun
9fc5b521e5 feat: 新增pure菜单路由 2024-09-06 18:23:11 +08:00
橙子
775e31c5e9 Merge branch 'refs/heads/pure-dev' into abp 2024-09-05 23:13:18 +08:00
橙子
3339e30014 feat: 整体pure,核心功能对接完成 2024-09-05 23:10:40 +08:00
chenchun
4ed44a2a8f feat:新增个人中心api 2024-09-05 21:44:10 +08:00
橙子
6134e76030 feat: 完成在线用户、登录日志、操作日志页面 2024-09-04 23:31:42 +08:00
chenchun
e1ea210fe9 fix: 合并修复构建错误 2024-09-04 16:35:21 +08:00
橙子
d9022a0383 !33 添加前端无感刷新功能,并将token相关改为localstage存储
Merge pull request !33 from daxiongok/master
2024-09-04 08:33:00 +00:00
橙子
0b0c1405ea !40 新增支持abp实体IHasConcurrencyStamp接口,并发更新ConcurrencyStamp
Merge pull request !40 from Bi8bo/Fix-ConcurrencyStampProp-invalid
2024-09-04 08:29:58 +00:00
chenchun
e393e1f525 feat:新增岗位管理 2024-09-04 16:24:26 +08:00
橙子
ad78cb1bcd fix: 修复登录验证码样式问题 2024-09-04 00:17:49 +08:00
橙子
e886614641 feat: 完成用户管理、角色管理、菜单管理、部门管理 2024-09-02 23:26:41 +08:00
chenchun
bc83362b35 feat:新增菜单、部门页面 2024-09-02 18:16:07 +08:00
chenchun
b648f09f16 feat:完善接口定义 2024-09-02 17:16:25 +08:00
Bi8bo
eb8d1626ea 并发修改失败修改为异常抛出 2024-09-02 16:06:20 +08:00
Bi8bo
db94cd32d5 单实体更新支持abp IHasConcurrencyStamp接口,乐观锁更新 2024-09-02 15:01:49 +08:00
橙子
71fd5a13fc feat: 完成pure角色页面对接 2024-09-01 21:34:36 +08:00
橙子
67c7ef37e6 feat: 新增支持furion规范化接口格式 2024-09-01 03:06:03 +08:00
橙子
2e22f4ea67 !38 审计日志报错
Merge pull request !38 from faith/abp
2024-08-31 15:28:30 +00:00
橙子
8be36f088b feat: 完成用户页面查询 2024-08-31 22:01:55 +08:00
Administrator
b985c2c784 修改审计日志bug,表达式里只能有具体值 2024-08-31 20:24:26 +08:00
橙子
e39d381f08 Merge branch 'refs/heads/abp' into pure 2024-08-31 19:08:38 +08:00
橙子
7694c7f97b !36 解决Sqlsugar Select()映射时如果嵌套对象,实体访问修饰符为非public的属性无法绑定值
Merge pull request !36 from Bi8bo/fix-sqlsugar-select-valuebind
2024-08-31 10:51:22 +00:00
橙子
eadb5eb216 !34 修复CPU系统使用率、当前空闲率
Merge pull request !34 from GitHubList/abp
2024-08-31 10:47:28 +00:00
橙子
cf5c46b2ce feat: 完成用户页面查询 2024-08-31 12:55:04 +08:00
chenchun
6e9dd669ba feat:完善用户表单页面对接 2024-08-30 17:36:33 +08:00
chenchun
60ef93b510 feat:完善对应菜单 2024-08-30 16:08:20 +08:00
bi8bo
e3aada0fff 替换Sqlsugar默认序列化器,解决.Select()映射嵌套/匿名类时,实体的非公有访问器 值无法绑定,如Id(protect属性) 2024-08-30 14:41:45 +08:00
橙子
bbe1a44788 feat: 完成登录页面接入pure 2024-08-29 22:59:16 +08:00
chenchun
bfe0f346c8 fix:修复软删除问题 2024-08-29 10:26:07 +08:00
GitHubList
8f10146d39 修复CPU系统使用率、当前空闲率 2024-08-28 00:03:15 +08:00
chenchun
5f402488d4 feat:登录页面改造 2024-08-23 18:26:26 +08:00
chenchun
07c48479af chorm: 搭建前端 2024-08-23 17:00:18 +08:00
chenchun
4bc2cebd60 feat:新增pure-admin前端 2024-08-23 14:31:00 +08:00
daxiongok
dc242420f8 feat:添加列表排序支持和默认排序
Signed-off-by: daxiongok <571115139@qq.com>
2024-08-18 10:48:54 +08:00
daxiongok
0656e3f536 feat:默认时间倒序
Signed-off-by: daxiongok <571115139@qq.com>
2024-08-18 10:20:23 +08:00
daxiongok
cfffcda068 Merge branch 'master' of https://gitee.com/tirisfalcn/Yi.git 2024-08-18 10:06:50 +08:00
daxiongok
187885fdb9 fix:未引用方法问题
Signed-off-by: daxiongok <571115139@qq.com>
2024-08-18 10:05:26 +08:00
daxiongok
f67b60dd82 update Yi.RuoYi.Vue3/src/utils/request.js.
Signed-off-by: daxiongok <12421064+tirisfalcn@user.noreply.gitee.com>
2024-08-17 15:08:17 +00:00
daxiongok
92a2421a9b feat:新增前端token无感刷新功能
fix:前端权限码太多时,cookie太大请求异常问题。改为localstage存储token

Signed-off-by: daxiongok <571115139@qq.com>
2024-08-17 22:53:19 +08:00
chenchun
556d32729a chorm: 构建 2024-08-16 19:07:08 +08:00
chenchun
4a3bd18bac fix: 修复新手任务 2024-08-16 18:57:17 +08:00
chenchun
de8f23bf2f fix: 修复更新昵称任务 2024-08-16 18:42:48 +08:00
chenchun
3691a74d7e feat: 上线新手任务功能 2024-08-16 18:33:10 +08:00
chenchun
2aba4eccee feat: 新增新手任务 2024-08-16 17:57:58 +08:00
chenchun
3e6d02eccc Merge branch 'refs/heads/abp-dev' into abp 2024-08-16 15:32:26 +08:00
橙子
2cf244058b fix:修复点赞主题通知问题 2024-08-15 21:53:28 +08:00
橙子
971f137a21 fix;修复链接跳转 2024-08-15 21:43:15 +08:00
橙子
b1a245c2a2 Merge branch 'refs/heads/abp-dev' into abp 2024-08-15 21:39:29 +08:00
chenchun
0c1ad1f4e5 Merge remote-tracking branch 'origin/abp' into abp 2024-08-12 18:19:53 +08:00
橙子
e2a675054c !29 update Yi.RuoYi.Vue3/src/views/system/dept/index.vue.
Merge pull request !29 from songjianjack/N/A
2024-08-12 10:15:37 +00:00
songjianjack
0ad49e9b9d update Yi.RuoYi.Vue3/src/views/system/dept/index.vue.
解决新增部门无法选择上级部门

Signed-off-by: songjianjack <1400053039@qq.com>
2024-08-12 09:38:04 +00:00
1098 changed files with 119916 additions and 15205 deletions

9
.gitignore vendored
View File

@@ -20,7 +20,7 @@ x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[L]og/
# Visual Studio 2015 cache/options directory
.vs/
@@ -267,9 +267,12 @@ dist
.vscode
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Development.json
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Production.json
Logs
logs
/Yi.Abp.Net8/test/Yi.Abp.Test/appsettings.Development.json
/Yi.Abp.Net8/test/Yi.Abp.Test/appsettings.Production.json
/Yi.Abp.Net8/tool/Yi.Abp.Tool.Web/appsettings.Development.json
database_backup
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Staging.json
/Yi.Abp.Net8/src/Yi.Abp.Web/logs/
/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db
package-lock.json

213
README-en.md Normal file
View File

@@ -0,0 +1,213 @@
<h1 align="center"><img align="left" height="150px" src="https://ccnetcore.com/prod-api/wwwroot/logo.png"> Yi-Framework</h1>
<h4 align="center">A .NET 8 Web open-source Asp.NetCore framework focused on user experience.</h4>
<h5 align="center">Supports Native/Abp.vNext/Furion/Ruoyi/Pure</h5>
<h2 align="center">A comprehensive solution that ultimately becomes a wheel</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)
English | [简体中文](README.md)
****
## 🍍 Introduction:
YiFramework is a DDD (Domain-Driven Design) backend open-source framework based on .Net8, Abp.vNext, and SqlSugar.
Who says ABP is complex? Who says DDD is difficult?`Breaking conventions, simplifying complexity.`,Newcomer-friendly and one of the best approaches for project extensions.
Modular design allows for the independent inclusion or exclusion of components based on business needs. It is an all-encompassing framework where you may gain unique insights.
A Comprehensive Solution, Ultimately Just Another Wheel.
(Frequent updates, feel free to watch for continuous updates.)
— This is not just a program; it is also a work of art, focused on artistic development!
> Core Features: Simple and easy to use, the framework is not referenced in a packaged form, but is provided directly with the project alongside the source code. It offers maximum freedom and complies with the MIT license, allowing for unrestricted modifications (please indicate the source).
**Branch Directory**
- Branch **Abp**: Based on the Abp.vNext branch, DDD (Domain-Driven Design) simplifies the essence of development, providing support for multiple frontends from one backend.
- Yi.Abp.Net8Backend
- Yi.Bbs.Vue3Bbs Community - Frontend
- Yi.Doc.Md: Open Source Documentation Tutorial
- Yi.Pure.Vue3Pure TS Backend Frontend
- Yi.RuoYi.Vue3RuoYi JS Backend Frontend
****
## 🍊 Official website and demo link
Let's get straight to the point and provide the link.
YiCommunity official website URL.(Bbs)[ccnetcore.com](https://ccnetcore.com) (Now live, welcome to join!)
Rbachttps://ccnetcore.com:1000 (userName cc\password 123456)
Purehttps://ccnetcore.com:1001 userNamecc\password 123456
## 🍏 Support:
- [x] Fully supports monolithic application architecture
- [x] Fully supports distributed application architecture
- [x] Fully supports microservices architecture
****
## 🍇 Explosive Detail Yi Framework Tutorial Navigation:
1. [Framework Quick Start Guide](https://ccnetcore.com/article/aaa00329-7f35-d3fe-d258-3a0f8380b742)(Completed)
2. [Framework Functionality Module Tutorials](https://ccnetcore.com/article/8c464ab3-8ba5-2761-a4b0-3a0f83a9f312)(Completed)
3. [Practical Development Exercises](https://ccnetcore.com/article/e89c9593-f337-ada7-d108-3a0f83ae48e6)(Completed)
4. [Chengzi Ops CI/CD Tutorial](https://ccnetcore.com/article/6b80ed42-50cd-db15-c073-3a0fa8f7fd77)(Completed)
5. [Version Update Log](https://ccnetcore.com/article/e9e69a38-ce1e-06f5-7944-3a0fdc942ef3)(Completed)
****
## 🍓 Its philosophy:
Who says ABP is complicated? Who says DDD is difficult? Break the norm, simplify complexity, and serve as one of the best ways for newcomers and project second development.
> For every hundred people, there are a hundred different interpretations of DDD. The YiFramework may not strict adherence to DDD principles, but it is built on the shoulders of giants, distilled from numerous projects to craft a best practice.
Effortlessly achieve rapid development; typically, simplicity and elegance are hard to reconcile. The YiFramework does not solely pursue extreme decoupling but considers user experience and ease of use.
A user-oriented rapid development backend framework.
> Once you truly get hands-on, you'll understand this: extreme simplicity is also a form of elegance.
****
## 🍍 Features
- A user-oriented backend framework that is easy to use, suitable for small, medium, and enterprise-level projects.
- The project comes with the source code directly embedded, without packaging, making it ideal for secondary development and modification.
- Includes a large number of reusable modules for common scenarios.
- Elegantly supports distributed and microservices architectures.
- And more…
## 🥭 Core Technologies
#### Backend
C# Asp.NetCore 8.0
- [x] Dynamic API: Abp.vNext
- [x] Authentication and Authorization: Jwt
- [x] Logging: Serilog
- [x] Modularization: Abp.vNext
- [x] Dependency Injection: Autofac
- [x] Object Mapping: Mapster
- [x] ORM: SqlsugarCore
- [x] Multi-tenancy: Abp.vNext
- [x] Background Tasks: Quartz.Net
- [x] Local Caching: Abp.vNext
- [x] Distributed Caching: Abp.vNext
- [x] Event Bus: Abp.vNext
#### Frontend
js Vue3
- [x] Asynchronous Requests: axios
- [x] Charts: echarts
- [x] UI: element-plus
- [x] State Management: pinia
- [x] Routing: vue-router
- [x] Bundling: vite
#### DevOps
- [x] Deployment: nginx
- [x] CICD: gitlab+Jenkins
- [x] Docker: harbor
#### 🍉 Demo
<table>
<tr>
<td><img src="readme/101.png"/></td>
<td><img src="readme/102.png"/></td>
</tr>
<tr>
<td><img src="readme/103.png"/></td>
<td><img src="readme/104.png"/></td>
</tr>
</table>
<table>
<tr>
<td><img src="readme/201.png"/></td>
<td><img src="readme/202.png"/></td>
</tr>
<tr>
<td><img src="readme/203.png"/></td>
<td><img src="readme/204.png"/></td>
</tr>
<tr>
<td><img src="readme/205.png"/></td>
<td><img src="readme/206.png"/></td>
</tr>
</table>
<table>
<tr>
<td><img src="readme/1.png"/></td>
<td><img src="readme/2.png"/></td>
</tr>
<tr>
<td><img src="readme/3.png"/></td>
<td><img src="readme/4.png"/></td>
</tr>
<tr>
<td><img src="readme/3.png"/></td>
<td><img src="readme/4.png"/></td>
</tr>
<tr>
<td><img src="readme/5.png"/></td>
<td><img src="readme/6.png"/></td>
</tr>
<tr>
<td><img src="readme/7.png"/></td>
<td><img src="readme/8.png"/></td>
</tr>
<tr>
<td><img src="readme/9.png"/></td>
<td><img src="readme/10.png"/></td>
</tr>
<tr>
<td><img src="readme/11.png"/></td>
<td><img src="readme/12.png"/></td>
</tr>
</table>
## 🌶 Thank you
[橙子]https://ccnetcore.com
[XWen]https://gitee.com/on-wensil
[朝夕教育]https://www.zhaoxiedu.net
[Sqlsugar老杰哥]https://www.donet5.com/Home/Doc
[车神]微信公众号搜索Dotnet技术进阶
[RuYiAdmin如意老兄]https://gitee.com/pang-mingjun/RuYiAdmin
[ZrAdminNetCore字母老哥]https://gitee.com/izory/ZrAdminNetCore
[Admin.NET]https://gitee.com/zuohuaijun/Admin.NET
[Furion百小僧]https://furion.baiqian.ltd/
****
## 🌽 Contact Us:
Author's QQ`454313500`
QQ group chat官方一群Full、官方二群Full、官方三群`786308927`Full、官方四群:`498310311`Full、官方五群:`981136525`New
WeChat Group Chat官方微信一群Full、官方微信二群
WeChat Community: Add the author's WeChat chengzilaoge520 橙子老哥520,Note: Join the group.
Contact the author, everyone here is a consultant.
Official website message area[ccnetcore.com](https://ccnetcore.com)
****
## 🍄 FQA:
Visit the official website to view the message board.
[the message board](https://ccnetcore.com/discuss/1641030787056930818)

View File

@@ -1,6 +1,6 @@
<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 Vue3.0</h5>
<h5 align="center">支持Abp.vNext 版本原生版本、Furion版本前端接入Ruoyi/Pure Vue</h5>
<h2 align="center">集大成者,终究轮子</h2>
[![star](https://gitee.com/ccnetcore/yi/badge/star.svg?theme=dark)](https://gitee.com/ccnetcore/Yi)
@@ -9,7 +9,7 @@
[English](README-en.md) | 简体中文
****
## :tw-1f34e: 简介:
## 🍍 简介:
YiFramework是一个基于.Net8+Abp.vNext+SqlSugar的DDD领域驱动设计后端开源框架
谁说Abp复杂谁说DDD难`打破常规,化繁为简`,新人入门,项目二开,最佳方式之一
@@ -31,44 +31,45 @@ Yi框架-一套与SqlSugar一样爽的.Net8开源框架。
> 核心特点简单好用框架不以打包形式引用而是直接以项目附带源码给出自由度拉满遵循Mit协议允许随意修改请注明来源即可
**分支:**
**分支目录**
- (推荐) **Abp**: 基于Abp.vNext分支DDD领域驱动设计,回归开发本质,极度简单,用起来贼爽
- 分支**Abp**: 基于Abp.vNext分支DDD领域驱动设计,回归开发本质,极度简单,一个后台支持以下多个前端
- **Furion**: 基于Furion分支
- Yi.Abp.Net8后端
- Yi.Bbs.Vue3Bbs社区 前端
- Yi.Doc.Md: 开源文档教程
- Yi.Pure.Vue3Pure ts后台前端
- Yi.RuoYi.Vue3RuoYi js后台前端
****
## :tw-1f350: 官网及演示地址:
## 🍊 官网及演示地址:
废话少说直接上地址
Yi社区官网网址[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
Yi社区官网网址Bbs社区正式[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
Rbac后台管理系统:已上线,暂不提供演示地址,可本地部署访问
Rbac后台演示地址https://ccnetcore.com:1000 用户cc、密码123456
App移动端系统已上线暂不提供演示地址可本地部署访问
Pure后台演示地址https://ccnetcore.com:1001 用户cc、密码123456
Rbac演示地址https://ccnetcore.com:1000 用户cc、密码123456
## :tw-1f351: 支持:
## 🍏 支持:
- [x] 完全支持单体应用架构
- [x] 完全支持分布式应用架构
- [x] 完全支持微服务架构
****
## :tw-1f352: 详细到爆炸的Yi框架教程导航
## 🍇 详细到爆炸的Yi框架教程导航
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)
3. [实战演练开发教程](https://ccnetcore.com/article/e89c9593-f337-ada7-d108-3a0f83ae48e6)(已完成)
4. [橙子运维CICD教程](https://ccnetcore.com/article/6b80ed42-50cd-db15-c073-3a0fa8f7fd77)(已完成)
5. [版本更新日志](https://ccnetcore.com/article/e9e69a38-ce1e-06f5-7944-3a0fdc942ef3)(已完成)
****
## :tw-1f353: 它的理念:
## 🍓 它的理念:
谁说Abp复杂谁说DDD难打破常规化繁为简新人入门项目二开最佳方式之一
> 一百个人就有一百种DDDYi框架不一定是极度严格的DDD而是站在巨人的肩膀上经过极多项目的提炼摸索出一种最佳实践
@@ -78,17 +79,17 @@ Rbac演示地址https://ccnetcore.com:1000 用户cc、密码123456
> 一个面向用户的快速开发后端框架
在真正的使用,你会明白这一点,极致的简单,也是优雅的一种体现。
在真正的使用,你会明白这一点,极致的简单,也是优雅的一种体现。
****
## :tw-1f354: 特点
## 🍍 特点
- 面向用户的后端框架,使用简单,适合小型、中型、企业级项目
- 项目直接内置源码,不打包,非常适合进行二开改造
- 内置包含大量通用场景模块
- 优雅支持分布式及微服务架构
- 等等
## :tw-1f340: 基础设施简介
## 🍍 基础设施简介
以下全部功能可直接使用:
@@ -96,14 +97,14 @@ Rbac演示地址https://ccnetcore.com:1000 用户cc、密码123456
- [SqlSugar官网](https://www.donet5.com/home/doc)
## :tw-1f341: 内置模块简介
- Rbac权限管理系统已上线
## 🍅 内置模块简介
- Rbac权限管理系统已上线支持pure、ruoyi前端
- Bbs论坛社区系统已上线
> 重复的东西,无需再写一遍,这也是优雅的体现之一
****
## :tw-1f31e: 核心技术
## 🥭 核心技术
#### 后端
C# Asp.NetCore 8.0
- [x] 动态ApiAbp.vNext
@@ -120,7 +121,7 @@ C# Asp.NetCore 8.0
- [x] 事件总线Abp.vNext
#### 前端
js Vue3.2
js Vue3
- [x] 异步请求axios
- [x] 图表echarts
- [x] uielement-plus
@@ -135,9 +136,9 @@ js Vue3.2
****
## :tw-1f366: 业务支持模块:
## 🍌 业务支持模块:
#### :tw-1f42f: RABC权限管理系统持续更新
#### 🍒 RABC权限管理系统持续更新
采用ruoyi前端
- 用户管理
- 角色管理
@@ -152,9 +153,8 @@ js Vue3.2
- 定时任务
- 缓存列表
- 服务监控
- WebFirst代码生成工具
#### :tw-1f431: BBS社区论坛系统持续更新
#### 🍐 BBS社区论坛系统持续更新
采用vue3前端
- 文章功能
- 板块功能
@@ -163,7 +163,7 @@ js Vue3.2
- 授权中心
- 权限管理
#### :star: 演示截图:
#### 🍉 演示截图:
<table>
<tr>
<td><img src="readme/101.png"/></td>
@@ -174,7 +174,22 @@ js Vue3.2
<td><img src="readme/104.png"/></td>
</tr>
</table>
<table>
<tr>
<td><img src="readme/201.png"/></td>
<td><img src="readme/202.png"/></td>
</tr>
<tr>
<td><img src="readme/203.png"/></td>
<td><img src="readme/204.png"/></td>
</tr>
<tr>
<td><img src="readme/205.png"/></td>
<td><img src="readme/206.png"/></td>
</tr>
</table>
<table>
<tr>
@@ -207,7 +222,7 @@ js Vue3.2
</tr>
</table>
## :tw-1f44f: 感谢:
## 🌶 感谢:
[橙子]https://ccnetcore.com
@@ -228,12 +243,14 @@ js Vue3.2
[Furion百小僧]https://furion.baiqian.ltd/
****
## :tw-1f438: 联系我们:
## 🌽 联系我们:
作者QQ`454313500`2029年之前作者24小时在线时刻保持活跃更新。
QQ交流群官方一群已满、官方二群已满、官方三群`786308927`(已满)、官方四群:`498310311`(基本已满)、官方五群:`981136525`(新群)
微信交流群:官方微信一群(已满)、官方微信二群
微信交流群:加作者微信 chengzilaoge520 橙子老哥520备注拉群
联系作者,这里人人都是顾问
@@ -241,7 +258,7 @@ QQ交流群官方一群已满、官方二群已满、官方三群
官方网址留言区:[ccnetcore.com](https://ccnetcore.com)
****
## :tw-1f41e: FQA:
## 🍄 FQA:
前往官网查看留言区

View File

@@ -35,6 +35,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
usings.props = usings.props
version.props = version.props
publish.bat = publish.bat
publish_Demo.bat = publish_Demo.bat
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}"
@@ -79,20 +80,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.AuditLogging.S
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.AspNetCore.Authentication.OAuth", "framework\Yi.Framework.AspNetCore.Authentication.OAuth\Yi.Framework.AspNetCore.Authentication.OAuth.csproj", "{791AC2FA-50D3-4408-8D68-31DA72F608BE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{01300F0F-686E-47B3-821D-12424177867B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Web", "sample\Acme.BookStore.Web\Acme.BookStore.Web.csproj", "{576DBC97-4E5D-4444-B65C-F41649A5F8E0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Domain.Shared", "sample\Acme.BookStore.Domain.Shared\Acme.BookStore.Domain.Shared.csproj", "{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Domain", "sample\Acme.BookStore.Domain\Acme.BookStore.Domain.csproj", "{B615847F-8568-41D1-8B7E-63D61AE69F3D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Application.Contracts", "sample\Acme.BookStore.Application.Contracts\Acme.BookStore.Application.Contracts.csproj", "{20827DB5-5CDE-491A-82E8-3CAB82618C1E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Application", "sample\Acme.BookStore.Application\Acme.BookStore.Application.csproj", "{320273B6-7AE3-42DA-9675-D9AD4928A289}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.SqlSugarCore", "sample\Acme.BookStore.SqlSugarCore\Acme.BookStore.SqlSugarCore.csproj", "{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.Test", "test\Yi.Abp.Test\Yi.Abp.Test.csproj", "{68627BC2-F049-4C69-AD17-81DF9478E8CE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tenant-management", "tenant-management", "{499A8C71-7892-42D0-A77E-48756E1EFF16}"
@@ -169,6 +156,22 @@ 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("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "digital-collectibles", "digital-collectibles", "{B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Application", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Application\Yi.Framework.DigitalCollectibles.Application.csproj", "{236B88D4-F018-4A5F-A506-7458F2308C70}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Application.Contracts", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Application.Contracts\Yi.Framework.DigitalCollectibles.Application.Contracts.csproj", "{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Domain", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Domain\Yi.Framework.DigitalCollectibles.Domain.csproj", "{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Domain.Shared", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Domain.Shared\Yi.Framework.DigitalCollectibles.Domain.Shared.csproj", "{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.SqlSugarCore", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.SqlSugarCore\Yi.Framework.DigitalCollectibles.SqlSugarCore.csproj", "{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}"
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
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -275,30 +278,6 @@ Global
{791AC2FA-50D3-4408-8D68-31DA72F608BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{791AC2FA-50D3-4408-8D68-31DA72F608BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{791AC2FA-50D3-4408-8D68-31DA72F608BE}.Release|Any CPU.Build.0 = Release|Any CPU
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Release|Any CPU.Build.0 = Release|Any CPU
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Release|Any CPU.Build.0 = Release|Any CPU
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Release|Any CPU.Build.0 = Release|Any CPU
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Release|Any CPU.Build.0 = Release|Any CPU
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Debug|Any CPU.Build.0 = Debug|Any CPU
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Release|Any CPU.ActiveCfg = Release|Any CPU
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Release|Any CPU.Build.0 = Release|Any CPU
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Release|Any CPU.Build.0 = Release|Any CPU
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -427,6 +406,34 @@ 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
{236B88D4-F018-4A5F-A506-7458F2308C70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{236B88D4-F018-4A5F-A506-7458F2308C70}.Debug|Any CPU.Build.0 = Debug|Any CPU
{236B88D4-F018-4A5F-A506-7458F2308C70}.Release|Any CPU.ActiveCfg = Release|Any CPU
{236B88D4-F018-4A5F-A506-7458F2308C70}.Release|Any CPU.Build.0 = Release|Any CPU
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Release|Any CPU.Build.0 = Release|Any CPU
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Release|Any CPU.Build.0 = Release|Any CPU
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Release|Any CPU.Build.0 = Release|Any CPU
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.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
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -460,12 +467,6 @@ Global
{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}
{576DBC97-4E5D-4444-B65C-F41649A5F8E0} = {01300F0F-686E-47B3-821D-12424177867B}
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D} = {01300F0F-686E-47B3-821D-12424177867B}
{B615847F-8568-41D1-8B7E-63D61AE69F3D} = {01300F0F-686E-47B3-821D-12424177867B}
{20827DB5-5CDE-491A-82E8-3CAB82618C1E} = {01300F0F-686E-47B3-821D-12424177867B}
{320273B6-7AE3-42DA-9675-D9AD4928A289} = {01300F0F-686E-47B3-821D-12424177867B}
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7} = {01300F0F-686E-47B3-821D-12424177867B}
{68627BC2-F049-4C69-AD17-81DF9478E8CE} = {0D10EEF2-FBAE-4C72-B816-A52823FC299B}
{499A8C71-7892-42D0-A77E-48756E1EFF16} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{FA5BBAA1-08DC-472F-BB2C-5314E59D1556} = {499A8C71-7892-42D0-A77E-48756E1EFF16}
@@ -502,6 +503,14 @@ 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}
{B8F76A6B-2EEB-4E64-9F26-D84584E16B9C} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{236B88D4-F018-4A5F-A506-7458F2308C70} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
{81CEA2ED-917B-41D8-BE0D-39A785B050C0} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
{862CA181-BEE6-4870-82D2-B662E527ED8C} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {23D6FBC9-C970-4641-BC1E-2AEA59F51C18}

View File

@@ -9,84 +9,89 @@ using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Options;
namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
{
public static class SwaggerAddExtensions
{
public static IServiceCollection AddYiSwaggerGen<Program>(this IServiceCollection services, Action<SwaggerGenOptions>? action=null)
public static IServiceCollection AddYiSwaggerGen<Program>(this IServiceCollection services,
Action<SwaggerGenOptions>? action = null)
{
var serviceProvider = services.BuildServiceProvider();
var mvcOptions = serviceProvider.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>();
var mvcSettings = mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName);
var mvcOptions = services.GetPreConfigureActions<AbpAspNetCoreMvcOptions>().Configure();
var mvcSettings =
mvcOptions.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName);
services.AddAbpSwaggerGen(
options =>
{
if (action is not null)
options =>
{
action.Invoke(options);
}
// 配置分组,还需要去重,支持重写,如果外部传入后,将以外部为准
foreach (var setting in mvcSettings.OrderBy(x => x.RemoteServiceName))
{
if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName))
if (action is not null)
{
options.SwaggerDoc(setting.RemoteServiceName, new OpenApiInfo { Title = setting.RemoteServiceName, Version = "v1" });
action.Invoke(options);
}
}
// 根据分组名称过滤 API 文档
options.DocInclusionPredicate((docName, apiDesc) =>
{
if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
// 配置分组,还需要去重,支持重写,如果外部传入后,将以外部为准
foreach (var setting in mvcSettings.OrderBy(x => x.RemoteServiceName))
{
var settingOrNull = mvcSettings.Where(x => x.Assembly == controllerActionDescriptor.ControllerTypeInfo.Assembly).FirstOrDefault();
if (settingOrNull is not null)
if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName))
{
return docName == settingOrNull.RemoteServiceName;
options.SwaggerDoc(setting.RemoteServiceName,
new OpenApiInfo { Title = setting.RemoteServiceName, Version = "v1" });
}
}
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"))
// 根据分组名称过滤 API 文档
options.DocInclusionPredicate((docName, apiDesc) =>
{
options.IncludeXmlComments(item, true);
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>();
}
);
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;
}
@@ -103,7 +108,6 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
/// </summary>
/// <param name="model"></param>
/// <param name="context"></param>
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
if (context.Type.IsEnum)
@@ -112,7 +116,7 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
model.Type = "string";
model.Format = null;
StringBuilder stringBuilder = new StringBuilder();
Enum.GetNames(context.Type)
.ToList()
@@ -121,9 +125,10 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
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 />");
stringBuilder.Append(
$"【枚举:{name}{(descrptionOrNull is null ? string.Empty : $"({descrptionOrNull})")}={Convert.ToInt64(Enum.Parse(context.Type, name))}】<br />");
});
model.Description= stringBuilder.ToString();
model.Description = stringBuilder.ToString();
}
}
@@ -133,13 +138,13 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : null;
}
}
public class AddRequiredHeaderParameter : IOperationFilter
{
public static string HeaderKey { get; set; } = "__tenant";
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
@@ -150,8 +155,8 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
In = ParameterLocation.Header,
Required = false,
AllowEmptyValue = true,
Description="租户id或者租户名称可空为默认租户"
Description = "租户id或者租户名称可空为默认租户"
});
}
}
}
}

View File

@@ -0,0 +1,37 @@
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Volo.Abp.AspNetCore.WebClientInfo;
namespace Yi.Framework.AspNetCore;
public class RealIpHttpContextWebClientInfoProvider : HttpContextWebClientInfoProvider
{
public RealIpHttpContextWebClientInfoProvider(ILogger<HttpContextWebClientInfoProvider> logger,
IHttpContextAccessor httpContextAccessor) : base(logger, httpContextAccessor)
{
}
protected override string? GetClientIpAddress()
{
try
{
var httpContext = HttpContextAccessor.HttpContext;
var headers = httpContext?.Request?.Headers;
if (headers != null && headers.ContainsKey("X-Forwarded-For"))
{
httpContext.Connection.RemoteIpAddress =
IPAddress.Parse(headers["X-Forwarded-For"].FirstOrDefault());
}
return httpContext?.Connection?.RemoteIpAddress?.ToString();
}
catch (Exception ex)
{
Logger.LogException(ex, LogLevel.Warning);
return null;
}
}
}

View File

@@ -0,0 +1,46 @@
// MIT 许可证
//
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
//
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
//
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
//
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
namespace Yi.Framework.AspNetCore.UnifyResult;
/// <summary>
/// 异常元数据
/// </summary>
public sealed class ExceptionMetadata
{
/// <summary>
/// 状态码
/// </summary>
public int StatusCode { get; internal set; }
/// <summary>
/// 错误码
/// </summary>
public object ErrorCode { get; internal set; }
/// <summary>
/// 错误码(没被复写过的 ErrorCode
/// </summary>
public object OriginErrorCode { get; internal set; }
/// <summary>
/// 错误对象(信息)
/// </summary>
public object Errors { get; internal set; }
/// <summary>
/// 额外数据
/// </summary>
public object Data { get; internal set; }
}

View File

@@ -0,0 +1,106 @@
// MIT 许可证
//
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
//
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
//
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
//
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.DependencyInjection;
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;
// 如果异常在其他地方被标记了处理,那么这里不再处理
if (context.ExceptionHandled) return;
// 解析异常信息
var exceptionMetadata = GetExceptionMetadata(context);
IUnifyResultProvider unifyResult = context.GetRequiredService<IUnifyResultProvider>();
// 执行规范化异常处理
context.Result = unifyResult.OnException(context, exceptionMetadata);
// 创建日志记录器
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<FriendlyExceptionFilter>>();
// 记录拦截日常
logger.LogError(context.Exception, context.Exception.Message);
}
/// <summary>
/// 获取异常元数据
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static ExceptionMetadata GetExceptionMetadata(ActionContext context)
{
object errorCode = default;
object originErrorCode = default;
object errors = default;
object data = default;
var statusCode = StatusCodes.Status500InternalServerError;
var isValidationException = false; // 判断是否是验证异常
var isFriendlyException = false;
// 判断是否是 ExceptionContext 或者 ActionExecutedContext
var exception = context is ExceptionContext exContext
? exContext.Exception
: (
context is ActionExecutedContext edContext
? edContext.Exception
: default
);
// 判断是否是友好异常
if (exception is UserFriendlyException friendlyException)
{
int statusCode2 = 500;
int.TryParse(friendlyException.Code, out statusCode2);
isFriendlyException = true;
errorCode = friendlyException.Code;
originErrorCode = friendlyException.Code;
statusCode = statusCode2==0?403:statusCode2;
isValidationException = false;
errors = friendlyException.Message;
data = friendlyException.Data;
}
return new ExceptionMetadata
{
StatusCode = statusCode,
ErrorCode = errorCode,
OriginErrorCode = originErrorCode,
Errors = errors,
Data = data
};
}
}

View File

@@ -0,0 +1,276 @@
using System.Collections;
using System.Reflection;
using System.Text.Encodings.Web;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.DependencyInjection;
using Yi.Framework.Core.Extensions;
namespace Yi.Framework.AspNetCore.UnifyResult.Fiters;
/// <summary>
/// 规范化结构(请求成功)过滤器
/// </summary>
public class SucceededUnifyResultFilter : IAsyncActionFilter, IOrderedFilter
{
/// <summary>
/// 过滤器排序
/// </summary>
private const int FilterOrder = 8888;
/// <summary>
/// 排序属性
/// </summary>
public int Order => FilterOrder;
/// <summary>
/// 处理规范化结果
/// </summary>
/// <param name="context"></param>
/// <param name="next"></param>
/// <returns></returns>
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// 执行 Action 并获取结果
var actionExecutedContext = await next();
// 排除 WebSocket 请求处理
if (actionExecutedContext.HttpContext.IsWebSocketRequest()) return;
// 处理已经含有状态码结果的 Result
if (actionExecutedContext.Result is IStatusCodeActionResult statusCodeResult &&
statusCodeResult.StatusCode != null)
{
// 小于 200 或者 大于 299 都不是成功值,直接跳过
if (statusCodeResult.StatusCode.Value < 200 || statusCodeResult.StatusCode.Value > 299)
{
// 处理规范化结果
if (!CheckStatusCodeNonUnify(context.HttpContext, out var unifyRes))
{
var httpContext = context.HttpContext;
var statusCode = statusCodeResult.StatusCode.Value;
// 解决刷新 Token 时间和 Token 时间相近问题
if (statusCodeResult.StatusCode.Value == StatusCodes.Status401Unauthorized
&& httpContext.Response.Headers.ContainsKey("access-token")
&& httpContext.Response.Headers.ContainsKey("x-access-token"))
{
httpContext.Response.StatusCode = statusCode = StatusCodes.Status403Forbidden;
}
// 如果 Response 已经完成输出,则禁止写入
if (httpContext.Response.HasStarted) return;
await unifyRes.OnResponseStatusCodes(httpContext, statusCode,
httpContext.RequestServices.GetService<IOptions<UnifyResultSettingsOptions>>()?.Value);
}
return;
}
}
// 如果出现异常,则不会进入该过滤器
if (actionExecutedContext.Exception != null) return;
// 获取控制器信息
var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
// 判断是否支持 MVC 规范化处理,检测配置而已
// if (!UnifyContext.CheckSupportMvcController(context.HttpContext, actionDescriptor, out _)) return;
// 判断是否跳过规范化处理检测NonUnifyAttribute而已
if (CheckSucceededNonUnify(actionDescriptor.MethodInfo))
{
return;
}
IUnifyResultProvider unifyResult = context.GetRequiredService<IUnifyResultProvider>();
// 处理 BadRequestObjectResult 类型规范化处理
if (actionExecutedContext.Result is BadRequestObjectResult badRequestObjectResult)
{
// 解析验证消息
var validationMetadata = GetValidationMetadata(badRequestObjectResult.Value);
var result = unifyResult.OnValidateFailed(context, validationMetadata);
if (result != null) actionExecutedContext.Result = result;
}
else
{
IActionResult result = default;
// 检查是否是有效的结果(可进行规范化的结果)
if (CheckVaildResult(actionExecutedContext.Result, out var data))
{
result = unifyResult.OnSucceeded(actionExecutedContext, data);
}
// 如果是不能规范化的结果类型,则跳过
if (result == null) return;
actionExecutedContext.Result = result;
}
}
/// <summary>
/// 获取验证错误信息
/// </summary>
/// <param name="errors"></param>
/// <returns></returns>
private static ValidationMetadata GetValidationMetadata(object errors)
{
ModelStateDictionary _modelState = null;
object validationResults = null;
(string message, string firstErrorMessage, string firstErrorProperty) = (default, default, default);
// 判断是否是集合类型
if (errors is IEnumerable && errors is not string)
{
// 如果是模型验证字典类型
if (errors is ModelStateDictionary modelState)
{
_modelState = modelState;
// 将验证错误信息转换成字典并序列化成 Json
validationResults = modelState.Where(u => modelState[u.Key].ValidationState == ModelValidationState.Invalid)
.ToDictionary(u => u.Key, u => modelState[u.Key].Errors.Select(c => c.ErrorMessage).ToArray());
}
// 如果是 ValidationProblemDetails 特殊类型
else if (errors is ValidationProblemDetails validation)
{
validationResults = validation.Errors
.ToDictionary(u => u.Key, u => u.Value.ToArray());
}
// 如果是字典类型
else if (errors is Dictionary<string, string[]> dicResults)
{
validationResults = dicResults;
}
message = JsonSerializer.Serialize(validationResults, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true
});
firstErrorMessage = (validationResults as Dictionary<string, string[]>).First().Value[0];
firstErrorProperty = (validationResults as Dictionary<string, string[]>).First().Key;
}
// 其他类型
else
{
validationResults = firstErrorMessage = message = errors?.ToString();
}
return new ValidationMetadata
{
ValidationResult = validationResults,
Message = message,
ModelState = _modelState,
FirstErrorProperty = firstErrorProperty,
FirstErrorMessage = firstErrorMessage
};
}
/// <summary>
/// 检查是否是有效的结果(可进行规范化的结果)
/// </summary>
/// <param name="result"></param>
/// <param name="data"></param>
/// <returns></returns>
private bool CheckVaildResult(IActionResult result, out object data)
{
data = default;
// 排除以下结果,跳过规范化处理
var isDataResult = result switch
{
ViewResult => false,
PartialViewResult => false,
FileResult => false,
ChallengeResult => false,
SignInResult => false,
SignOutResult => false,
RedirectToPageResult => false,
RedirectToRouteResult => false,
RedirectResult => false,
RedirectToActionResult => false,
LocalRedirectResult => false,
ForbidResult => false,
ViewComponentResult => false,
PageResult => false,
NotFoundResult => false,
NotFoundObjectResult => false,
_ => true,
};
// 目前支持返回值 ActionResult
if (isDataResult) data = result switch
{
// 处理内容结果
ContentResult content => content.Content,
// 处理对象结果
ObjectResult obj => obj.Value,
// 处理 JSON 对象
JsonResult json => json.Value,
_ => null,
};
return isDataResult;
}
/// <summary>
/// 检查短路状态码(>=400是否进行规范化处理
/// </summary>
/// <param name="context"></param>
/// <param name="unifyResult"></param>
/// <returns>返回 true 跳过处理,否则进行规范化处理</returns>
internal static bool CheckStatusCodeNonUnify(HttpContext context, out IUnifyResultProvider unifyResult)
{
// 获取终点路由特性
var endpointFeature = context.Features.Get<IEndpointFeature>();
if (endpointFeature == null) return (unifyResult = null) == null;
// 判断是否跳过规范化处理
var isSkip = context.GetEndpoint()?.Metadata?.GetMetadata<NonUnifyAttribute>()!= null
|| endpointFeature?.Endpoint?.Metadata?.GetMetadata<NonUnifyAttribute>() != null
|| context.Request.Headers["accept"].ToString().Contains("odata.metadata=", StringComparison.OrdinalIgnoreCase)
|| context.Request.Headers["accept"].ToString().Contains("odata.streaming=", StringComparison.OrdinalIgnoreCase);
if (isSkip == true) unifyResult = null;
else
{
unifyResult = context.RequestServices.GetRequiredService<IUnifyResultProvider>();
}
return unifyResult == null || isSkip;
}
/// <summary>
/// 检查请求成功是否进行规范化处理
/// </summary>
/// <param name="method"></param>
/// <param name="isWebRequest"></param>
/// <returns>返回 true 跳过处理,否则进行规范化处理</returns>
private bool CheckSucceededNonUnify(MethodInfo method, bool isWebRequest = true)
{
// 判断是否跳过规范化处理
var isSkip = method.CustomAttributes.Any(x => typeof(NonUnifyAttribute).IsAssignableFrom(x.AttributeType) || typeof(ProducesResponseTypeAttribute).IsAssignableFrom(x.AttributeType) || typeof(IApiResponseMetadataProvider).IsAssignableFrom(x.AttributeType))
|| method.ReflectedType.IsDefined(typeof(NonUnifyAttribute), true)
|| method.DeclaringType.Assembly.GetName().Name.StartsWith("Microsoft.AspNetCore.OData");
if (!isWebRequest)
{
return isSkip;
}
return isSkip;
}
}

View File

@@ -0,0 +1,58 @@
// MIT 许可证
//
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
//
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
//
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
//
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Yi.Framework.AspNetCore.UnifyResult;
/// <summary>
/// 规范化结果提供器
/// </summary>
public interface IUnifyResultProvider
{
/// <summary>
/// 异常返回值
/// </summary>
/// <param name="context"></param>
/// <param name="metadata"></param>
/// <returns></returns>
IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata);
/// <summary>
/// 成功返回值
/// </summary>
/// <param name="context"></param>
/// <param name="data"></param>
/// <returns></returns>
IActionResult OnSucceeded(ActionExecutedContext context, object data);
/// <summary>
/// 验证失败返回值
/// </summary>
/// <param name="context"></param>
/// <param name="metadata"></param>
/// <returns></returns>
IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata);
/// <summary>
/// 拦截返回状态码
/// </summary>
/// <param name="context"></param>
/// <param name="statusCode"></param>
/// <param name="unifyResultSettings"></param>
/// <returns></returns>
Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings = default);
}

View File

@@ -0,0 +1,23 @@
// MIT 许可证
//
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
//
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
//
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
//
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
namespace Yi.Framework.AspNetCore.UnifyResult;
/// <summary>
/// 禁止规范化处理
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public sealed class NonUnifyAttribute : Attribute
{
}

View File

@@ -0,0 +1,136 @@
// MIT 许可证
//
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
//
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
//
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
//
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Volo.Abp.DependencyInjection;
namespace Yi.Framework.AspNetCore.UnifyResult.Providers;
/// <summary>
/// RESTful 风格返回值
/// </summary>
[Dependency(TryRegister = true)]
[ExposeServices(typeof(IUnifyResultProvider))]
public class RESTfulResultProvider : IUnifyResultProvider,ITransientDependency
{
/// <summary>
/// 设置响应状态码
/// </summary>
/// <param name="context"></param>
/// <param name="statusCode"></param>
/// <param name="unifyResultSettings"></param>
public static void SetResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings)
{
if (unifyResultSettings == null) return;
// 篡改响应状态码
if (unifyResultSettings.AdaptStatusCodes != null && unifyResultSettings.AdaptStatusCodes.Length > 0)
{
var adaptStatusCode = unifyResultSettings.AdaptStatusCodes.FirstOrDefault(u => u[0] == statusCode);
if (adaptStatusCode != null && adaptStatusCode.Length > 0 && adaptStatusCode[0] > 0)
{
context.Response.StatusCode = adaptStatusCode[1];
return;
}
}
// 如果为 null则所有请求错误的状态码设置为 200
if (unifyResultSettings.Return200StatusCodes == null) context.Response.StatusCode = 200;
// 否则只有里面的才设置为 200
else if (unifyResultSettings.Return200StatusCodes.Contains(statusCode)) context.Response.StatusCode = 200;
else { }
}
/// <summary>
/// 异常返回值
/// </summary>
/// <param name="context"></param>
/// <param name="metadata"></param>
/// <returns></returns>
public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata)
{
return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, errors: metadata.Errors));
}
/// <summary>
/// 成功返回值
/// </summary>
/// <param name="context"></param>
/// <param name="data"></param>
/// <returns></returns>
public IActionResult OnSucceeded(ActionExecutedContext context, object data)
{
return new JsonResult(RESTfulResult(StatusCodes.Status200OK, true, data));
}
/// <summary>
/// 验证失败/业务异常返回值
/// </summary>
/// <param name="context"></param>
/// <param name="metadata"></param>
/// <returns></returns>
public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata)
{
return new JsonResult(RESTfulResult(metadata.StatusCode ?? StatusCodes.Status400BadRequest, data: metadata.Data, errors: metadata.ValidationResult));
}
/// <summary>
/// 特定状态码返回值
/// </summary>
/// <param name="context"></param>
/// <param name="statusCode"></param>
/// <param name="unifyResultSettings"></param>
/// <returns></returns>
public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings)
{
// 设置响应状态码
SetResponseStatusCodes(context, statusCode, unifyResultSettings);
switch (statusCode)
{
// 处理 401 状态码
case StatusCodes.Status401Unauthorized:
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "401 Unauthorized"));
break;
// 处理 403 状态码
case StatusCodes.Status403Forbidden:
await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "403 Forbidden"));
break;
default: break;
}
}
/// <summary>
/// 返回 RESTful 风格结果集
/// </summary>
/// <param name="statusCode"></param>
/// <param name="succeeded"></param>
/// <param name="data"></param>
/// <param name="errors"></param>
/// <returns></returns>
public static RESTfulResult<object> RESTfulResult(int statusCode, bool succeeded = default, object data = default, object errors = default)
{
return new RESTfulResult<object>
{
StatusCode = statusCode,
Succeeded = succeeded,
Data = data,
Errors = errors,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};
}
}

View File

@@ -0,0 +1,52 @@
// MIT 许可证
//
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
//
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
//
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
//
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
namespace Yi.Framework.AspNetCore.UnifyResult;
/// <summary>
/// RESTful 风格结果集
/// </summary>
/// <typeparam name="T"></typeparam>
public class RESTfulResult<T>
{
/// <summary>
/// 状态码
/// </summary>
public int? StatusCode { get; set; }
/// <summary>
/// 数据
/// </summary>
public T Data { get; set; }
/// <summary>
/// 执行成功
/// </summary>
public bool Succeeded { get; set; }
/// <summary>
/// 错误信息
/// </summary>
public object Errors { get; set; }
/// <summary>
/// 附加数据
/// </summary>
public object Extras { get; set; }
/// <summary>
/// 时间戳
/// </summary>
public long Timestamp { get; set; }
}

View File

@@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
using Yi.Framework.AspNetCore.UnifyResult.Fiters;
namespace Yi.Framework.AspNetCore.UnifyResult;
/// <summary>
/// 规范化接口
/// 由于太多人反应想兼容一套类似furion的返回情况200状态码包一层更符合国内习惯既然如此不如直接搬过来
/// </summary>
public static class UnifyResultExtensions
{
public static IServiceCollection AddFurionUnifyResultApi(this IServiceCollection services)
{
//成功规范接口
services.AddTransient<SucceededUnifyResultFilter>();
//异常规范接口
services.AddTransient<FriendlyExceptionFilter>();
services.AddMvc(options =>
{
options.Filters.AddService<SucceededUnifyResultFilter>(99);
options.Filters.AddService<FriendlyExceptionFilter>(100);
options.Filters.RemoveAll(x => (x as ServiceFilterAttribute)?.ServiceType == typeof(AbpExceptionFilter));
});
return services;
}
}

View File

@@ -0,0 +1,50 @@
// MIT 许可证
//
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
//
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
//
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
//
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
using Microsoft.Extensions.Configuration;
namespace Yi.Framework.AspNetCore.UnifyResult;
/// <summary>
/// 规范化配置选项
/// </summary>
public sealed class UnifyResultSettingsOptions
{
/// <summary>
/// 设置返回 200 状态码列表
/// <para>默认401403如果设置为 null则标识所有状态码都返回 200 </para>
/// </summary>
public int[] Return200StatusCodes { get; set; }
/// <summary>
/// 适配篡改Http 状态码(只支持短路状态码,比如 401403500 等)
/// </summary>
public int[][] AdaptStatusCodes { get; set; }
/// <summary>
/// 是否支持 MVC 控制台规范化处理
/// </summary>
public bool? SupportMvcController { get; set; }
/// <summary>
/// 选项后期配置
/// </summary>
/// <param name="options"></param>
/// <param name="configuration"></param>
public void PostConfigure(UnifyResultSettingsOptions options, IConfiguration configuration)
{
options.Return200StatusCodes ??= new[] { 401, 403 };
options.SupportMvcController ??= false;
}
}

View File

@@ -0,0 +1,69 @@
// MIT 许可证
//
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
//
// 特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,
// 包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,
// 以及再授权被配发了本软件的人如上的权利,须在下列条件下:
//
// 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。
//
// 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。
// 在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,
// 还是产生于、源于或有关于本软件以及本软件的使用或其它处置。
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Yi.Framework.AspNetCore.UnifyResult;
/// <summary>
/// 验证信息元数据
/// </summary>
public sealed class ValidationMetadata
{
/// <summary>
/// 验证结果
/// </summary>
/// <remarks>返回字典或字符串类型</remarks>
public object ValidationResult { get; internal set; }
/// <summary>
/// 异常消息
/// </summary>
public string Message { get; internal set; }
/// <summary>
/// 验证状态
/// </summary>
public ModelStateDictionary ModelState { get; internal set; }
/// <summary>
/// 错误码
/// </summary>
public object ErrorCode { get; internal set; }
/// <summary>
/// 错误码(没被复写过的 ErrorCode
/// </summary>
public object OriginErrorCode { get; internal set; }
/// <summary>
/// 状态码
/// </summary>
public int? StatusCode { get; internal set; }
/// <summary>
/// 首个错误属性
/// </summary>
public string FirstErrorProperty { get; internal set; }
/// <summary>
/// 首个错误消息
/// </summary>
public string FirstErrorMessage { get; internal set; }
/// <summary>
/// 额外数据
/// </summary>
public object Data { get; internal set; }
}

View File

@@ -11,6 +11,7 @@ using Newtonsoft.Json.Linq;
using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.WebClientInfo;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Modularity;
using Yi.Framework.AspNetCore.Mvc;
@@ -22,6 +23,11 @@ namespace Yi.Framework.AspNetCore
)]
public class YiFrameworkAspNetCoreModule : AbpModule
{
public override void PostConfigureServices(ServiceConfigurationContext context)
{
var services = context.Services;
services.Replace(new ServiceDescriptor(typeof(IWebClientInfoProvider),
typeof(RealIpHttpContextWebClientInfoProvider), ServiceLifetime.Transient));
}
}
}

View File

@@ -0,0 +1,45 @@
using Hangfire.Server;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
namespace Yi.Framework.BackgroundWorkers.Hangfire;
public class UnitOfWorkHangfireFilter : IServerFilter, ISingletonDependency
{
private const string CurrentJobUow = "HangfireUnitOfWork";
private readonly IUnitOfWorkManager _unitOfWorkManager;
public UnitOfWorkHangfireFilter(IUnitOfWorkManager unitOfWorkManager)
{
_unitOfWorkManager = unitOfWorkManager;
}
public void OnPerforming(PerformingContext context)
{
var uow = _unitOfWorkManager.Begin();
context.Items.Add(CurrentJobUow, uow);
}
public void OnPerformed(PerformedContext context)
{
AsyncHelper.RunSync(()=>OnPerformedAsync(context));
}
private async Task OnPerformedAsync(PerformedContext context)
{
if (context.Items.TryGetValue(CurrentJobUow, out var obj)
&& obj is IUnitOfWork uow)
{
if (context.Exception == null && !uow.IsCompleted)
{
await uow.CompleteAsync();
}
else
{
await uow.RollbackAsync();
}
uow.Dispose();
}
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\common.props" />
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.BackgroundWorkers.Hangfire" Version="$(AbpVersion)" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,35 @@
using Hangfire;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.BackgroundWorkers.Hangfire;
namespace Yi.Framework.BackgroundWorkers.Hangfire;
[DependsOn(typeof(AbpBackgroundWorkersHangfireModule))]
public class YiFrameworkBackgroundWorkersHangfireModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddConventionalRegistrar(new YiHangfireConventionalRegistrar());
}
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
//定时任务自动注入Abp默认只有在Quartz才实现
var backgroundWorkerManager = context.ServiceProvider.GetRequiredService<IBackgroundWorkerManager>();
var works = context.ServiceProvider.GetServices<IHangfireBackgroundWorker>();
foreach (var work in works)
{
//如果为空默认使用服务器本地utc时间
work.TimeZone ??= TimeZoneInfo.Local;
await backgroundWorkerManager.AddAsync(work);
}
}
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
{
var services = context.ServiceProvider;
GlobalJobFilters.Filters.Add(services.GetRequiredService<UnitOfWorkHangfireFilter>());
}
}

View File

@@ -0,0 +1,20 @@
using Volo.Abp.BackgroundWorkers.Hangfire;
using Volo.Abp.DependencyInjection;
namespace Yi.Framework.BackgroundWorkers.Hangfire;
public class YiHangfireConventionalRegistrar : DefaultConventionalRegistrar
{
protected override bool IsConventionalRegistrationDisabled(Type type)
{
return !typeof(IHangfireBackgroundWorker).IsAssignableFrom(type) || base.IsConventionalRegistrationDisabled(type);
}
protected override List<Type> GetExposedServiceTypes(Type type)
{
return new List<Type>()
{
typeof(IHangfireBackgroundWorker)
};
}
}

View File

@@ -0,0 +1,123 @@
using Hangfire.Dashboard;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Users;
namespace Yi.Framework.BackgroundWorkers.Hangfire;
public class YiTokenAuthorizationFilter : IDashboardAsyncAuthorizationFilter, ITransientDependency
{
private const string Bearer = "Bearer: ";
private string RequireUser { get; set; } = "cc";
private TimeSpan ExpiresTime { get; set; } = TimeSpan.FromMinutes(10);
private IServiceProvider _serviceProvider;
public YiTokenAuthorizationFilter(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public YiTokenAuthorizationFilter SetRequireUser(string userName)
{
RequireUser = userName;
return this;
}
public YiTokenAuthorizationFilter SetExpiresTime(TimeSpan expiresTime)
{
ExpiresTime = expiresTime;
return this;
}
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
var _currentUser = _serviceProvider.GetRequiredService<ICurrentUser>();
//如果验证通过设置cookies
if (_currentUser.IsAuthenticated)
{
var cookieOptions = new CookieOptions
{
Expires = DateTimeOffset.Now + ExpiresTime, // 设置 cookie 过期时间,10分钟
};
var authorization = httpContext.Request.Headers["Authorization"].ToString();
if (!string.IsNullOrWhiteSpace(authorization))
{
var token = httpContext.Request.Headers["Authorization"].ToString().Substring(Bearer.Length - 1);
httpContext.Response.Cookies.Append("Token", token, cookieOptions);
}
if (_currentUser.UserName == RequireUser)
{
return true;
}
}
SetChallengeResponse(httpContext);
return false;
}
private void SetChallengeResponse(HttpContext httpContext)
{
httpContext.Response.StatusCode = 401;
httpContext.Response.ContentType = "text/html; charset=utf-8";
string html = """
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Token </title>
<script>
function sendToken() {
// 获取输入的 token
var token = document.getElementById("tokenInput").value;
token = token.replace('Bearer ','');
// 构建请求 URL
var url = "/hangfire";
// 发送 GET 请求
fetch(url,{
headers: {
'Content-Type': 'application/json', // 设置内容类型为 JSON
'Authorization': 'Bearer '+encodeURIComponent(token), // 设置授权头,例如使用 Bearer token
},
})
.then(response => {
if (response.ok) {
return response.text(); // 或使用 response.json() 如果返回的是 JSON
}
throw new Error('Network response was not ok.');
})
.then(data => {
// 处理成功返回的数据
document.open();
document.write(data);
document.close();
})
.catch(error => {
// 处理错误
console.error('There has been a problem with your fetch operation:', error);
alert("请求失败: " + error.message);
});
}
</script>
</head>
<body style="text-align: center;">
<h1>Yi-hangfire</h1>
<h1>Token</h1>
<textarea id="tokenInput" placeholder="请输入 token" style="width: 80%;height: 120px;margin: 0 10%;"></textarea>
<button onclick="sendToken()"></button>
</body>
</html>
""";
httpContext.Response.WriteAsync(html);
}
public Task<bool> AuthorizeAsync(DashboardContext context)
{
return Task.FromResult(Authorize(context));
}
}

View File

@@ -11,10 +11,10 @@ namespace Yi.Framework.Core.Enums
/// </summary>
public enum FileTypeEnum
{
File,
Image,
Thumbnail,
Excel,
Temp
file,
image,
thumbnail,
excel,
temp
}
}

View File

@@ -1,4 +1,4 @@
using System.Text;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Http;
@@ -74,9 +74,12 @@ namespace Yi.Framework.Core.Extensions
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?)$");
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}$");
result = regResult ? result : "127.0.0.1";
return result;
@@ -96,5 +99,15 @@ namespace Yi.Framework.Core.Extensions
{
return context.User.Claims.Where(x => x.Type == permissionsName).Select(x => x.Value).ToArray();
}
/// <summary>
/// 判断是否是 WebSocket 请求
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static bool IsWebSocketRequest(this HttpContext context)
{
return context.WebSockets.IsWebSocketRequest || context.Request.Path == "/ws";
}
}
}

View File

@@ -78,12 +78,25 @@ namespace Yi.Framework.Core.Helper
return 0;
}
}
/// <summary>
/// CPU使用情况
/// </summary>
/// <returns></returns>
public static CPUMetrics GetCPUMetrics()
{
CPUMetrics cpuMetrics = new CPUMetrics();
var cpudetail = GetCPUDetails();
cpuMetrics.CoreTotal = cpudetail.Cores;
cpuMetrics.LogicalProcessors =cpudetail.LogicalProcessors;
cpuMetrics.CPURate = Math.Ceiling(ParseToDouble(GetCPURate()));
cpuMetrics.FreeRate = 1 - cpuMetrics.CPURate;
return cpuMetrics;
}
/// <summary>
/// 内存使用情况
/// </summary>
/// <returns></returns>
public static MemoryMetrics GetComputerInfo()
public static MemoryMetrics GetMemoryMetrics()
{
try
{
@@ -94,7 +107,7 @@ namespace Yi.Framework.Core.Helper
memoryMetrics.UsedRam = Math.Round(memoryMetrics.Used / 1024, 2) + "GB";
memoryMetrics.TotalRAM = Math.Round(memoryMetrics.Total / 1024, 2) + "GB";
memoryMetrics.RAMRate = Math.Ceiling(100 * memoryMetrics.Used / memoryMetrics.Total).ToString() + "%";
memoryMetrics.CPURate = Math.Ceiling(ParseToDouble(GetCPURate())) + "%";
return memoryMetrics;
}
catch (Exception ex)
@@ -105,7 +118,7 @@ namespace Yi.Framework.Core.Helper
}
/// <summary>
/// 获取内存大小
/// 获取磁盘信息
/// </summary>
/// <returns></returns>
public static List<DiskInfo> GetDiskInfos()
@@ -174,7 +187,7 @@ namespace Yi.Framework.Core.Helper
var isUnix = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
return isUnix;
}
public static string GetCPURate()
{
string cpuRate;
@@ -221,8 +234,69 @@ namespace Yi.Framework.Core.Helper
}
return runTime;
}
}
public static CPUInfo GetCPUDetails()
{
int logicalProcessors = 0;
int cores = 0;
if (IsUnix())
{
string logicalOutput = ShellHelper.Bash("lscpu | grep '^CPU(s):' | awk '{print $2}'");
logicalProcessors = int.Parse(logicalOutput.Trim());
string coresOutput = ShellHelper.Bash("lscpu | grep 'Core(s) per socket:' | awk '{print $4}'");
string socketsOutput = ShellHelper.Bash("lscpu | grep 'Socket(s):' | awk '{print $2}'");
cores = int.Parse(coresOutput.Trim()) * int.Parse(socketsOutput.Trim());
}
else
{
string output = ShellHelper.Cmd("wmic", "cpu get NumberOfCores,NumberOfLogicalProcessors /format:csv");
var lines = output.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
if (lines.Length > 1)
{
var values = lines[1].Split(',');
cores = int.Parse(values[1].Trim());
logicalProcessors =int.Parse(values[2].Trim());
}
}
return new CPUInfo
{
LogicalProcessors = logicalProcessors,
Cores = cores
};
}
}
public class CPUInfo
{
public int LogicalProcessors { get; set; }
public int Cores { get; set; }
}
public class CPUMetrics
{
/// <summary>
/// 内核数
/// </summary>
public int CoreTotal { get; set; }
/// <summary>
/// 逻辑处理器数
/// </summary>
public int LogicalProcessors { get; set; }
/// <summary>
/// CPU使用率%
/// </summary>
public double CPURate { get; set; }
/// <summary>
/// CPU空闲率%
/// </summary>
public double FreeRate { get; set; }
}
/// <summary>
/// 内存信息
/// </summary>
@@ -236,10 +310,7 @@ namespace Yi.Framework.Core.Helper
public double Free { get; set; }
public string UsedRam { get; set; }
/// <summary>
/// CPU使用率%
/// </summary>
public string CPURate { get; set; }
/// <summary>
/// 总内存 GB
/// </summary>
@@ -306,20 +377,25 @@ namespace Yi.Framework.Core.Helper
/// <returns></returns>
public MemoryMetrics GetUnixMetrics()
{
string output = ShellHelper.Bash("free -m | awk '{print $2,$3,$4,$5,$6}'");
string output = ShellHelper.Bash(@"
# 从 /proc/meminfo 文件中提取总内存
total_mem=$(cat /proc/meminfo | grep -i ""MemTotal"" | awk '{print $2}')
# 从 /proc/meminfo 文件中提取剩余内存
free_mem=$(cat /proc/meminfo | grep -i ""MemFree"" | awk '{print $2}')
# 显示提取的信息
echo $total_mem $used_mem $free_mem
");
var metrics = new MemoryMetrics();
var lines = output.Split('\n', (char)StringSplitOptions.RemoveEmptyEntries);
if (lines.Length <= 0) return metrics;
if (lines != null && lines.Length > 0)
if (!string.IsNullOrWhiteSpace(output))
{
var memory = lines[1].Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
if (memory.Length >= 3)
var memory = output.Split(' ', (char)StringSplitOptions.RemoveEmptyEntries);
if (memory.Length >= 2)
{
metrics.Total = double.Parse(memory[0]);
metrics.Used = double.Parse(memory[1]);
metrics.Free = double.Parse(memory[2]);//m
metrics.Total = Math.Round(double.Parse(memory[0]) / 1024, 0);
metrics.Free = Math.Round(double.Parse(memory[1])/ 1024, 0);//m
metrics.Used = metrics.Total - metrics.Free;
}
}
return metrics;

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

@@ -0,0 +1,27 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Yi.Framework.Core.Json;
public class DatetimeJsonConverter : JsonConverter<DateTime>
{
private string _format;
public DatetimeJsonConverter(string format="yyyy-MM-dd HH:mm:ss")
{
_format = format;
}
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if(reader.TokenType==JsonTokenType.String)
{
if (DateTime.TryParse(reader.GetString(), out DateTime dateTime)) return dateTime;
}
return reader.GetDateTime();
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(_format));
}
}

View File

@@ -13,5 +13,39 @@ namespace Yi.Framework.Ddd.Application.Contracts
/// 查询结束时间条件
/// </summary>
public DateTime? EndTime { get; set; }
/// <summary>
/// 排序列名,字段名对应前端
/// </summary>
public string? OrderByColumn { get; set; }
/// <summary>
/// 是否顺序,字段名对应前端
/// </summary>
public string? IsAsc { get; set; }
/// <summary>
/// 是否顺序
/// </summary>
public bool CanAsc => IsAsc?.ToLower() == "ascending" ? true : false;
private string _sorting;
//排序引用
public new string? Sorting
{
get
{
if (!OrderByColumn.IsNullOrWhiteSpace())
{
return $"{OrderByColumn} {(CanAsc ? "ASC" : "DESC")}";
}
else
{
return _sorting;
}
}
set => _sorting = value;
}
}
}
}

View File

@@ -8,9 +8,11 @@ using Volo.Abp.Domain.Repositories;
namespace Yi.Framework.Ddd.Application
{
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey> : YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
public abstract class
YiCrudAppService<TEntity, TEntityDto, TKey> : YiCrudAppService<TEntity, TEntityDto, TKey,
PagedAndSortedResultRequestDto>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
{
@@ -49,16 +51,53 @@ namespace Yi.Framework.Ddd.Application
}
public abstract class YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
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>
where TEntity : class, IEntity<TKey>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)
{
}
public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
{
await CheckUpdatePolicyAsync();
var entity = await GetEntityByIdAsync(id);
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)
{
return Task.CompletedTask;
}
public override async Task<TGetOutputDto> CreateAsync(TCreateInput input)
{
await CheckCreatePolicyAsync();
await CheckCreateInputDtoAsync(input);
var entity = await MapToEntityAsync(input);
TryToSetTenantId(entity);
await Repository.InsertAsync(entity, autoSave: true);
return await MapToGetOutputDtoAsync(entity);
}
protected virtual Task CheckCreateInputDtoAsync(TCreateInput input)
{
return Task.CompletedTask;
}
/// <summary>
/// 多查
/// </summary>
@@ -70,12 +109,14 @@ namespace Yi.Framework.Ddd.Application
//区分多查还是批量查
if (input is IPagedResultRequest pagedInput)
{
entites = await Repository.GetPagedListAsync(pagedInput.SkipCount, pagedInput.MaxResultCount, string.Empty);
entites = await Repository.GetPagedListAsync(pagedInput.SkipCount, pagedInput.MaxResultCount,
string.Empty);
}
else
{
entites = await Repository.GetListAsync();
}
var total = await Repository.GetCountAsync();
var output = await MapToGetListOutputDtosAsync(entites);
return new PagedResultDto<TGetListOutputDto>(total, output);
@@ -146,4 +187,4 @@ namespace Yi.Framework.Ddd.Application
//await Repository.InsertManyAsync(entities);
}
}
}
}

View File

@@ -1,4 +1,5 @@
using SqlSugar;
using ArgumentException = System.ArgumentException;
namespace Yi.Framework.SqlSugarCore.Abstractions
{
@@ -19,7 +20,10 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
/// </summary>
public bool EnabledDbSeed { get; set; } = false;
/// <summary>
/// 开启驼峰转下划线
/// </summary>
public bool EnableUnderLine { get; set; } = false;
/// <summary>
/// 开启codefirst
@@ -50,6 +54,5 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
/// 开启Saas多租户
/// </summary>
public bool EnabledSaasMultiTenancy { get; set; } = false;
}
}
}

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

@@ -10,14 +10,14 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
{
public interface ISqlSugarDbContext
{
// IAbpLazyServiceProvider LazyServiceProvider { get; set; }
/// <summary>
/// SqlSugarDb
/// </summary>
ISqlSugarClient SqlSugarClient { get; }
DbConnOptions Options { get; }
/// <summary>
/// 数据库备份
/// </summary>
void BackupDataBase();
void SetSqlSugarClient(ISqlSugarClient sqlSugarClient);
}
}

View File

@@ -0,0 +1,21 @@
using System.Reflection;
using SqlSugar;
namespace Yi.Framework.SqlSugarCore.Abstractions;
public interface ISqlSugarDbContextDependencies
{
/// <summary>
/// 执行顺序
/// </summary>
int ExecutionOrder { get; }
void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient);
void DataExecuted(object oldValue, DataAfterModel entityInfo);
void DataExecuting(object oldValue, DataFilterModel entityInfo);
void OnLogExecuting(string sql, SugarParameter[] pars);
void OnLogExecuted(string sql, SugarParameter[] pars);
void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo);
}

View File

@@ -2,11 +2,12 @@
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 ()
public interface ISqlSugarRepository<TEntity>:IRepository<TEntity>,IUnitOfWorkEnabled where TEntity : class, IEntity,new ()
{
ISqlSugarClient _Db { get; }
ISugarQueryable<TEntity> _DbQueryable { get; }

View File

@@ -0,0 +1,291 @@
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;
public class DefaultSqlSugarDbContext : SqlSugarDbContext
{
protected DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
protected IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
protected ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
public IUnitOfWorkManager UnitOfWorkManager => LazyServiceProvider.LazyGetRequiredService<IUnitOfWorkManager>();
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
protected IEntityChangeEventHelper EntityChangeEventHelper =>
LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
public DefaultSqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider)
{
}
protected override void CustomDataFilter(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);
}
}
public override 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有个特殊删除会返回批量的结果
//这里sqlsugar有第二个特殊删除事件是行级事件
if (entityInfo.EntityValue is IEnumerable entityValues)
{
foreach (var entityValue in entityValues)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue);
}
}
// }
break;
}
//实体领域事件-所有操作类型
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
var eventReport = CreateEventReport(entityInfo.EntityValue);
PublishEntityEvents(eventReport);
}
}
public override 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<DefaultSqlSugarDbContext>().LogDebug(sb.ToString());
}
}
public override void OnLogExecuted(string sql, SugarParameter[] pars)
{
if (Options.EnabledSqlLog)
{
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
Logger.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;
}
}
/// <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)
{
UnitOfWorkManager.Current?.AddOrReplaceLocalEvent(
new UnitOfWorkEventRecord(localEvent.EventData.GetType(), localEvent.EventData, localEvent.EventOrder)
);
}
foreach (var distributedEvent in changeReport.DistributedEvents)
{
UnitOfWorkManager.Current?.AddOrReplaceDistributedEvent(
new UnitOfWorkEventRecord(distributedEvent.EventData.GetType(), distributedEvent.EventData, distributedEvent.EventOrder)
);
}
}
}

View File

@@ -1,8 +1,12 @@
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Microsoft.Extensions.Logging;
using Nito.AsyncEx;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Linq;
@@ -13,9 +17,9 @@ namespace Yi.Framework.SqlSugarCore.Repositories
{
public class SqlSugarRepository<TEntity> : ISqlSugarRepository<TEntity>, IRepository<TEntity> where TEntity : class, IEntity, new()
{
public ISqlSugarClient _Db => GetDbContextAsync().Result;
public ISqlSugarClient _Db => AsyncContext.Run(async () => await GetDbContextAsync());
public ISugarQueryable<TEntity> _DbQueryable => GetDbContextAsync().Result.Queryable<TEntity>();
public ISugarQueryable<TEntity> _DbQueryable => _Db.Queryable<TEntity>();
private ISugarDbContextProvider<ISqlSugarDbContext> _sugarDbContextProvider;
public IAsyncQueryableExecuter AsyncExecuter { get; }
@@ -33,9 +37,7 @@ namespace Yi.Framework.SqlSugarCore.Repositories
/// <returns></returns>
public virtual async Task<ISqlSugarClient> GetDbContextAsync()
{
var db = (await _sugarDbContextProvider.GetDbContextAsync()).SqlSugarClient;
//await Console.Out.WriteLineAsync("获取的id" + db.ContextID);
return db;
}
@@ -243,7 +245,7 @@ namespace Yi.Framework.SqlSugarCore.Repositories
{
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
return await (await GetDbSimpleClientAsync()).AsUpdateable().SetColumns(nameof(ISoftDelete), true).Where(whereExpression).ExecuteCommandAsync() > 0;
return await (await GetDbSimpleClientAsync()).AsUpdateable().SetColumns(nameof(ISoftDelete.IsDeleted), true).Where(whereExpression).ExecuteCommandAsync() > 0;
}
else
{
@@ -372,6 +374,20 @@ namespace Yi.Framework.SqlSugarCore.Repositories
public virtual async Task<bool> UpdateAsync(TEntity updateObj)
{
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>())//带版本号乐观锁更新
{
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,41 @@
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
{
/// <summary>
/// 新增db对象可支持多个
/// </summary>
/// <param name="service"></param>
/// <param name="serviceLifetime"></param>
/// <typeparam name="TDbContext"></typeparam>
/// <returns></returns>
public static IServiceCollection AddYiDbContext<TDbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where TDbContext : class, ISqlSugarDbContextDependencies
{
service.AddTransient<ISqlSugarDbContextDependencies, TDbContext>();
return service;
}
/// <summary>
/// 新增db对象可支持多个
/// </summary>
/// <param name="service"></param>
/// <param name="options"></param>
/// <typeparam name="TDbContext"></typeparam>
/// <returns></returns>
public static IServiceCollection AddYiDbContext<TDbContext>(this IServiceCollection service, Action<DbConnOptions> options) where TDbContext : class, ISqlSugarDbContextDependencies
{
service.Configure<DbConnOptions>(options.Invoke);
service.AddYiDbContext<TDbContext>();
return service;
}
}
}

View File

@@ -1,114 +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
{
EntityService = (c, p) =>
{
if (new NullabilityInfoContext()
.Create(c).WriteState is NullabilityState.Nullable)
{
p.IsNullable = true;
}
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,371 +1,49 @@
using System;
using System.Collections;
using System.Reflection;
using System.Security.Policy;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Reflection;
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.Entities.Events;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Users;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore
namespace Yi.Framework.SqlSugarCore;
public abstract class SqlSugarDbContext : ISqlSugarDbContextDependencies
{
public class SqlSugarDbContext : ISqlSugarDbContext
protected IAbpLazyServiceProvider LazyServiceProvider { get; }
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
{
/// <summary>
/// SqlSugar 客户端
/// </summary>
public ISqlSugarClient SqlSugarClient { get; private set; }
public ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
private IAbpLazyServiceProvider LazyServiceProvider { get; }
private IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
protected ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
public IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
public IEntityChangeEventHelper EntityChangeEventHelper => LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
public AbpDbConnectionOptions ConnectionOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDbConnectionOptions>>().Value;
private ISqlSugarDbConnectionCreator _dbConnectionCreator;
public void SetSqlSugarClient(ISqlSugarClient sqlSugarClient)
{
SqlSugarClient = sqlSugarClient;
}
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
{
LazyServiceProvider = lazyServiceProvider;
var connectionCreator = LazyServiceProvider.LazyGetRequiredService<ISqlSugarDbConnectionCreator>();
_dbConnectionCreator = connectionCreator;
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();
}));
connectionCreator.SetDbAop(SqlSugarClient);
}
/// <summary>
/// db切换多库支持
/// </summary>
/// <returns></returns>
protected virtual string GetCurrentConnectionString()
{
var defautlUrl = Options.Url ?? ConnectionOptions.GetConnectionStringOrNull(ConnectionStrings.DefaultConnectionStringName);
//如果未开启多租户返回db url 或者 默认连接字符串
if (!Options.EnabledSaasMultiTenancy)
{
return defautlUrl;
}
//开启了多租户
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
var connectionString = connectionStringResolver.ResolveAsync().GetAwaiter().GetResult();
//没有检测到使用多租户功能,默认使用默认库即可
if (string.IsNullOrWhiteSpace(connectionString))
{
Volo.Abp.Check.NotNull(Options.Url, "租户默认库Defalut未找到");
connectionString = defautlUrl;
}
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;
}
}
Volo.Abp.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)
{
//表达式不能放方法
Guid? tenantId = CurrentTenant?.Id;
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == tenantId);
}
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);
}
}
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
{
if (CurrentUser.Id != null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
break;
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
{
//主键为空或者为默认最小值
if (Guid.Empty.Equals(oldValue))
{
entityInfo.SetValue(GuidGenerator.Create());
}
}
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
{
//为空或者为默认最小值
if (oldValue is null || DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
{
if (CurrentUser.Id != null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
//插入时需要租户id,先预留
if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
{
if (CurrentTenant 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 == "ConcurrencyStamp")
{
column.IsIgnore = 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("其他数据库备份未实现");
}
}
this.LazyServiceProvider = lazyServiceProvider;
}
}
protected ISqlSugarClient SqlSugarClient { get;private set; }
public int ExecutionOrder => 0;
public void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
{
SqlSugarClient = sqlSugarClient;
CustomDataFilter(sqlSugarClient);
}
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{
}
public virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
{
}
public virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
{
}
public virtual void OnLogExecuting(string sql, SugarParameter[] pars)
{
}
public virtual void OnLogExecuted(string sql, SugarParameter[] pars)
{
}
public virtual void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
{
}
}

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
{
public class SqlSugarDbContextFactory : ISqlSugarDbContext
{
/// <summary>
/// SqlSugar 客户端
/// </summary>
public ISqlSugarClient SqlSugarClient { get; private set; }
private IAbpLazyServiceProvider LazyServiceProvider { get; }
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
private ISerializeService SerializeService => LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
private IEnumerable<ISqlSugarDbContextDependencies> SqlSugarDbContextDependencies =>
LazyServiceProvider.LazyGetRequiredService<IEnumerable<ISqlSugarDbContextDependencies>>();
private static readonly ConcurrentDictionary<string, ConnectionConfig> ConnectionConfigCache = new();
public SqlSugarDbContextFactory(IAbpLazyServiceProvider lazyServiceProvider)
{
LazyServiceProvider = lazyServiceProvider;
var connectionString = GetCurrentConnectionString();
var connectionConfig =BuildConnectionConfig(action: options =>
{
options.ConnectionString = connectionString;
options.DbType = GetCurrentDbType();
});
// var connectionConfig = ConnectionConfigCache.GetOrAdd(connectionString, (_) =>
// BuildConnectionConfig(action: options =>
// {
// options.ConnectionString = connectionString;
// options.DbType = GetCurrentDbType();
// }));
SqlSugarClient = new SqlSugarClient(connectionConfig);
//生命周期以下都可以直接使用sqlsugardb了
// Aop及多租户连接字符串和类型需要单独设置
// Aop操作不能进行缓存
SetDbAop(SqlSugarClient);
}
/// <summary>
/// 构建Aop-sqlsugaraop在多租户模式中需单独设置
/// </summary>
/// <param name="sqlSugarClient"></param>
protected virtual void SetDbAop(ISqlSugarClient sqlSugarClient)
{
//替换默认序列化器
sqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
//将所有ISqlSugarDbContextDependencies进行累加
Action<string, SugarParameter[]> onLogExecuting = null;
Action<string, SugarParameter[]> onLogExecuted = null;
Action<object, DataFilterModel> dataExecuting = null;
Action<object, DataAfterModel> dataExecuted = null;
Action<ISqlSugarClient> onSqlSugarClientConfig = null;
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
{
onLogExecuting += dependency.OnLogExecuting;
onLogExecuted += dependency.OnLogExecuted;
dataExecuting += dependency.DataExecuting;
dataExecuted += dependency.DataExecuted;
onSqlSugarClientConfig += dependency.OnSqlSugarClientConfig;
}
//最先存放db操作
onSqlSugarClientConfig(sqlSugarClient);
sqlSugarClient.Aop.OnLogExecuting =onLogExecuting;
sqlSugarClient.Aop.OnLogExecuted = onLogExecuted;
sqlSugarClient.Aop.DataExecuting =dataExecuting;
sqlSugarClient.Aop.DataExecuted =dataExecuted;
}
/// <summary>
/// 构建连接配置
/// </summary>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
protected virtual ConnectionConfig BuildConnectionConfig(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); // 驼峰转下划线
//将所有ISqlSugarDbContextDependencies的EntityService进行累加
//额外的实体服务需要这里配置,
Action<PropertyInfo, EntityColumnInfo> entityService = null;
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
{
entityService += dependency.EntityService;
}
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;
}
/// <summary>
/// db切换多库支持
/// </summary>
/// <returns></returns>
protected virtual string GetCurrentConnectionString()
{
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
var connectionString =
AsyncHelper.RunSync(() => connectionStringResolver.ResolveAsync());
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;
}
public virtual 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("其他数据库备份未实现");
}
}
}
}

View File

@@ -0,0 +1,76 @@
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using SqlSugar;
namespace Yi.Framework.SqlSugarCore;
public class NonPublicPropertiesResolver : DefaultContractResolver
{
/// <summary>
/// 重写获取属性存在get set方法就可以写入
/// </summary>
/// <param name="member"></param>
/// <param name="memberSerialization"></param>
/// <returns></returns>
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
if (member is PropertyInfo pi)
{
prop.Readable = (pi.GetMethod != null);
prop.Writable = (pi.SetMethod != null);
}
return prop;
}
}
public class SqlSugarNonPublicSerializer : ISerializeService
{
/// <summary>
/// 默认的序列化服务
/// </summary>
private readonly ISerializeService _serializeService = DefaultServices.Serialize;
public string SerializeObject(object value)
{
//保留原有实现
return _serializeService.SerializeObject(value);
}
public string SugarSerializeObject(object value)
{ //保留原有实现
return _serializeService.SugarSerializeObject(value);
}
/// <summary>
/// 重写对象反序列化支持NoPublic访问器
/// </summary>
/// <param name="value"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T DeserializeObject<T>(string value)
{
if (typeof(T).FullName.StartsWith("System.Text.Json."))
{
// 动态创建一个 JsonSerializer 实例
Type serializerType =typeof(T).Assembly.GetType("System.Text.Json.JsonSerializer");
var methods = serializerType
.GetMethods().Where(it=>it.Name== "Deserialize")
.Where(it=>it.GetParameters().Any(z=>z.ParameterType==typeof(string))).First();
// 调用 SerializeObject 方法序列化对象
T json = (T)methods.MakeGenericMethod(typeof(T))
.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);
}
}

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

@@ -17,4 +17,4 @@ namespace Yi.Framework.SqlSugarCore.Uow
DbContext = dbContext;
}
}
}
}

View File

@@ -14,7 +14,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
public ISqlSugarDbContext GetDbContext()
{
return _sqlsugarDbContext;
}

View File

@@ -13,8 +13,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
{
public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext
{
private readonly ISqlSugarDbConnectionCreator _dbConnectionCreator;
public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; }
public IServiceProvider ServiceProvider { get; set; }
@@ -28,8 +26,7 @@ namespace Yi.Framework.SqlSugarCore.Uow
IUnitOfWorkManager unitOfWorkManager,
IConnectionStringResolver connectionStringResolver,
ICancellationTokenProvider cancellationTokenProvider,
ICurrentTenant currentTenant,
ISqlSugarDbConnectionCreator dbConnectionCreator
ICurrentTenant currentTenant
)
{
UnitOfWorkManager = unitOfWorkManager;
@@ -37,10 +34,8 @@ namespace Yi.Framework.SqlSugarCore.Uow
CancellationTokenProvider = cancellationTokenProvider;
CurrentTenant = currentTenant;
Logger = NullLogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>>.Instance;
_dbConnectionCreator = dbConnectionCreator;
}
//private static object _databaseApiLock = new object();
public virtual async Task<TDbContext> GetDbContextAsync()
{
@@ -48,23 +43,20 @@ namespace Yi.Framework.SqlSugarCore.Uow
//获取当前连接字符串,未多租户时,默认为空
var connectionString = await ResolveConnectionStringAsync(connectionStringName);
var dbContextKey = $"{this.GetType().FullName}_{connectionString}";
var dbContextKey = $"{this.GetType().Name}_{connectionString}";
var unitOfWork = UnitOfWorkManager.Current;
if (unitOfWork == null /*|| unitOfWork.Options.IsTransactional == false*/)
if (unitOfWork == null )
{
var dbContext = (TDbContext)ServiceProvider.GetRequiredService<ISqlSugarDbContext>();
//提高体验,取消工作单元强制性
//throw new AbpException("A DbContext can only be created inside a unit of work!");
//var dbContext = (TDbContext)ServiceProvider.GetRequiredService<ISqlSugarDbContext>();
//如果不启用工作单元创建一个新的db不开启事务即可
return dbContext;
//return dbContext;
//2024-11-30改回强制性使用工作单元否则容易造成歧义
throw new AbpException("DbContext 只能在工作单元内工作当前DbContext没有工作单元如需创建新线程并发操作请手动创建工作单元");
}
//尝试当前工作单元获取db
var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey);

View File

@@ -6,12 +6,10 @@ 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 Yi.Framework.SqlSugarCore.Abstractions;
using Yi.Framework.SqlSugarCore.Repositories;
using Yi.Framework.SqlSugarCore.Uow;
@@ -25,9 +23,35 @@ namespace Yi.Framework.SqlSugarCore
{
var service = context.Services;
var configuration = service.GetConfiguration();
Configure<DbConnOptions>(configuration.GetSection("DbConnOptions"));
var section = configuration.GetSection("DbConnOptions");
Configure<DbConnOptions>(section);
var dbConnOptions = new DbConnOptions();
section.Bind(dbConnOptions);
service.TryAddScoped<ISqlSugarDbContext, SqlSugarDbContext>();
//很多人遗漏了这一点,不同的数据库,对于主键的使用规约不一样,需要根据数据库进行判断
SequentialGuidType guidType;
switch (dbConnOptions.DbType)
{
case DbType.MySql:
case DbType.PostgreSQL:
guidType= SequentialGuidType.SequentialAsString;
break;
case DbType.SqlServer:
guidType = SequentialGuidType.SequentialAtEnd;
break;
case DbType.Oracle:
guidType = SequentialGuidType.SequentialAsBinary;
break;
default:
guidType = SequentialGuidType.SequentialAtEnd;
break;
}
Configure<AbpSequentialGuidGeneratorOptions>(options =>
{
options.DefaultSequentialGuidType = guidType;
});
service.TryAddScoped<ISqlSugarDbContext, SqlSugarDbContextFactory>();
//不开放sqlsugarClient
//service.AddTransient<ISqlSugarClient>(x => x.GetRequiredService<ISqlsugarDbContext>().SqlSugarClient);
@@ -39,8 +63,14 @@ namespace Yi.Framework.SqlSugarCore
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; });
context.Services.AddYiDbContext<DefaultSqlSugarDbContext>();
return Task.CompletedTask;
}
@@ -50,8 +80,8 @@ namespace Yi.Framework.SqlSugarCore
//进行CodeFirst
var service = context.ServiceProvider;
var options = service.GetRequiredService<IOptions<DbConnOptions>>().Value;
var _logger= service.GetRequiredService<ILogger<YiFrameworkSqlSugarCoreModule>>();
var logger = service.GetRequiredService<ILogger<YiFrameworkSqlSugarCoreModule>>();
StringBuilder sb = new StringBuilder();
@@ -65,13 +95,13 @@ namespace Yi.Framework.SqlSugarCore
sb.AppendLine("===============================");
_logger.LogInformation(sb.ToString());
//Todo准备支持多租户种子数据及CodeFirst
logger.LogInformation(sb.ToString());
if (options.EnabledCodeFirst)
{
CodeFirst(service);
}
if (options.EnabledDbSeed)
{
await DataSeedAsync(service);
@@ -80,7 +110,6 @@ namespace Yi.Framework.SqlSugarCore
private void CodeFirst(IServiceProvider service)
{
var moduleContainer = service.GetRequiredService<IModuleContainer>();
var db = service.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient;
@@ -95,11 +124,11 @@ namespace Yi.Framework.SqlSugarCore
.Where(x => x.GetCustomAttribute<SugarTable>() != null)
.Where(x => x.GetCustomAttribute<SplitTableAttribute>() is null));
}
if (types.Count > 0)
{
db.CopyNew().CodeFirst.InitTables(types.ToArray());
}
}
private async Task DataSeedAsync(IServiceProvider service)
@@ -108,4 +137,4 @@ namespace Yi.Framework.SqlSugarCore
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

@@ -16,6 +16,7 @@ public class AuditingStore : IAuditingStore, ITransientDependency
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected AbpAuditingOptions Options { get; }
protected IAuditLogInfoToAuditLogConverter Converter { get; }
public AuditingStore(
IAuditLogRepository auditLogRepository,
IUnitOfWorkManager unitOfWorkManager,
@@ -52,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))
using (var uow = UnitOfWorkManager.Begin())
{
await AuditLogRepository.InsertAsync(await Converter.ConvertAsync(auditInfo));
await uow.CompleteAsync();
}
}
}
}

View File

@@ -22,6 +22,7 @@ public class AuditLogActionEntity : Entity<Guid>, IMultiTenant
public virtual string? MethodName { get; protected set; }
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
public virtual string? Parameters { get; protected set; }
public virtual DateTime? ExecutionTime { get; protected set; }

View File

@@ -103,12 +103,14 @@ namespace Yi.Framework.AuditLogging.Domain.Entities
public virtual string? CorrelationId { get; set; }
[SugarColumn(Length = 2000)]
public virtual string? BrowserInfo { get; protected set; }
public virtual string? HttpMethod { get; protected set; }
public virtual string? Url { get; protected set; }
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
public virtual string? Exceptions { get; protected set; }
public virtual string? Comments { get; protected set; }

View File

@@ -1,12 +1,15 @@
using Yi.Framework.Bbs.Domain.Shared.Enums;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.BbsUser;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Analyse;
public class MoneyTopUserDto
/// <summary>
/// 用户排行榜
/// </summary>
public class BaseAnalyseTopUserDto
{
public Guid UserId { get; set; }
public string UserName { get; set; }
public string? Nick { get; set; }
public decimal Money { get; set; }
public int Order { get; set; }
public string? Icon { get; set; }
public int Level { get; set; }
@@ -18,5 +21,4 @@ public class MoneyTopUserDto
/// 用户限制
/// </summary>
public UserLimitEnum UserLimit { get; set; }
}

View File

@@ -0,0 +1,7 @@
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Analyse;
public class MoneyTopUserDto:BaseAnalyseTopUserDto
{
public decimal Money { get; set; }
}

View File

@@ -0,0 +1,8 @@
using Yi.Framework.Bbs.Domain.Shared.Enums;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Analyse;
public class PointsTopUserDto:BaseAnalyseTopUserDto
{
public int Points { get; set; }
}

View File

@@ -0,0 +1,6 @@
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Analyse;
public class ValueTopUserDto:BaseAnalyseTopUserDto
{
public decimal Value { get; set; }
}

View File

@@ -4,8 +4,6 @@ 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; }

View File

@@ -1,4 +1,5 @@
using Volo.Abp.Application.Dtos;
using Yi.Framework.Bbs.Domain.Shared.Consts;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article
{
@@ -10,5 +11,23 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article
public Guid ParentId { get; set; }
public DateTime CreationTime { get; set; }
public bool HasPermission { get;internal set; }
/// <summary>
/// 设置权限
/// </summary>
public void SetPassPermission()
{
HasPermission = true;
}
/// <summary>
/// 设置无权限
/// </summary>
public void SetNoPermission()
{
HasPermission = false;
Content=DiscussConst.Privacy;
}
}
}

View File

@@ -31,5 +31,15 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
/// 是否禁止评论创建功能
/// </summary>
public bool IsDisableCreateComment { get; set; }
/// <summary>
/// 标签
/// </summary>
public List<Guid>? DiscussLableIds { get; set; }
/// <summary>
/// 角色
/// </summary>
public List<string>? PermissionRoleCodes { get; set; } = new List<string>();
}
}

View File

@@ -6,7 +6,7 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
public class DiscussGetListInputVo : PagedAndSortedResultRequestDto
{
/// <summary>
/// 创建者的用户名
/// 创建者的用户名
/// </summary>
public string? UserName { get; set; }
public Guid? UserId { get; set; }
@@ -15,11 +15,11 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
public Guid? PlateId { get; set; }
//默认查询非置顶
//默认查询非置顶
public bool? IsTop { get; set; }
//查询方式
//查询方式
public QueryDiscussTypeEnum Type { get; set; } = QueryDiscussTypeEnum.New;
}
}

View File

@@ -1,5 +1,6 @@
using Volo.Abp.Application.Dtos;
using Yi.Framework.Bbs.Application.Contracts.Dtos.BbsUser;
using Yi.Framework.Bbs.Application.Contracts.Dtos.DiscussLable;
using Yi.Framework.Bbs.Domain.Shared.Consts;
using Yi.Framework.Bbs.Domain.Shared.Enums;
using Yi.Framework.Rbac.Application.Contracts.Dtos.User;
@@ -9,89 +10,47 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
public class DiscussGetListOutputDto : EntityDto<Guid>
{
/// <summary>
/// 是否禁止评论创建功能
/// 是否禁止评论创建功能
/// </summary>
public bool IsDisableCreateComment { get; set; }
/// <summary>
/// 是否已点赞,默认未登录不点赞
/// 是否已点赞,默认未登录不点赞
/// </summary>
public bool IsAgree { get; set; } = false;
public string Title { get; set; }
public string Types { get; set; }
public string? Introduction { get; set; }
public int AgreeNum { get; set; }
public int SeeNum { get; set; }
//批量查询,不给内容,性能考虑
//批量查询,不给内容,性能考虑
//public string Content { get; set; }
public string? Color { get; set; }
public Guid PlateId { get; set; }
//是否置顶默认false
//是否置顶默认false
public bool IsTop { get; set; }
public DiscussPermissionTypeEnum PermissionType { get; set; }
//是否禁止默认false
//是否禁止默认false
public bool IsBan { get; set; }
/// <summary>
/// 封面
/// 封面
/// </summary>
public string? Cover { get; set; }
//私有需要判断code权限
public string? PrivateCode { get; set; }
public DateTime CreationTime { get; set; }
public List<Guid>? PermissionUserIds { get; set; }
/// <summary>
/// 所需角色
/// </summary>
public List<string>? PermissionRoleCodes { get; set; } = new List<string>();
public BbsUserGetListOutputDto User { get; set; }
public void SetBan()
{
Title = DiscussConst.Privacy;
Introduction = "";
Cover = null;
//被禁止
IsBan = true;
}
public List<Guid>? DiscussLableIds { get; set; } = new List<Guid>();
public List<DiscussLableGetOutputDto> Lables { get; set; } = new List<DiscussLableGetOutputDto>();
}
public static class DiscussGetListOutputDtoExtension
{
public static void ApplyPermissionTypeFilter(this List<DiscussGetListOutputDto> dtos, Guid userId)
{
dtos?.ForEach(dto =>
{
switch (dto.PermissionType)
{
case DiscussPermissionTypeEnum.Public:
break;
case DiscussPermissionTypeEnum.Oneself:
//当前主题是仅自己可见,同时不是当前登录用户
if (dto.User.Id != userId)
{
dto.SetBan();
}
break;
case DiscussPermissionTypeEnum.User:
//当前主题为部分可见,同时不是当前登录用户 也 不在可见用户列表中
if (dto.User.Id != userId && !dto.PermissionUserIds.Contains(userId))
{
dto.SetBan();
}
break;
default:
break;
}
});
}
}
}

View File

@@ -1,6 +1,8 @@
using Volo.Abp.Application.Dtos;
using Yi.Framework.Bbs.Application.Contracts.Dtos.BbsUser;
using Yi.Framework.Bbs.Application.Contracts.Dtos.DiscussLable;
using Yi.Framework.Bbs.Application.Contracts.Dtos.Plate;
using Yi.Framework.Bbs.Domain.Shared.Consts;
using Yi.Framework.Bbs.Domain.Shared.Enums;
using Yi.Framework.Rbac.Application.Contracts.Dtos.User;
@@ -9,11 +11,10 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
public class DiscussGetOutputDto : EntityDto<Guid>
{
/// <summary>
/// 是否禁止评论创建功能
/// 是否禁止评论创建功能
/// </summary>
public bool IsDisableCreateComment { get; set; }
public string Title { get; set; }
public string? Types { get; set; }
public string? Introduction { get; set; }
public int AgreeNum { get; set; }
public int SeeNum { get; set; }
@@ -21,24 +22,48 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
public string? Color { get; set; }
public Guid PlateId { get; set; }
//是否置顶默认false
//是否置顶默认false
public bool IsTop { get; set; }
/// <summary>
/// 封面
/// 封面
/// </summary>
public string? Cover { get; set; }
//是否私有默认false
//是否私有默认false
public bool IsPrivate { get; set; }
//私有需要判断code权限
//私有需要判断code权限
public string? PrivateCode { get; set; }
public DateTime CreationTime { get; set; }
public DiscussPermissionTypeEnum PermissionType { get; set; }
public bool IsAgree { get; set; } = false;
public List<Guid>? PermissionUserIds { get; set; }
public List<string> PermissionRoleCodes { get; set; } = new List<string>();
public BbsUserGetListOutputDto User { get; set; }
public PlateGetOutputDto Plate { get; set; }
public List<Guid>? DiscussLableIds { get; set; } = new List<Guid>();
public List<DiscussLableGetOutputDto> Lables { get; set; } =new List<DiscussLableGetOutputDto>();
public bool HasPermission { get;internal set; }
/// <summary>
/// 设置权限
/// </summary>
public void SetPassPermission()
{
HasPermission = true;
}
/// <summary>
/// 设置无权限
/// </summary>
public void SetNoPermission()
{
HasPermission = false;
Content=DiscussConst.Privacy;
}
}
}

View File

@@ -15,15 +15,25 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
public DiscussPermissionTypeEnum PermissionType { get; set; }
/// <summary>
/// 封面
/// 封面
/// </summary>
public string? Cover { get; set; }
public int OrderNum { get; set; }
/// <summary>
/// 是否禁止评论创建功能
/// 是否禁止评论创建功能
/// </summary>
public bool IsDisableCreateComment { get; set; }
/// <summary>
/// 标签
/// </summary>
public List<Guid>? DiscussLableIds { get; set; }
/// <summary>
/// 需求角色
/// </summary>
public List<string>? PermissionRoleCodes { get; set; } = new List<string>();
}
}

View File

@@ -0,0 +1,11 @@
using Volo.Abp.Application.Dtos;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.DiscussLable;
public class DiscussLableGetOutputDto:EntityDto<Guid>
{
public Guid Id { get; set; }
public string Name { get; set; }
public string? Color { get; set; }
public string? BackgroundColor { get; set; }
}

View File

@@ -3,7 +3,7 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.MyType
/// <summary>
/// Label输入创建对象
/// </summary>
public class MyTypeCreateInputVo
public class DiscussLableCreateInputVo
{
public string Name { get; set; }
public string? Color { get; set; }

View File

@@ -0,0 +1,9 @@
using Volo.Abp.Application.Dtos;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.MyType
{
public class DiscussLableGetListInputVo : PagedAndSortedResultRequestDto
{
public string? Name { get; set; }
}
}

View File

@@ -2,11 +2,10 @@ using Volo.Abp.Application.Dtos;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.MyType
{
public class MyTypeGetListOutputDto : EntityDto<Guid>
public class DiscussLableGetListOutputDto : EntityDto<Guid>
{
public string Name { get; set; }
public string? Color { get; set; }
public string? BackgroundColor { get; set; }
public Guid UserId { get; set; }
}
}

View File

@@ -2,7 +2,7 @@ using Volo.Abp.Application.Dtos;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.MyType
{
public class MyTypeOutputDto : EntityDto<Guid>
public class DiscussLableOutputDto : EntityDto<Guid>
{
public string Name { get; set; }
public string? Color { get; set; }

View File

@@ -1,10 +1,9 @@
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.MyType
{
public class MyTypeUpdateInputVo
public class DiscussLableUpdateInputVo
{
public string Name { get; set; }
public string? Color { get; set; }
public string? BackgroundColor { get; set; }
public Guid UserId { get; set; }
}
}

View File

@@ -1,12 +0,0 @@
using Volo.Abp.Application.Dtos;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.MyType
{
public class MyTypeGetListInputVo : PagedAndSortedResultRequestDto
{
public string? Name { get; set; }
public string? Color { get; set; }
public string? BackgroundColor { get; set; }
public Guid? UserId { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Shop;
public class BbsShopAccountDto
{
/// <summary>
/// 钱钱
/// </summary>
public decimal Money { get; set; }
/// <summary>
/// 积分
/// </summary>
public int Points { get; set; }
/// <summary>
/// 价值
/// </summary>
public decimal Value { get; set; }
}

View File

@@ -0,0 +1,7 @@
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Shop;
public class BuyShopInputDto
{
public Guid GoodsId { get; set; }
public string ContactInformation { get; set; }
}

View File

@@ -0,0 +1,74 @@
using Volo.Abp.Application.Dtos;
using Yi.Framework.Bbs.Domain.Shared.Enums;
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Shop;
public class ShopGetListOutput:EntityDto<Guid>
{
/// <summary>
/// 上架时间
/// </summary>
public DateTime CreationTime { get; set; }
/// <summary>
/// 商品类型
/// </summary>
public GoodsTypeEnum GoodsType{ get; set; }
/// <summary>
/// 下架时间
/// </summary>
public DateTime? EndTime { get; set; }
/// <summary>
/// 商品名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 每人限购数量
/// </summary>
public int LimitNumber { get; set; }
/// <summary>
/// 当前库存数量
/// </summary>
public int StockNumber { get; set; }
/// <summary>
/// 商品图片url
/// </summary>
public string ImageUrl { get; set; }
/// <summary>
/// 描述
/// </summary>
public string Describe { get; set; }
/// <summary>
/// 编号
/// </summary>
public string Code { get; set; }
/// <summary>
/// 所需钱钱
/// </summary>
public decimal NeedMoney { get; set; }
/// <summary>
/// 所需价值
/// </summary>
public decimal NeedValue { get; set; }
/// <summary>
/// 所需积分
/// </summary>
public decimal NeedPoints { get; set; }
public int OrderNum { get; set; }
/// <summary>
/// 是否已限制
/// </summary>
public bool IsLimit { get; set; }
}

View File

@@ -5,7 +5,7 @@ namespace Yi.Framework.Bbs.Application.Contracts.IServices
/// <summary>
/// Label服务抽象
/// </summary>
public interface IMyTypeService : IYiCrudAppService<MyTypeOutputDto, MyTypeGetListOutputDto, Guid, MyTypeGetListInputVo, MyTypeCreateInputVo, MyTypeUpdateInputVo>
public interface IDiscussLableService : IYiCrudAppService<DiscussLableOutputDto, DiscussLableGetListOutputDto, Guid, DiscussLableGetListInputVo, DiscussLableCreateInputVo, DiscussLableUpdateInputVo>
{
}

View File

@@ -8,6 +8,5 @@ namespace Yi.Framework.Bbs.Application.Contracts.IServices
/// </summary>
public interface IDiscussService : IYiCrudAppService<DiscussGetOutputDto, DiscussGetListOutputDto, Guid, DiscussGetListInputVo, DiscussCreateInputVo, DiscussUpdateInputVo>
{
Task VerifyDiscussPermissionAsync(Guid discussId);
}
}

View File

@@ -3,6 +3,7 @@
<ItemGroup>
<ProjectReference Include="..\..\digital-collectibles\Yi.Framework.DigitalCollectibles.Application.Contracts\Yi.Framework.DigitalCollectibles.Application.Contracts.csproj" />
<ProjectReference Include="..\..\rbac\Yi.Framework.Rbac.Application.Contracts\Yi.Framework.Rbac.Application.Contracts.csproj" />
<ProjectReference Include="..\Yi.Framework.Bbs.Domain.Shared\Yi.Framework.Bbs.Domain.Shared.csproj" />
</ItemGroup>

View File

@@ -4,7 +4,9 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
using Yi.Framework.Bbs.Domain.Shared.Caches;
using Yi.Framework.Bbs.Domain.Shared.Etos;
namespace Yi.Framework.Bbs.Application.Extensions;
@@ -14,6 +16,29 @@ namespace Yi.Framework.Bbs.Application.Extensions;
/// 需考虑一致性问题,又不能上锁影响性能
/// </summary>
public class AccessLogMiddleware : IMiddleware, ITransientDependency
{
private static int _accessLogNumber = 0;
internal static void ResetAccessLogNumber()
{
_accessLogNumber = 0;
}
internal static int GetAccessLogNumber()
{
return _accessLogNumber;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
await next(context);
Interlocked.Increment(ref _accessLogNumber);
}
}
public class AccessLogResetEventHandler : ILocalEventHandler<AccessLogResetArgs>,
ITransientDependency
{
/// <summary>
/// 缓存前缀
@@ -39,13 +64,28 @@ public class AccessLogMiddleware : IMiddleware, ITransientDependency
return redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled);
}
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
//该事件由job定时10秒触发
public async Task HandleEventAsync(AccessLogResetArgs eventData)
{
await next(context);
if (EnableRedisCache)
{
await RedisClient.IncrByAsync($"{CacheKeyPrefix}:{AccessLogCacheConst.Key}:{DateTime.Now.Date}", 1);
//分布式锁
if (await RedisClient.SetNxAsync("AccessLogLock",true,TimeSpan.FromSeconds(5)))
{
//自增长数
var incrNumber= AccessLogMiddleware.GetAccessLogNumber();
//立即重置,开始计算,方式丢失
AccessLogMiddleware.ResetAccessLogNumber();
if (incrNumber>0)
{
await RedisClient.IncrByAsync(
$"{CacheKeyPrefix}{AccessLogCacheConst.Key}:{DateTime.Now.Date:yyyyMMdd}", incrNumber);
}
}
}
}
}

View File

@@ -0,0 +1,42 @@
using FreeRedis;
using Hangfire;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Volo.Abp.BackgroundWorkers.Hangfire;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EventBus.Local;
using Yi.Framework.Bbs.Domain.Entities;
using Yi.Framework.Bbs.Domain.Shared.Caches;
using Yi.Framework.Bbs.Domain.Shared.Enums;
using Yi.Framework.Bbs.Domain.Shared.Etos;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Bbs.Application.Jobs;
public class AccessLogCacheJob : HangfireBackgroundWorkerBase
{
private readonly ILocalEventBus _localEventBus;
public AccessLogCacheJob(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
RecurringJobId = "访问日志写入缓存";
//每10秒执行一次将本地缓存转入redis防止丢数据
CronExpression = "*/10 * * * * *";
//
// JobDetail = JobBuilder.Create<AccessLogCacheJob>().WithIdentity(nameof(AccessLogCacheJob))
// .Build();
//每10秒执行一次将本地缓存转入redis防止丢数据
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(AccessLogCacheJob))
// .WithSimpleSchedule((schedule) => { schedule.WithInterval(TimeSpan.FromSeconds(10)).RepeatForever();; })
// .Build();
}
public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
{
await _localEventBus.PublishAsync(new AccessLogResetArgs());
}
}

View File

@@ -1,11 +1,9 @@
using FreeRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Quartz;
using Volo.Abp.BackgroundWorkers.Quartz;
using Volo.Abp.BackgroundWorkers.Hangfire;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Yi.Framework.Bbs.Domain.Entities;
using Yi.Framework.Bbs.Domain.Shared.Caches;
using Yi.Framework.Bbs.Domain.Shared.Enums;
@@ -13,7 +11,7 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Bbs.Application.Jobs;
public class AccessLogStoreJob : QuartzBackgroundWorkerBase
public class AccessLogStoreJob : HangfireBackgroundWorkerBase
{
private readonly ISqlSugarRepository<AccessLogAggregateRoot> _repository;
@@ -45,24 +43,29 @@ public class AccessLogStoreJob : QuartzBackgroundWorkerBase
public AccessLogStoreJob(ISqlSugarRepository<AccessLogAggregateRoot> repository)
{
_repository = repository;
JobDetail = JobBuilder.Create<AccessLogStoreJob>().WithIdentity(nameof(AccessLogStoreJob))
.Build();
RecurringJobId = "访问日志写入数据库";
//每分钟执行一次
Trigger = TriggerBuilder.Create().WithIdentity(nameof(AccessLogStoreJob))
.WithCronSchedule("0 * * * * ?")
.Build();
CronExpression = "0 * * * * ?";
// JobDetail = JobBuilder.Create<AccessLogStoreJob>().WithIdentity(nameof(AccessLogStoreJob))
// .Build();
// //每分钟执行一次
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(AccessLogStoreJob))
// .WithCronSchedule("0 * * * * ?")
// .Build();
}
public override async Task Execute(IJobExecutionContext context)
public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
{
if (EnableRedisCache)
{
//当天的访问量
var number =
await RedisClient.GetAsync<long>($"{CacheKeyPrefix}:{AccessLogCacheConst.Key}:{DateTime.Now.Date}");
await RedisClient.GetAsync<long>($"{CacheKeyPrefix}{AccessLogCacheConst.Key}:{DateTime.Now.Date:yyyyMMdd}");
var entity = await _repository._DbQueryable.Where(x => x.AccessLogType == AccessLogTypeEnum.Request)
@@ -81,7 +84,7 @@ public class AccessLogStoreJob : QuartzBackgroundWorkerBase
}
//删除前一天的缓存
await RedisClient.DelAsync($"{CacheKeyPrefix}:{AccessLogCacheConst.Key}:{DateTime.Now.Date.AddDays(-1)}");
await RedisClient.DelAsync($"{CacheKeyPrefix}{AccessLogCacheConst.Key}:{DateTime.Now.Date.AddDays(-1):yyyyMMdd}");
}
}
}

View File

@@ -1,5 +1,4 @@
using Quartz;
using Volo.Abp.BackgroundWorkers.Quartz;
using Volo.Abp.BackgroundWorkers.Hangfire;
using Yi.Framework.Bbs.Domain.Managers;
namespace Yi.Framework.Bbs.Application.Jobs;
@@ -7,20 +6,25 @@ namespace Yi.Framework.Bbs.Application.Jobs;
/// <summary>
/// 每日任务job
/// </summary>
public class AssignmentExpireTimeOutJob : QuartzBackgroundWorkerBase
public class AssignmentExpireTimeOutJob : HangfireBackgroundWorkerBase
{
private readonly AssignmentManager _assignmentManager;
public AssignmentExpireTimeOutJob(AssignmentManager assignmentManager)
{
_assignmentManager = assignmentManager;
JobDetail = JobBuilder.Create<AssignmentExpireTimeOutJob>().WithIdentity(nameof(AssignmentExpireTimeOutJob)).Build();
//每个小时整点执行一次
Trigger = TriggerBuilder.Create().WithIdentity(nameof(AssignmentExpireTimeOutJob)).WithCronSchedule("0 0 * * * ?")
.Build();
RecurringJobId = "每日任务系统超时检测";
//每分钟执行一次
CronExpression = "0 * * * * ?";
//
// JobDetail = JobBuilder.Create<AssignmentExpireTimeOutJob>().WithIdentity(nameof(AssignmentExpireTimeOutJob)).Build();
// //每个小时整点执行一次
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(AssignmentExpireTimeOutJob)).WithCronSchedule("0 0 * * * ?")
// .Build();
}
public override async Task Execute(IJobExecutionContext context)
public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
{
await _assignmentManager.ExpireTimeoutAsync();
}

View File

@@ -1,20 +1,24 @@
using Quartz;
using Volo.Abp.BackgroundWorkers.Quartz;
using Volo.Abp.BackgroundWorkers.Hangfire;
using Yi.Framework.Bbs.Domain.Managers;
namespace Yi.Framework.Bbs.Application.Jobs
{
public class InterestRecordsJob : QuartzBackgroundWorkerBase
public class InterestRecordsJob : HangfireBackgroundWorkerBase
{
private BankManager _bankManager;
public InterestRecordsJob(BankManager bankManager)
{
_bankManager = bankManager;
JobDetail = JobBuilder.Create<InterestRecordsJob>().WithIdentity(nameof(InterestRecordsJob)).Build();
RecurringJobId = "银行利息积分刷新";
//每个小时整点执行一次
Trigger = TriggerBuilder.Create().WithIdentity(nameof(InterestRecordsJob)).WithCronSchedule("0 0 * * * ?").Build();
CronExpression = "0 0 * * * ?";
// JobDetail = JobBuilder.Create<InterestRecordsJob>().WithIdentity(nameof(InterestRecordsJob)).Build();
//
// //每个小时整点执行一次
//
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(InterestRecordsJob)).WithCronSchedule("0 0 * * * ?").Build();
//测试
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(InterestRecordsJob))
@@ -23,7 +27,8 @@ namespace Yi.Framework.Bbs.Application.Jobs
// .RepeatForever())
//.Build();
}
public override async Task Execute(IJobExecutionContext context)
public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
{
//创建一个记录,莫得了
await _bankManager.GetCurrentInterestRate();

View File

@@ -17,9 +17,11 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
public class BbsForumAnalyseService : ApplicationService, IApplicationService
{
private ForumManager _forumManager;
public BbsForumAnalyseService(ForumManager forumManager)
private ISqlSugarRepository<AgreeEntity> _agreeRepository;
public BbsForumAnalyseService(ForumManager forumManager, ISqlSugarRepository<AgreeEntity> agreeRepository)
{
_forumManager = forumManager;
_agreeRepository = agreeRepository;
}
/// <summary>
@@ -38,7 +40,7 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
.Select((discuss, user, info) => new DiscussGetListOutputDto
{
Id = discuss.Id,
IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null, x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
// IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null, x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
User = new BbsUserGetListOutputDto()
{
@@ -52,9 +54,79 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
}, true)
.ToPageListAsync(input.SkipCount, input.MaxResultCount);
var discussId = output.Select(x => x.Id);
//点赞字典key为主题idy为用户ids
var agreeDic =
(await _agreeRepository._DbQueryable.Where(x => discussId.Contains(x.DiscussId)).ToListAsync())
.GroupBy(x => x.DiscussId)
.ToDictionary(x => x.Key, y => y.Select(y => y.CreatorId).ToList());
//等级、是否点赞赋值
output?.ForEach(x =>
{
if (CurrentUser.Id is not null)
{
//默认fasle
if (agreeDic.TryGetValue(x.Id,out var userIds))
{
x.IsAgree = userIds.Contains(CurrentUser.Id);
}
}
});
return output;
}
/// <summary>
/// 作者主题,返回当前作者最新的主题
/// </summary>
/// <returns></returns>
[HttpGet("analyse/bbs-discuss/author/{userId}")]
public async Task<List<DiscussGetListOutputDto>> GetAuthorDiscussAsync(
[FromRoute] Guid userId,
[FromQuery] PagedResultRequestDto input)
{
var output = await _forumManager._discussRepository._DbQueryable.Where(discuss=>discuss.CreatorId==userId)
.Where(discuss=>discuss.PermissionType== DiscussPermissionTypeEnum.Public)
.LeftJoin<UserAggregateRoot>((discuss, user) => discuss.CreatorId == user.Id)
.LeftJoin<BbsUserExtraInfoEntity>((discuss, user, info) => user.Id == info.UserId)
.OrderByDescending(discuss => discuss.CreationTime)
.Select((discuss, user, info) => new DiscussGetListOutputDto
{
Id = discuss.Id,
User = new BbsUserGetListOutputDto()
{
Id = user.Id,
UserName = user.UserName,
Nick = user.Nick,
Icon = user.Icon,
Level = info.Level,
UserLimit = info.UserLimit
}
}, true)
.ToPageListAsync(input.SkipCount, input.MaxResultCount);
var discussId = output.Select(x => x.Id);
//点赞字典key为主题idy为用户ids
var agreeDic =
(await _agreeRepository._DbQueryable.Where(x => discussId.Contains(x.DiscussId)).ToListAsync())
.GroupBy(x => x.DiscussId)
.ToDictionary(x => x.Key, y => y.Select(y => y.CreatorId).ToList());
//等级、是否点赞赋值
output?.ForEach(x =>
{
if (CurrentUser.Id is not null)
{
//默认fasle
if (agreeDic.TryGetValue(x.Id,out var userIds))
{
x.IsAgree = userIds.Contains(CurrentUser.Id);
}
}
});
return output;
}
}
}

View File

@@ -9,6 +9,7 @@ using Yi.Framework.Bbs.Domain.Entities;
using Yi.Framework.Bbs.Domain.Entities.Integral;
using Yi.Framework.Bbs.Domain.Managers;
using Yi.Framework.Bbs.Domain.Shared.Enums;
using Yi.Framework.DigitalCollectibles.Application.Contracts.IServices;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.Rbac.Domain.Authorization;
using Yi.Framework.Rbac.Domain.Shared.Consts;
@@ -20,11 +21,16 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
{
private BbsUserManager _bbsUserManager;
private IOnlineService _onlineService;
private readonly IPointAnalyseService _pointAnalyseService;
private readonly IValueAnalyseService _valueAnalyseService;
public BbsUserAnalyseService(BbsUserManager bbsUserManager, IOnlineService onlineService)
public BbsUserAnalyseService(BbsUserManager bbsUserManager, IOnlineService onlineService,
IPointAnalyseService pointAnalyseService, IValueAnalyseService valueAnalyseService)
{
_bbsUserManager = bbsUserManager;
_onlineService = onlineService;
_pointAnalyseService = pointAnalyseService;
_valueAnalyseService = valueAnalyseService;
}
@@ -110,7 +116,9 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
)
.ToPageListAsync(pageIndex, input.MaxResultCount, total);
output.ForEach(x => { x.LevelName = _bbsUserManager._levelCacheDic[x.Level].Name; });
var levelCache = await _bbsUserManager.GetLevelCacheMapAsync();
output.ForEach(x => { x.LevelName = levelCache[x.Level].Name; });
return new PagedResultDto<MoneyTopUserDto>
{
Items = output,
@@ -169,5 +177,118 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
return output;
}
}
/// <summary>
/// 积分排行榜
/// </summary>
/// <returns></returns>
[HttpGet("analyse/dc-user/points-top/{userId?}")]
public async Task<PagedResultDto<PointsTopUserDto>> GetPointsTopAsync([FromQuery] PagedResultRequestDto input,
[FromRoute] Guid? userId)
{
var result = await _pointAnalyseService.GetValueTopAsync(input, null);
var userIds = result.Items.Select(x => x.UserId).ToList();
var baseOutput = await _bbsUserManager._userRepository._DbQueryable
.Where(u => userIds.Contains(u.Id))
.LeftJoin<BbsUserExtraInfoEntity>((u, info) => u.Id == info.UserId)
.Select((u, info) =>
new BaseAnalyseTopUserDto
{
UserName = u.UserName,
Nick = u.Nick,
Icon = u.Icon,
Level = info.Level,
UserLimit = info.UserLimit,
UserId = info.UserId
}
).ToListAsync();
var output = new List<PointsTopUserDto>();
var levelCache = await _bbsUserManager.GetLevelCacheMapAsync();
result.Items.ToList().ForEach(x =>
{
var currentUserInfo = baseOutput.Where(u => u.UserId == x.UserId).FirstOrDefault();
if (currentUserInfo is not null)
{
output.Add(new PointsTopUserDto
{
UserName = currentUserInfo.UserName,
Nick = currentUserInfo.Nick,
Order = x.Order,
Icon = currentUserInfo.Icon,
Level = currentUserInfo.Level,
LevelName = levelCache[currentUserInfo.Level].Name,
UserLimit = UserLimitEnum.Normal,
Points = x.Points
});
}
});
return new PagedResultDto<PointsTopUserDto>
{
Items = output,
TotalCount = result.TotalCount
};
}
/// <summary>
/// 价值排行榜
/// </summary>
/// <returns></returns>
[HttpGet("analyse/dc-user/value-top/{userId?}")]
public async Task<PagedResultDto<ValueTopUserDto>> GetValueTopAsync([FromQuery] PagedResultRequestDto input,
[FromRoute] Guid? userId)
{
var result = await _valueAnalyseService.GetValueTopAsync(input, null);
var userIds = result.Items.Select(x => x.UserId).ToList();
var baseOutput = await _bbsUserManager._userRepository._DbQueryable
.Where(u => userIds.Contains(u.Id))
.LeftJoin<BbsUserExtraInfoEntity>((u, info) => u.Id == info.UserId)
.Select((u, info) =>
new BaseAnalyseTopUserDto
{
UserName = u.UserName,
Nick = u.Nick,
Icon = u.Icon,
Level = info.Level,
UserLimit = info.UserLimit,
UserId = info.UserId
}
).ToListAsync();
var output = new List<ValueTopUserDto>();
var levelCache = await _bbsUserManager.GetLevelCacheMapAsync();
result.Items.ToList().ForEach(x =>
{
var currentUserInfo = baseOutput.Where(u => u.UserId == x.UserId).FirstOrDefault();
if (currentUserInfo is not null)
{
output.Add(new ValueTopUserDto
{
UserName = currentUserInfo.UserName,
Nick = currentUserInfo.Nick,
Order = x.Order,
Icon = currentUserInfo.Icon,
Level = currentUserInfo.Level,
LevelName =levelCache[currentUserInfo.Level].Name,
UserLimit = UserLimitEnum.Normal,
Value = x.Value
});
}
});
return new PagedResultDto<ValueTopUserDto>
{
Items = output,
TotalCount = result.TotalCount
};
}
}
}

View File

@@ -27,13 +27,19 @@ namespace Yi.Framework.Bbs.Application.Services
var userEntity = await _bbsUserManager._userRepository.GetFirstAsync(x => x.UserName == userNameOrUserId);
if (userEntity == null)
{
throw new Volo.Abp.UserFriendlyException("该用户不存在");
throw new UserFriendlyException("该用户不存在");
}
userId= userEntity.Id;
}
var output =await _bbsUserManager.GetBbsUserInfoAsync(userId);
//不是自己
if (CurrentUser.Id != output.Id)
{
output.Phone = null;
output.Email=null;
}
return output!;
}
}

View File

@@ -30,22 +30,21 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
/// <summary>
/// Article服务实现
/// </summary>
public class ArticleService : YiCrudAppService<ArticleAggregateRoot, ArticleGetOutputDto, ArticleGetListOutputDto, Guid, ArticleGetListInputVo, ArticleCreateInputVo, ArticleUpdateInputVo>,
IArticleService
public class ArticleService : YiCrudAppService<ArticleAggregateRoot, ArticleGetOutputDto, ArticleGetListOutputDto,
Guid, ArticleGetListInputVo, ArticleCreateInputVo, ArticleUpdateInputVo>,
IArticleService
{
public ArticleService(IArticleRepository articleRepository,
ISqlSugarRepository<DiscussAggregateRoot> discussRepository,
IDiscussService discussService,
ForumManager forumManager) : base(articleRepository)
{
_articleRepository = articleRepository;
_discussRepository = discussRepository;
_discussService = discussService;
_forumManager = forumManager;
}
private ForumManager _forumManager;
private IArticleRepository _articleRepository;
private ISqlSugarRepository<DiscussAggregateRoot> _discussRepository;
@@ -55,13 +54,34 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
{
RefAsync<int> total = 0;
var entities = await _articleRepository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.Name), x => x.Name.Contains(input.Name!))
//.WhereIF(!string.IsNullOrEmpty(input.Code), x => x.Name.Contains(input.Code!))
.WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
var entities = await _articleRepository._DbQueryable
.WhereIF(!string.IsNullOrEmpty(input.Name), x => x.Name.Contains(input.Name!))
.WhereIF(input.StartTime is not null && input.EndTime is not null,
x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<ArticleGetListOutputDto>(total, await MapToGetListOutputDtosAsync(entities));
}
/// <summary>
/// 查询文章
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public override async Task<ArticleGetOutputDto> GetAsync(Guid id)
{
var entity = await _articleRepository.GetAsync(id);
var output = entity.Adapt<ArticleGetOutputDto>();
if (!await _forumManager.VerifyDiscussPermissionAsync(entity.DiscussId, CurrentUser.Id, CurrentUser.Roles))
{
output.SetNoPermission();
}
else
{
output.SetPassPermission();
}
return output;
}
/// <summary>
/// 获取文章全部树级信息
@@ -72,17 +92,13 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
[Route("article/all/discuss-id/{discussId}")]
public async Task<List<ArticleAllOutputDto>> GetAllAsync([FromRoute] Guid discussId)
{
await _discussService.VerifyDiscussPermissionAsync(discussId);
var entities = await _articleRepository.GetTreeAsync(x => x.DiscussId == discussId);
//var result = entities.Tile();
var items = entities.Adapt<List<ArticleAllOutputDto>>();
return items;
}
/// <summary>
/// 查询文章
/// 查询文章概述
/// </summary>
/// <param name="discussId"></param>
/// <returns></returns>
@@ -109,7 +125,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
[Authorize]
public async override Task<ArticleGetOutputDto> CreateAsync(ArticleCreateInputVo input)
{
await VerifyDiscussCreateIdAsync(input.DiscussId);
await VerifyPermissionAsync(input.DiscussId);
return await base.CreateAsync(input);
}
@@ -122,7 +138,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
public override async Task<ArticleGetOutputDto> UpdateAsync(Guid id, ArticleUpdateInputVo input)
{
var entity = await _articleRepository.GetByIdAsync(id);
await VerifyDiscussCreateIdAsync(entity.DiscussId);
await VerifyPermissionAsync(entity.DiscussId);
return await base.UpdateAsync(id, input);
}
@@ -135,7 +151,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
public override async Task DeleteAsync(Guid id)
{
var entity = await _articleRepository.GetByIdAsync(id);
await VerifyDiscussCreateIdAsync(entity.DiscussId);
await VerifyPermissionAsync(entity.DiscussId);
await base.DeleteAsync(id);
}
@@ -144,8 +160,10 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
/// 导入文章
/// </summary>
/// <returns></returns>
public async Task PostImportAsync([FromQuery] ArticleImprotDto input, [FromForm][Required] IFormFileCollection file)
public async Task PostImportAsync([FromQuery] ArticleImprotDto input,
[FromForm] [Required] IFormFileCollection file)
{
await VerifyPermissionAsync(input.DiscussId);
var fileObjs = new List<FileObject>();
if (file.Count > 0)
{
@@ -172,45 +190,18 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
{
throw new UserFriendlyException("未选择文件");
}
//使用简单工厂根据传入的类型进行判断
await _forumManager.PostImportAsync(input.DiscussId, input.ArticleParentId, fileObjs, input.ImportType);
}
/// <summary>
/// 校验创建权限userId为主题创建者
/// </summary>
/// <param name="disucssId"></param>
/// <returns></returns>
private async Task VerifyDiscussCreateIdAsync(Guid disucssId)
private async Task VerifyPermissionAsync(Guid discussId)
{
var discuss = await _discussRepository.GetFirstAsync(x => x.Id == disucssId);
if (discuss is null)
if (!await _forumManager.VerifyDiscussPermissionAsync(discussId, CurrentUser.Id, isVerifyLook: false))
{
throw new UserFriendlyException(DiscussConst.No_Exist);
}
//这块有点绕,这个版本的写法比较清晰
bool result = false;
if (CurrentUser.GetPermissions().Contains(UserConst.AdminPermissionCode))
{
//如果是超管,直接跳过
result = true;
}
else
{
//如果不是超管,必须满足作者是自己,同时还有发布的权限
if (discuss.CreatorId == CurrentUser.Id)
{
result = true;
}
}
if (!result)
{
throw new UserFriendlyException("权限不足,请联系主题作者或管理员申请开通");
throw new UserFriendlyException("您无权限进行操作", "403");
}
}
}
}
}

View File

@@ -44,14 +44,13 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
private IDiscussService _discussService { get; set; }
/// <summary>
/// 获取改主题下的评论,结构为二维列表,该查询无分页
/// Todo: 可放入领域层
/// </summary>
/// <param name="discussId"></param>
/// <param name="input"></param>
/// <returns></returns>
public async Task<PagedResultDto<CommentGetListOutputDto>> GetDiscussIdAsync([FromRoute] Guid discussId, [FromQuery] CommentGetListInputVo input)
{
await _discussService.VerifyDiscussPermissionAsync(discussId);
await _forumManager.VerifyDiscussPermissionAsync(discussId,CurrentUser.Id);
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.Content), x => x.Content.Contains(input.Content))
.Where(x => x.DiscussId == discussId)
@@ -64,15 +63,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
List<Guid> userIds = entities.Where(x => x.CreatorId != null).Select(x => x.CreatorId ?? Guid.Empty).ToList();
var bbsUserInfoDic = (await _bbsUserManager.GetBbsUserInfoAsync(userIds)).ToDictionary(x => x.Id);
//------数据查询完成------
//------数据查询完成------,以下只是dto的简单组装
//从根目录开始组装
//结果初始值,第一层等于全部根节点
@@ -117,11 +108,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
rootOutoutDto?.ForEach(x =>
{
x.Children = x.Children.OrderByDescending(x => x.CreationTime).ToList();
});
return new PagedResultDto<CommentGetListOutputDto>(entities.Count(), rootOutoutDto);
}
@@ -136,6 +123,11 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
[Authorize]
public override async Task<CommentGetOutputDto> CreateAsync(CommentCreateInputVo input)
{
if (string.IsNullOrWhiteSpace(input.Content)|| input.Content=="<p><br></p>")
{
throw new UserFriendlyException("评论不能为空");
}
var discuess = await _discussRepository.GetFirstAsync(x => x.Id == input.DiscussId);
if (discuess is null)
{

View File

@@ -0,0 +1,53 @@
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Data;
using Yi.Framework.Bbs.Application.Contracts.Dtos.MyType;
using Yi.Framework.Bbs.Application.Contracts.IServices;
using Yi.Framework.Bbs.Domain.Entities.Forum;
using Yi.Framework.Ddd.Application;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Bbs.Application.Services.Forum
{
/// <summary>
/// DiscussLable服务实现
/// </summary>
public class DiscussLableService : YiCrudAppService<DiscussLableAggregateRoot, DiscussLableOutputDto,
DiscussLableGetListOutputDto, Guid, DiscussLableGetListInputVo, DiscussLableCreateInputVo,
DiscussLableUpdateInputVo>,
IDiscussLableService
{
private ISqlSugarRepository<DiscussLableAggregateRoot, Guid> _repository;
public DiscussLableService(ISqlSugarRepository<DiscussLableAggregateRoot, Guid> repository) : base(repository)
{
_repository = repository;
}
[HttpGet("discuss-lable/all")]
public async Task<ListResultDto<DiscussLableGetListOutputDto>> GetAllListAsync(DiscussLableGetListInputVo input)
{
var order = input.Sorting ?? nameof(DiscussLableAggregateRoot.Name);
var output = await _repository._DbQueryable
.WhereIF(input.Name is not null, x => x.Name.Contains(input.Name))
.OrderBy(order)
.Select(x => new DiscussLableGetListOutputDto(), true)
.ToListAsync();
return new ListResultDto<DiscussLableGetListOutputDto>(output);
}
public override async Task<PagedResultDto<DiscussLableGetListOutputDto>> GetListAsync(
DiscussLableGetListInputVo input)
{
RefAsync<int> total = 0;
var order = input.Sorting ?? nameof(DiscussLableAggregateRoot.Name);
var output = await _repository._DbQueryable
.WhereIF(input.Name is not null, x => x.Name.Contains(input.Name))
.OrderBy(order)
.Select(x => new DiscussLableGetListOutputDto(), true)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<DiscussLableGetListOutputDto>(total, output);
}
}
}

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