Compare commits

..

215 Commits

Author SHA1 Message Date
橙子
45736dfce9 feat: 完成多租户优化改造 2025-02-22 15:26:00 +08:00
chenchun
753b5b0a26 feat: 优化多租户配置 2025-02-21 18:00:06 +08:00
橙子
1fd4f2754a feat:优化文章性能 2025-02-12 22:25:27 +08:00
橙子
bedee3391e feat:聊天室支持公式,优化文章 2025-02-12 22:25:15 +08:00
橙子
176a672e86 update README-en.md.
Signed-off-by: 橙子 <454313500@qq.com>
2025-02-09 14:29:55 +00:00
橙子
9da6bcde41 update README.md.
Signed-off-by: 橙子 <454313500@qq.com>
2025-02-09 14:29:18 +00:00
橙子
3f8daa1d17 update README-Docker.md.
Signed-off-by: 橙子 <454313500@qq.com>
2025-02-09 14:25:52 +00:00
橙子
ed873da3b6 feat: 支持docker 2025-02-09 22:23:23 +08:00
橙子
c0dfa83828 doc: 完善docker文档 2025-02-09 01:49:07 +08:00
橙子
db08688968 Merge remote-tracking branch 'origin/abp' into abp 2025-02-09 01:28:22 +08:00
橙子
400a146a48 feat: 新增docker支持 2025-02-09 01:28:13 +08:00
chenchun
a645264da7 feat: 统一修改时区 2025-02-08 10:39:53 +08:00
chenchun
09d19d876f fix: 时区默认采用上海 2025-02-08 10:37:33 +08:00
橙子
373877cfcf feat: 支持hangfire内存模式 2025-02-07 17:52:38 +08:00
橙子
9e143c0a75 style: 修改job时间 2025-02-07 16:06:46 +08:00
橙子
37b16e8395 perf: 整体优化细节 2025-02-06 12:54:48 +08:00
橙子
9c94953e0e fix: 修复文件上传问题 2025-02-06 11:41:56 +08:00
橙子
2c48b8f881 feat:新增面试宝典 2025-02-05 11:58:50 +08:00
橙子
4c4b78dda7 perf: 优化包版本 2025-02-05 11:36:20 +08:00
橙子
4ba9a7917f feat:支持更新时间或者创建时间排序 2025-02-05 00:59:25 +08:00
橙子
5d286ebc9e feat: db操作支持不修改更新审计日志 2025-02-05 00:02:04 +08:00
橙子
e69bbb46b3 fix: 修复跳转刷新问题 2025-02-04 15:47:59 +08:00
橙子
f203c53e19 Merge branch 'abp' into digital-collectibles 2025-02-04 15:40:13 +08:00
橙子
8a9a0c8396 fix: 修复跳转刷新问题 2025-02-04 15:40:00 +08:00
橙子
813b08bf1c Merge branch 'abp' into digital-collectibles
# Conflicts:
#	Yi.Bbs.Vue3/src/views/home/Index.vue
2025-02-04 15:28:23 +08:00
橙子
bce9b58265 perf: 优化bbs前端整体显示 2025-02-04 15:23:20 +08:00
橙子
85223629c1 Merge branch 'refs/heads/abp' into digital-collectibles 2025-02-03 10:30:26 +08:00
橙子
9a73789788 feat: 全面支持deepseek 2025-02-03 01:18:15 +08:00
橙子
25929483c3 feat: 支持多ai聊天 2025-02-02 00:22:27 +08:00
橙子
0e90c54dbb Merge remote-tracking branch 'origin/abp' into abp 2025-02-01 21:53:12 +08:00
橙子
5dea4ab18c feat: 支持分布式锁 2025-02-01 21:53:05 +08:00
橙子
7187397687 !87 update Yi.RuoYi.Vue3/src/views/system/role/index.vue.
Merge pull request !87 from YangHaiPing/N/A
2025-01-31 13:58:12 +00:00
橙子
bf8454a963 !84 拼写错误
Merge pull request !84 from 高级CV工程师/abp
2025-01-31 13:57:21 +00:00
橙子
2b2ac9b924 !86 refactor(tool): 使工具支持跨平台运行
Merge pull request !86 from a2008q/abp
2025-01-31 13:56:36 +00:00
YangHaiPing
78b874936c update Yi.RuoYi.Vue3/src/views/system/role/index.vue.
系统管理-角色管理-数据权限:数据回显时“权限范围”字段信息展示的为对应索引值,不是下拉选项值

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

2
.gitignore vendored
View File

@@ -263,6 +263,7 @@ src/Acme.BookStore.Blazor.Server.Tiered/Logs/*
# Use abp install-libs to restore.
**/wwwroot/libs/*
public
dist
.vscode
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Development.json
@@ -275,3 +276,4 @@ database_backup
/Yi.Abp.Net8/src/Yi.Abp.Web/logs/
/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db
package-lock.json

37
README-Docker.md Normal file
View File

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

View File

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

View File

@@ -41,6 +41,17 @@ Yi框架-一套与SqlSugar一样爽的.Net8开源框架。
- Yi.Pure.Vue3Pure ts后台前端
- Yi.RuoYi.Vue3RuoYi js后台前端
****
## 🍉 docker 一键启动
完整内容在README-Docker.md
后端:`docker run -d --name yi.admin -p 19001:19001 jiftcc/yi.admin:last`
bbs前端`docker run -d --name yi.bbs -p 18001:18001 -v /home/Yi/Yi.Bbs.Vue3/yi-bbs.conf:/etc/nginx/conf.d/yi-bbs.conf jiftcc/yi.bbs:last`
> 另外我们提供docker的build操作我们更希望你能通过此种方式二开构建属于自己的镜像
****
## 🍊 官网及演示地址:
@@ -60,6 +71,7 @@ Pure后台演示地址https://ccnetcore.com:1001 用户cc、密码123456
- [x] 完全支持微服务架构
****
## 🍇 详细到爆炸的Yi框架教程导航
1. [框架快速开始教程](https://ccnetcore.com/article/aaa00329-7f35-d3fe-d258-3a0f8380b742)(已完成)

View File

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

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

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

View File

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

View File

@@ -36,6 +36,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
version.props = version.props
publish.bat = publish.bat
publish_Demo.bat = publish_Demo.bat
Dockerfile = Dockerfile
DockerfileFast = DockerfileFast
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.SqlSugarCore.Abstractions", "framework\Yi.Framework.SqlSugarCore.Abstractions\Yi.Framework.SqlSugarCore.Abstractions.csproj", "{FD6D6860-3753-4747-8A26-977E4A3001F9}"
@@ -156,6 +158,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
@@ -390,6 +408,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
@@ -459,6 +505,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,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,55 @@
using System.Linq.Expressions;
using Hangfire;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.BackgroundWorkers.Hangfire;
using Volo.Abp.DynamicProxy;
namespace Yi.Framework.BackgroundWorkers.Hangfire;
[DependsOn(typeof(AbpBackgroundWorkersHangfireModule))]
public class YiFrameworkBackgroundWorkersHangfireModule : AbpModule
{
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>();
var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>();
//【特殊,为了兼容内存模式,由于内存模式任务,不能使用队列】
bool.TryParse(configuration["Redis:IsEnabled"], out var redisEnabled);
foreach (var work in works)
{
//如果为空,默认使用服务器本地上海时间
work.TimeZone = TimeZoneInfo.Local;
if (redisEnabled)
{
await backgroundWorkerManager.AddAsync(work);
}
else
{
object unProxyWorker = ProxyHelper.UnProxy((object)work);
RecurringJob.AddOrUpdate(work.RecurringJobId,
(Expression<Func<Task>>)(() =>
((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(default(CancellationToken))),
work.CronExpression, new RecurringJobOptions()
{
TimeZone = work.TimeZone
});
}
}
}
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

@@ -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

@@ -1,4 +1,5 @@
using SqlSugar;
using ArgumentException = System.ArgumentException;
namespace Yi.Framework.SqlSugarCore.Abstractions
{
@@ -53,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

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

View File

@@ -0,0 +1,301 @@
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(null);
}
else
{
entityInfo.SetValue(DateTime.Now);
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
{
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
//最后更新者已经是空guid忽略
if (Guid.Empty.Equals(oldValue))
{
entityInfo.SetValue(null);
}
else 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

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

View File

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

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

View File

@@ -0,0 +1,100 @@
using Microsoft.Extensions.Options;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore;
/// <summary>
/// 租户配置
/// </summary>
public class TenantConfigurationWrapper : ITransientDependency
{
private readonly IAbpLazyServiceProvider _serviceProvider;
private ICurrentTenant CurrentTenant => _serviceProvider.LazyGetRequiredService<ICurrentTenant>();
private ITenantStore TenantStore => _serviceProvider.LazyGetRequiredService<ITenantStore>();
private DbConnOptions DbConnOptions => _serviceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
public TenantConfigurationWrapper(IAbpLazyServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
/// <summary>
/// 获取租户信息
/// [from:ai]
/// </summary>
/// <returns></returns>
public async Task<TenantConfiguration?> GetAsync()
{
//未开启多租户
if (!DbConnOptions.EnabledSaasMultiTenancy)
{
return await TenantStore.FindAsync(ConnectionStrings.DefaultConnectionStringName);
}
TenantConfiguration? tenantConfiguration = null;
if (CurrentTenant.Id is not null)
{
tenantConfiguration = await TenantStore.FindAsync(CurrentTenant.Id.Value);
if (tenantConfiguration == null)
{
throw new ApplicationException($"未找到租户信息,租户Id:{CurrentTenant.Id}");
}
return tenantConfiguration;
}
if (!string.IsNullOrEmpty(CurrentTenant.Name))
{
tenantConfiguration = await TenantStore.FindAsync(CurrentTenant.Name);
if (tenantConfiguration == null)
{
throw new ApplicationException($"未找到租户信息,租户名称:{CurrentTenant.Name}");
}
return tenantConfiguration;
}
return await TenantStore.FindAsync(ConnectionStrings.DefaultConnectionStringName);
}
/// <summary>
/// 获取当前连接字符串
/// </summary>
/// <returns></returns>
public async Task<string> GetCurrentConnectionStringAsync()
{
return (await GetAsync()).ConnectionStrings.Default!;
}
/// <summary>
/// 获取当前连接名
/// </summary>
/// <returns></returns>
public async Task<string> GetCurrentConnectionNameAsync()
{
return (await GetAsync()).Name;
}
}
public static class TenantConfigurationExtensions
{
/// <summary>
/// 获取当前连接字符串
/// </summary>
/// <returns></returns>
public static string GetCurrentConnectionString(this TenantConfiguration tenantConfiguration)
{
return tenantConfiguration.ConnectionStrings.Default!;
}
/// <summary>
/// 获取当前连接名
/// </summary>
/// <returns></returns>
public static string GetCurrentConnectionName(this TenantConfiguration tenantConfiguration)
{
return tenantConfiguration.Name;
}
}

View File

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

View File

@@ -13,12 +13,10 @@ 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; }
private static AsyncLocalDbContextAccessor ContextInstance => AsyncLocalDbContextAccessor.Instance;
protected readonly TenantConfigurationWrapper _tenantConfigurationWrapper;
protected readonly IUnitOfWorkManager UnitOfWorkManager;
protected readonly IConnectionStringResolver ConnectionStringResolver;
protected readonly ICancellationTokenProvider CancellationTokenProvider;
@@ -28,43 +26,36 @@ namespace Yi.Framework.SqlSugarCore.Uow
IUnitOfWorkManager unitOfWorkManager,
IConnectionStringResolver connectionStringResolver,
ICancellationTokenProvider cancellationTokenProvider,
ICurrentTenant currentTenant,
ISqlSugarDbConnectionCreator dbConnectionCreator
)
ICurrentTenant currentTenant, TenantConfigurationWrapper tenantConfigurationWrapper)
{
UnitOfWorkManager = unitOfWorkManager;
ConnectionStringResolver = connectionStringResolver;
CancellationTokenProvider = cancellationTokenProvider;
CurrentTenant = currentTenant;
_tenantConfigurationWrapper = tenantConfigurationWrapper;
Logger = NullLogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>>.Instance;
_dbConnectionCreator = dbConnectionCreator;
}
//private static object _databaseApiLock = new object();
public virtual async Task<TDbContext> GetDbContextAsync()
{
var connectionStringName = ConnectionStrings.DefaultConnectionStringName;
//获取当前连接字符串,未多租户时,默认为空
var connectionString = await ResolveConnectionStringAsync(connectionStringName);
var tenantConfiguration= await _tenantConfigurationWrapper.GetAsync();
//由于sqlsugar的特殊性没有db区分不再使用连接字符串解析器
var connectionStringName = tenantConfiguration.GetCurrentConnectionName();
var connectionString = tenantConfiguration.GetCurrentConnectionString();
var dbContextKey = $"{this.GetType().Name}_{connectionString}";
var unitOfWork = UnitOfWorkManager.Current;
if (unitOfWork == null /*|| unitOfWork.Options.IsTransactional == false*/)
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);
@@ -75,8 +66,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
databaseApi = new SqlSugarDatabaseApi(
await CreateDbContextAsync(unitOfWork, connectionStringName, connectionString)
);
//await Console.Out.WriteLineAsync(">>>----------------实例化了db"+ ((SqlSugarDatabaseApi)databaseApi).DbContext.SqlSugarClient.ContextID.ToString());
//创建的db加入到当前工作单元中
unitOfWork.AddDatabaseApi(dbContextKey, databaseApi);
@@ -106,7 +95,7 @@ namespace Yi.Framework.SqlSugarCore.Uow
protected virtual async Task<TDbContext> CreateDbContextWithTransactionAsync(IUnitOfWork unitOfWork)
{
//事务key
var transactionApiKey = $"SqlsugarCore_{SqlSugarDbContextCreationContext.Current.ConnectionString}";
var transactionApiKey = $"SqlSugarCore_{SqlSugarDbContextCreationContext.Current.ConnectionString}";
//尝试查找事务
var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as SqlSugarTransactionApi;
@@ -131,20 +120,5 @@ namespace Yi.Framework.SqlSugarCore.Uow
}
protected virtual async Task<string> ResolveConnectionStringAsync(string connectionStringName)
{
if (typeof(TDbContext).IsDefined(typeof(IgnoreMultiTenancyAttribute), false))
{
using (CurrentTenant.Change(null))
{
return await ConnectionStringResolver.ResolveAsync(connectionStringName);
}
}
return await ConnectionStringResolver.ResolveAsync(connectionStringName);
}
}
}

View File

@@ -6,12 +6,12 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.Domain;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Modularity;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.MultiTenancy.ConfigurationStore;
using Yi.Framework.SqlSugarCore.Abstractions;
using Yi.Framework.SqlSugarCore.Repositories;
using Yi.Framework.SqlSugarCore.Uow;
@@ -27,8 +27,33 @@ namespace Yi.Framework.SqlSugarCore
var configuration = service.GetConfiguration();
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.TryAddTransient<ISqlSugarDbContext, SqlSugarDbContextFactory>();
//不开放sqlsugarClient
//service.AddTransient<ISqlSugarClient>(x => x.GetRequiredService<ISqlsugarDbContext>().SqlSugarClient);
@@ -46,7 +71,26 @@ namespace Yi.Framework.SqlSugarCore
var dbConfig = section.Get<DbConnOptions>();
//将默认db传递给abp连接字符串模块
Configure<AbpDbConnectionOptions>(x => { x.ConnectionStrings.Default = dbConfig.Url; });
//配置abp默认租户对接abp模块
Configure<AbpDefaultTenantStoreOptions>(x => {
var tenantList = x.Tenants.ToList();
foreach(var tenant in tenantList)
{
tenant.NormalizedName = tenant.Name.Contains("@") ?
tenant.Name.Substring(0, tenant.Name.LastIndexOf("@")) :
tenant.Name;
}
tenantList.Insert(0, new TenantConfiguration
{
Id = Guid.Empty,
Name = $"{ConnectionStrings.DefaultConnectionStringName}",
NormalizedName = ConnectionStrings.DefaultConnectionStringName,
ConnectionStrings = new ConnectionStrings() { { ConnectionStrings.DefaultConnectionStringName, dbConfig.Url } },
IsActive = true
});
x.Tenants = tenantList.ToArray();
});
context.Services.AddYiDbContext<DefaultSqlSugarDbContext>();
return Task.CompletedTask;
}
@@ -72,7 +116,6 @@ namespace Yi.Framework.SqlSugarCore
logger.LogInformation(sb.ToString());
//Todo准备支持多租户种子数据及CodeFirst
if (options.EnabledCodeFirst)
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,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;
@@ -17,7 +18,6 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
/// </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; }
@@ -41,57 +41,16 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
/// 封面
/// </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

@@ -1,8 +1,8 @@
using FreeRedis;
using Hangfire;
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;
@@ -15,23 +15,27 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Bbs.Application.Jobs;
public class AccessLogCacheJob : QuartzBackgroundWorkerBase
public class AccessLogCacheJob : HangfireBackgroundWorkerBase
{
private readonly ILocalEventBus _localEventBus;
public AccessLogCacheJob(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
JobDetail = JobBuilder.Create<AccessLogCacheJob>().WithIdentity(nameof(AccessLogCacheJob))
.Build();
RecurringJobId = "访问日志写入缓存";
//每分钟执行一次将本地缓存转入redis防止丢数据
CronExpression = "0 * * * * ?";
//
// 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();
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(AccessLogCacheJob))
// .WithSimpleSchedule((schedule) => { schedule.WithInterval(TimeSpan.FromSeconds(10)).RepeatForever();; })
// .Build();
}
public override async Task Execute(IJobExecutionContext context)
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,18 +43,23 @@ public class AccessLogStoreJob : QuartzBackgroundWorkerBase
public AccessLogStoreJob(ISqlSugarRepository<AccessLogAggregateRoot> repository)
{
_repository = repository;
JobDetail = JobBuilder.Create<AccessLogStoreJob>().WithIdentity(nameof(AccessLogStoreJob))
.Build();
//每分钟执行一次
Trigger = TriggerBuilder.Create().WithIdentity(nameof(AccessLogStoreJob))
.WithCronSchedule("0 * * * * ?")
.Build();
RecurringJobId = "访问日志写入数据库";
//每小时执行一次
CronExpression = "0 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)
{

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

@@ -77,6 +77,56 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
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);
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Linq;
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
@@ -9,10 +10,12 @@ using Volo.Abp.EventBus.Local;
using Volo.Abp.Users;
using Yi.Framework.Bbs.Application.Contracts.Dtos.BbsUser;
using Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss;
using Yi.Framework.Bbs.Application.Contracts.Dtos.DiscussLable;
using Yi.Framework.Bbs.Application.Contracts.IServices;
using Yi.Framework.Bbs.Domain.Entities;
using Yi.Framework.Bbs.Domain.Entities.Forum;
using Yi.Framework.Bbs.Domain.Managers;
using Yi.Framework.Bbs.Domain.Repositories;
using Yi.Framework.Bbs.Domain.Shared.Consts;
using Yi.Framework.Bbs.Domain.Shared.Enums;
using Yi.Framework.Bbs.Domain.Shared.Etos;
@@ -36,16 +39,17 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
private ISqlSugarRepository<DiscussTopEntity> _discussTopRepository;
private ISqlSugarRepository<AgreeEntity> _agreeRepository;
private BbsUserManager _bbsUserManager;
private IDiscussLableRepository _discussLableRepository;
public DiscussService(BbsUserManager bbsUserManager, ForumManager forumManager,
ISqlSugarRepository<DiscussTopEntity> discussTopRepository,
ISqlSugarRepository<PlateAggregateRoot> plateEntityRepository, ILocalEventBus localEventBus,
ISqlSugarRepository<AgreeEntity> agreeRepository) : base(forumManager._discussRepository)
ISqlSugarRepository<AgreeEntity> agreeRepository, IDiscussLableRepository discussLableRepository) : base(forumManager._discussRepository)
{
_forumManager = forumManager;
_plateEntityRepository = plateEntityRepository;
_localEventBus = localEventBus;
_agreeRepository = agreeRepository;
_discussLableRepository = discussLableRepository;
_discussTopRepository = discussTopRepository;
_bbsUserManager = bbsUserManager;
}
@@ -55,8 +59,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
private ISqlSugarRepository<PlateAggregateRoot> _plateEntityRepository { get; set; }
/// <summary>
/// 单查
/// </summary>
@@ -65,15 +68,14 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
public async override Task<DiscussGetOutputDto> GetAsync(Guid id)
{
//查询主题发布 浏览主题 事件,浏览数+1
var item = await _forumManager._discussRepository._DbQueryable
var output = await _forumManager._discussRepository._DbQueryable
.LeftJoin<UserAggregateRoot>((discuss, user) => discuss.CreatorId == user.Id)
.LeftJoin<BbsUserExtraInfoEntity>((discuss, user, info) => user.Id == info.UserId)
.LeftJoin<PlateAggregateRoot>((discuss, user, info, plate) => plate.Id == discuss.PlateId)
.Select((discuss, user, info, plate) => new DiscussGetOutputDto
{
Id = discuss.Id,
IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null,
x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
IsAgree = false,
User = new BbsUserGetListOutputDto()
{
UserName = user.UserName,
@@ -94,19 +96,46 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
Logo = plate.Logo
}
}, true)
.SingleAsync(discuss => discuss.Id == id);
.FirstAsync(discuss => discuss.Id == id);
if (item is not null)
if (output is null)
{
await VerifyDiscussPermissionAsync(item.Id);
await _localEventBus.PublishAsync(new SeeDiscussEventArgs
{ DiscussId = item.Id, OldSeeNum = item.SeeNum });
throw new UserFriendlyException("该主题不存在", "404");
}
//组装点赞
var agreeCreatorList =
(await _agreeRepository._DbQueryable.Where(x => x.DiscussId == output.Id).Select(x=>x.CreatorId).ToListAsync());
//已登录
if (CurrentUser.Id is not null)
{
output.IsAgree = agreeCreatorList.Contains(CurrentUser.Id);
}
//组装标签
var lableDic=await _discussLableRepository.GetDiscussLableCacheMapAsync();
foreach (var lableId in output.DiscussLableIds)
{
if (lableDic.TryGetValue(lableId,out var item))
{
output.Lables.Add(item.Adapt<DiscussLableGetOutputDto>());
}
}
return item;
//如果没有权限
if (!await _forumManager.VerifyDiscussPermissionAsync(output.Id,CurrentUser.Id, CurrentUser.Roles))
{
output.SetNoPermission();
}
else
{
output.SetPassPermission();
}
await _localEventBus.PublishAsync(new SeeDiscussEventArgs
{ DiscussId = output.Id, OldSeeNum = output.SeeNum });
return output;
}
/// <summary>
/// 查询
/// </summary>
@@ -126,7 +155,11 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
.WhereIF(input.UserName is not null, (discuss, user) => user.UserName == input.UserName!)
.LeftJoin<BbsUserExtraInfoEntity>((discuss, user, info) => user.Id == info.UserId)
.OrderByDescending(discuss => discuss.OrderNum)
.OrderByIF(input.Type == QueryDiscussTypeEnum.New, discuss => discuss.CreationTime, OrderByType.Desc)
//已提示杰哥新增表达式
// .OrderByIF(input.Type == QueryDiscussTypeEnum.New,
// @"COALESCE(discuss.LastModificationTime, discuss.CreationTime) DESC")
//采用上方写法
.OrderByIF(input.Type == QueryDiscussTypeEnum.New,discuss=>SqlFunc.Coalesce(discuss.LastModificationTime,discuss.CreationTime),OrderByType.Desc)
.OrderByIF(input.Type == QueryDiscussTypeEnum.Host, discuss => discuss.SeeNum, OrderByType.Desc)
.OrderByIF(input.Type == QueryDiscussTypeEnum.Suggest, discuss => discuss.AgreeNum, OrderByType.Desc)
.Select((discuss, user, info) => new DiscussGetListOutputDto
@@ -154,14 +187,14 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
(await _agreeRepository._DbQueryable.Where(x => discussId.Contains(x.DiscussId)).ToListAsync())
.GroupBy(x => x.DiscussId)
.ToDictionary(x => x.Key, y => y.Select(y => y.CreatorId).ToList());
//查询完主题之后,要过滤一下私有的主题信息
items.ApplyPermissionTypeFilter(CurrentUser.Id ?? Guid.Empty);
//等级、是否点赞赋值
var levelCacheDic= await _bbsUserManager.GetLevelCacheMapAsync();
var lableDic=await _discussLableRepository.GetDiscussLableCacheMapAsync();
//组装等级、是否点赞赋值、标签
items?.ForEach(x =>
{
x.User.LevelName = _bbsUserManager._levelCacheDic[x.User.Level].Name;
x.User.LevelName = levelCacheDic[x.User.Level].Name;
if (CurrentUser.Id is not null)
{
//默认fasle
@@ -170,6 +203,15 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
x.IsAgree = userIds.Contains(CurrentUser.Id);
}
}
foreach (var lableId in x.DiscussLableIds)
{
if (lableDic.TryGetValue(lableId,out var item))
{
x.Lables.Add(item.Adapt<DiscussLableGetOutputDto>());
}
}
});
return new PagedResultDto<DiscussGetListOutputDto>(total, items);
}
@@ -199,7 +241,6 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
Address = user.Address,
Age = user.Age,
CreationTime = user.CreationTime,
Level = info.Level,
Introduction = user.Introduction,
Icon = user.Icon,
@@ -212,7 +253,20 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
}
}, true)
.ToListAsync();
output?.ForEach(x => x.User.LevelName = _bbsUserManager._levelCacheDic[x.User.Level].Name);
var levelCacheDic= await _bbsUserManager.GetLevelCacheMapAsync();
var lableDic=await _discussLableRepository.GetDiscussLableCacheMapAsync();
output?.ForEach(x =>
{
x.User.LevelName = levelCacheDic[x.User.Level].Name;
foreach (var lableId in x.DiscussLableIds)
{
if (lableDic.TryGetValue(lableId,out var item))
{
x.Lables.Add(item.Adapt<DiscussLableGetOutputDto>());
}
}
});
return output;
}
@@ -250,36 +304,10 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
return await MapToGetOutputDtoAsync(entity);
}
/// <summary>
/// 校验主题查询权限
/// </summary>
/// <param name="discussId"></param>
/// <returns></returns>
/// <exception cref="UserFriendlyException"></exception>
public async Task VerifyDiscussPermissionAsync(Guid discussId)
public override Task<DiscussGetOutputDto> UpdateAsync(Guid id, DiscussUpdateInputVo input)
{
var discuss = await _forumManager._discussRepository.GetFirstAsync(x => x.Id == discussId);
if (discuss is null)
{
throw new UserFriendlyException(DiscussConst.No_Exist);
}
if (discuss.PermissionType == DiscussPermissionTypeEnum.Oneself)
{
if (discuss.CreatorId != CurrentUser.Id)
{
throw new UserFriendlyException(DiscussConst.Privacy);
}
}
if (discuss.PermissionType == DiscussPermissionTypeEnum.User)
{
if (discuss.CreatorId != CurrentUser.Id &&
!discuss.PermissionUserIds.Contains(CurrentUser.Id ?? Guid.Empty))
{
throw new UserFriendlyException(DiscussConst.Privacy);
}
}
return base.UpdateAsync(id, input);
}
}
}

View File

@@ -1,54 +0,0 @@
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>
/// Label服务实现
/// </summary>
public class MyTypeService : YiCrudAppService<MyTypeEntity, MyTypeOutputDto, MyTypeGetListOutputDto, Guid, MyTypeGetListInputVo, MyTypeCreateInputVo, MyTypeUpdateInputVo>,
IMyTypeService
{
private ISqlSugarRepository<MyTypeEntity, Guid> _repository;
public MyTypeService(ISqlSugarRepository<MyTypeEntity, Guid> repository, IDataFilter dataFilter) : base(repository)
{
_repository = repository;
_dataFilter = dataFilter;
}
private IDataFilter _dataFilter { get; set; }
/// <summary>
/// 获取当前用户的主题类型
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public Task<PagedResultDto<MyTypeGetListOutputDto>> GetListCurrentAsync(MyTypeGetListInputVo input)
{
//过滤器需要更换
//_dataFilter.Enable<MyTypeEntity>(x => x.UserId == CurrentUser.Id);
//_dataFilter.AddFilter<MyTypeEntity>(x => x.UserId == CurrentUser.Id);
return base.GetListAsync(input);
}
/// <summary>
/// 创建
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public override async Task<MyTypeOutputDto> CreateAsync(MyTypeCreateInputVo input)
{
var entity = await MapToEntityAsync(input);
entity.UserId = CurrentUser.Id ?? Guid.Empty;
entity.IsDeleted = false;
var outputEntity = await _repository.InsertReturnEntityAsync(entity);
return await MapToGetOutputDtoAsync(outputEntity);
}
}
}

View File

@@ -0,0 +1,100 @@
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.EventBus.Local;
using Volo.Abp.Users;
using Yi.Framework.Bbs.Application.Contracts.Dtos.Shop;
using Yi.Framework.Bbs.Domain.Entities;
using Yi.Framework.Bbs.Domain.Entities.Shop;
using Yi.Framework.Bbs.Domain.Managers;
using Yi.Framework.Bbs.Domain.Managers.Shop;
using Yi.Framework.Bbs.Domain.Shared.Enums;
using Yi.Framework.Bbs.Domain.Shared.Etos;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Bbs.Application.Services.Shop;
/// <summary>
/// bbs商城服务
/// </summary>
public class BbsShopService : ApplicationService
{
private readonly ISqlSugarRepository<BbsGoodsAggregateRoot> _repository;
private readonly ISqlSugarRepository<BbsGoodsApplyAggregateRoot> _applyRepository;
private readonly BbsShopManager _bbsShopManager;
private readonly ISqlSugarRepository<BbsUserExtraInfoEntity> _bbsUserRepository;
private ILocalEventBus LocalEventBus => LazyServiceProvider.LazyGetRequiredService<ILocalEventBus>();
public BbsShopService(ISqlSugarRepository<BbsGoodsAggregateRoot> repository,
ISqlSugarRepository<BbsGoodsApplyAggregateRoot> applyRepository, BbsShopManager bbsShopManager,
ISqlSugarRepository<BbsUserExtraInfoEntity> bbsUserRepository)
{
_repository = repository;
_applyRepository = applyRepository;
_bbsShopManager = bbsShopManager;
_bbsUserRepository = bbsUserRepository;
}
//商城列表
[Authorize]
public async Task<PagedResultDto<ShopGetListOutput>> GetListAsync(PagedAndSortedResultRequestDto input)
{
var output = new List<ShopGetListOutput>();
var userId = CurrentUser.GetId();
RefAsync<int> total = 0;
var entities = await _repository._DbQueryable
.Where(x => x.EndTime > DateTime.Now)
.OrderBy(x => x.OrderNum)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
var applyEntities = await _applyRepository.GetListAsync(x => x.UserId == userId);
foreach (var entity in entities)
{
var dto = entity.Adapt<ShopGetListOutput>();
if (entity.GoodsType == GoodsTypeEnum.Apply)
{
//大于限购数量
if (applyEntities.Count(x => x.GoodsId == entity.Id) >= entity.LimitNumber)
{
dto.IsLimit = true;
}
}
output.Add(dto);
}
return new PagedResultDto<ShopGetListOutput>(total, output);
}
/// <summary>
/// 购买商品
/// </summary>
[Authorize]
public async Task PostBuyAsync([FromBody] BuyShopInputDto input)
{
var userId = CurrentUser.GetId();
await _bbsShopManager.BuyAsync(userId, input.GoodsId, input.ContactInformation);
}
/// <summary>
/// 获取该用户汇总信息(钱钱、积分、价值)
/// </summary>
[Authorize]
public async Task<BbsShopAccountDto> GetAccountAsync()
{
var userId = CurrentUser.GetId();
var output = new BbsShopAccountDto();
var money = await _bbsUserRepository._DbQueryable.Where(x => x.UserId == userId).Select(x => x.Money)
.FirstAsync();
var eto = new SetAccountInfoEto(userId);
await LocalEventBus.PublishAsync(eto, false);
//钱钱累加
output.Money =money+ eto.Money;
output.Points = eto.Points;
output.Value = eto.Value;
return output;
}
}

View File

@@ -0,0 +1,9 @@
namespace Yi.Framework.Bbs.Domain.Shared.Caches;
public class DiscussLableCacheItem
{
public Guid Id { get; set; }
public string Name { get; set; }
public string? Color { get; set; }
public string? BackgroundColor { get; set; }
}

View File

@@ -0,0 +1,6 @@
namespace Yi.Framework.Bbs.Domain.Shared.Consts;
public class DiscussLableConst
{
public const string DiscussLableCacheKey="DiscussLable:All";
}

View File

@@ -14,14 +14,9 @@ namespace Yi.Framework.Bbs.Domain.Shared.Enums
Public = 0,
/// <summary>
/// 仅自己可见
/// 角色要求可见
/// </summary>
Oneself,
/// <summary>
/// 部分用户可见
/// </summary>
User
Role=1
}
}

View File

@@ -0,0 +1,10 @@
namespace Yi.Framework.Bbs.Domain.Shared.Etos;
/// <summary>
/// 临时用户绑定到正式用户
/// </summary>
public class BindAccountEto
{
public Guid NewUserId { get; set; }
public Guid OldUserId { get; set; }
}

View File

@@ -0,0 +1,27 @@
namespace Yi.Framework.Bbs.Domain.Shared.Etos;
public class SetAccountInfoEto
{
public SetAccountInfoEto(Guid userId)
{
UserId = userId;
}
public Guid UserId { get; set; }
/// <summary>
/// 钱钱
/// </summary>
public decimal Money { get; set; }
/// <summary>
/// 积分
/// </summary>
public int Points { get; set; }
/// <summary>
/// 价值
/// </summary>
public decimal Value { get; set; }
}

View File

@@ -10,7 +10,7 @@ using Yi.Framework.Bbs.Domain.Shared.Enums;
namespace Yi.Framework.Bbs.Domain.Entities
{
/// <summary>
/// 评论
/// bbs用户
/// </summary>
[SugarTable("BbsUserExtraInfo")]
[SugarIndex($"index_{nameof(UserId)}", nameof(UserId), OrderByType.Asc)]

View File

@@ -8,9 +8,12 @@ namespace Yi.Framework.Bbs.Domain.Entities.Forum
{
[SugarTable("Discuss")]
[SugarIndex($"index_{nameof(Title)}", nameof(Title), OrderByType.Asc)]
[SugarIndex($"index_{nameof(PlateId)}", nameof(PlateId), OrderByType.Asc)]
[SugarIndex($"index_{nameof(CreatorId)}", nameof(CreatorId), OrderByType.Asc)]
[SugarIndex($"index_{nameof(CreationTime)}", nameof(CreationTime), OrderByType.Desc)]
[SugarIndex($"index_{nameof(IsDeleted)}_{nameof(PlateId)}_{nameof(CreatorId)}",
nameof(IsDeleted), OrderByType.Asc,
nameof(PlateId), OrderByType.Asc,
nameof(CreatorId), OrderByType.Asc
)]
public class DiscussAggregateRoot : AggregateRoot<Guid>, ISoftDelete, IAuditedObject
{
public DiscussAggregateRoot()
@@ -21,10 +24,18 @@ namespace Yi.Framework.Bbs.Domain.Entities.Forum
PlateId = plateId;
}
public void AddSeeNumber()
{
this.SeeNum += 1;
//设置最小值,不更新
this.LastModificationTime = DateTime.MinValue;
//设置空值,不更新
this.LastModifierId = Guid.Empty;
}
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true)]
public override Guid Id { get; protected 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; }
@@ -59,11 +70,14 @@ namespace Yi.Framework.Bbs.Domain.Entities.Forum
/// <summary>
/// 当PermissionType为部分用户时候,以下列表中的用户+创建者 代表拥有权限
/// 当PermissionType为角色时候,以下列表中的角色+创建者 代表拥有权限
/// </summary>
[SugarColumn(IsJson = true)]//使用json处理
public List<Guid>? PermissionUserIds { get; set; }
[SugarColumn(IsJson = true)] //使用json处理
public List<string>? PermissionRoleCodes { get; set; } = new List<string>();
[SugarColumn(IsJson = true)]//使用json处理
public List<Guid>? DiscussLableIds{ get; set; }
/// <summary>
/// 是否禁止评论创建功能
/// </summary>

View File

@@ -4,17 +4,13 @@ using Volo.Abp.Domain.Entities;
namespace Yi.Framework.Bbs.Domain.Entities.Forum
{
[SugarTable("MyType")]
public class MyTypeEntity : Entity<Guid>, ISoftDelete
[SugarTable("DiscussLable")]
public class DiscussLableAggregateRoot : AggregateRoot<Guid>, ISoftDelete
{
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true)]
public override Guid Id { get; protected set; }
public bool IsDeleted { get; set; }
public string Name { get; set; }
public string? Color { get; set; }
public string? BackgroundColor { get; set; }
public Guid UserId { get; set; }
}
}

View File

@@ -1,16 +0,0 @@
using SqlSugar;
using Volo.Abp.Domain.Entities;
namespace Yi.Framework.Bbs.Domain.Entities.Forum
{
[SugarTable("DiscussMyType")]
public class DiscussMyTypeEntity : Entity<Guid>
{
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true)]
public override Guid Id { get; protected set; }
public Guid DiscussId { get; set; }
public Guid MyTypeId { get; set; }
}
}

View File

@@ -2,6 +2,7 @@
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Yi.Framework.Bbs.Domain.Shared.Enums;
using Yi.Framework.Core.Data;
namespace Yi.Framework.Bbs.Domain.Entities.Shop;
@@ -9,7 +10,7 @@ namespace Yi.Framework.Bbs.Domain.Entities.Shop;
/// 商品定义表
/// </summary>
[SugarTable("BbsGoods")]
public class BbsGoodsAggregateRoot: AggregateRoot<Guid>, IHasCreationTime
public class BbsGoodsAggregateRoot: AggregateRoot<Guid>, IHasCreationTime,IOrderNum
{
/// <summary>
/// 上架时间
@@ -70,4 +71,6 @@ public class BbsGoodsAggregateRoot: AggregateRoot<Guid>, IHasCreationTime
/// 所需积分
/// </summary>
public decimal NeedPoints { get; set; }
public int OrderNum { get; set; }
}

View File

@@ -0,0 +1,47 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
using Yi.Framework.Bbs.Domain.Entities;
using Yi.Framework.Bbs.Domain.Shared.Etos;
using Yi.Framework.Rbac.Domain.Entities;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Bbs.Domain.EventHandlers;
/// <summary>
/// 临时账号绑定到正式账号,钱钱 (累加),禁用临时账号(修改)
/// </summary>
public class BindAccountForBbsEventHandler : ILocalEventHandler<BindAccountEto>, ITransientDependency
{
private readonly ISqlSugarRepository<BbsUserExtraInfoEntity> _bbsUserRepository;
private readonly ISqlSugarRepository<UserAggregateRoot> _userRepository;
public BindAccountForBbsEventHandler(ISqlSugarRepository<BbsUserExtraInfoEntity> bbsUserRepository,
ISqlSugarRepository<UserAggregateRoot> userRepository)
{
_bbsUserRepository = bbsUserRepository;
_userRepository = userRepository;
}
public async Task HandleEventAsync(BindAccountEto eventData)
{
//禁用临时用户
var oldUser = await _userRepository.GetFirstAsync(x => x.Id == eventData.OldUserId);
if (oldUser is null || oldUser.State == false)
{
throw new UserFriendlyException("无法将无效用户进行绑定");
}
oldUser.State = false;
await _userRepository.UpdateAsync(oldUser);
//账户钱转移
var bbsOldUser = await _bbsUserRepository.GetFirstAsync(x => x.UserId == eventData.OldUserId);
var bbsNewUser = await _bbsUserRepository.GetFirstAsync(x => x.UserId == eventData.NewUserId);
if (bbsNewUser is not null)
{
bbsNewUser.Money += bbsOldUser?.Money ?? 0;
await _bbsUserRepository.UpdateAsync(bbsNewUser);
}
}
}

View File

@@ -8,28 +8,24 @@ using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus;
using Yi.Framework.Bbs.Domain.Entities.Forum;
using Yi.Framework.Bbs.Domain.Shared.Etos;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Bbs.Domain.EventHandlers
{
public class SeeDiscussEventHandler : ILocalEventHandler<SeeDiscussEventArgs>, ITransientDependency
{
private IRepository<DiscussAggregateRoot, Guid> _repository;
public SeeDiscussEventHandler(IRepository<DiscussAggregateRoot, Guid> repository)
private ISqlSugarRepository<DiscussAggregateRoot, Guid> _repository;
public SeeDiscussEventHandler(ISqlSugarRepository<DiscussAggregateRoot, Guid> repository)
{
_repository = repository;
}
public async Task HandleEventAsync(SeeDiscussEventArgs eventData)
{
var entity = await _repository.GetAsync(eventData.DiscussId);
if (entity is not null)
{
entity.SeeNum += 1;
await _repository.UpdateAsync(entity);
}
await _repository._Db.Updateable<DiscussAggregateRoot>()
.SetColumns(x => new DiscussAggregateRoot { SeeNum = x.SeeNum + 1 })
.Where(x => x.Id == eventData.DiscussId).ExecuteCommandAsync();
}
}
}
}

View File

@@ -1,11 +0,0 @@
using Volo.Abp.Domain.Services;
namespace Yi.Framework.Bbs.Domain.Managers;
/// <summary>
/// bbs商品领域
/// </summary>
public class BbsShopManager: DomainService
{
}

View File

@@ -14,7 +14,8 @@ namespace Yi.Framework.Bbs.Domain.Managers
{
public ISqlSugarRepository<UserAggregateRoot> _userRepository;
public ISqlSugarRepository<BbsUserExtraInfoEntity> _bbsUserInfoRepository;
public Dictionary<int, LevelCacheItem> _levelCacheDic;
// public Dictionary<int, LevelCacheItem> _levelCacheDic;
private LevelManager _levelManager;
public BbsUserManager(ISqlSugarRepository<UserAggregateRoot> userRepository,
ISqlSugarRepository<BbsUserExtraInfoEntity> bbsUserInfoRepository,
@@ -23,7 +24,12 @@ namespace Yi.Framework.Bbs.Domain.Managers
{
_userRepository = userRepository;
_bbsUserInfoRepository = bbsUserInfoRepository;
_levelCacheDic = levelManager.GetCacheMapAsync().Result;
_levelManager = levelManager;
}
public async Task<Dictionary<int, LevelCacheItem>> GetLevelCacheMapAsync()
{
return await _levelManager.GetCacheMapAsync();
}
public async Task<BbsUserInfoDto?> GetBbsUserInfoAsync(Guid userId)
@@ -44,7 +50,8 @@ namespace Yi.Framework.Bbs.Domain.Managers
}, true)
.FirstAsync(user => user.Id == userId);
userInfo.LevelName = _levelCacheDic[userInfo.Level].Name;
var levelCacheDic= await GetLevelCacheMapAsync();
userInfo.LevelName = levelCacheDic[userInfo.Level].Name;
return userInfo;
}
@@ -66,7 +73,8 @@ namespace Yi.Framework.Bbs.Domain.Managers
DiscussNumber = info.DiscussNumber
}, true)
.ToListAsync();
userInfos?.ForEach(userInfo => userInfo.LevelName = _levelCacheDic[userInfo.Level].Name);
var levelCacheDic= await GetLevelCacheMapAsync();
userInfos?.ForEach(userInfo => userInfo.LevelName =levelCacheDic[userInfo.Level].Name);
return userInfos ?? new List<BbsUserInfoDto>();
}

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