267 Commits

Author SHA1 Message Date
RapGod fee60ef208 5/16完成:
1.眼界甄选帖子评论点踩表创建;
2.眼界甄选帖子评论点踩实体类及实现类等创建;
3.眼界甄选帖子评论点踩及取消点踩具体实现;
4.我发布的帖子列表接口实现;
5.我喜欢的帖子列表接口实现;
6.医疗机构特色技术表创建;实体类及实现类创建;
7.医疗机构列表接口实现;
8.医疗机构详情页具体实现;
2023-05-16 10:22:44 +08:00
Loki c11bc40b2b 👍 修改线上redis密码 2023-02-21 16:16:59 +08:00
Loki 56e0fea50a 👍 修复 2023-02-21 16:03:51 +08:00
Loki 84d4d8e118 👍 修复评测搜索 2023-02-21 16:03:51 +08:00
Loki bd85cca154 增加数据库 2023-02-21 13:08:59 +08:00
Loki eb76349932 👍 修改为本地数据库。 2023-01-11 14:10:41 +08:00
Loki c8ca54f026 👍 删除部分异常 新增BXG统一异常SHOPEX 2022-12-21 15:44:55 +08:00
Loki 691efec9f7 👍 增加幂等性的验证,删除过期无用的注解。 2022-12-20 11:27:44 +08:00
Loki 482b1c4d12 👍 删除清理首页缓存的钩子。 2022-12-19 15:46:02 +08:00
Loki 9152b81092 👍 修复自动确认收货的逻辑,后台手动传递确认时限 2022-12-19 11:17:40 +08:00
Loki caaa969acb 👍 修复本地规格报错 2022-12-09 11:15:17 +08:00
Loki 37fd40f6bc 👍 修复砍价价格不准确的问题。 2022-12-08 18:13:54 +08:00
zhanyunjiu eed9b2a0dd 查询 关键字 2022-12-08 16:17:13 +08:00
zhanyunjiu 0e55d4f4f8 selectone 修复 2022-12-07 17:40:28 +08:00
Loki 6b18623b7f 👍 全部订单列表不过滤退款状态。 2022-12-07 10:52:49 +08:00
Loki 16fe20ea48 👍 商品被删除增加标签 2022-12-07 10:40:00 +08:00
Loki baedf074be 👍 增加NPE防御 2022-12-07 10:38:29 +08:00
Loki d84528ee14 👎 商品订单全部导出不用分页 2022-12-07 10:21:44 +08:00
Loki 51be61b741 👎 修改自动收货的字段错误 2022-12-06 19:03:16 +08:00
Loki 8a36c99ec7 👎 增加后台商品列表的数据统计字段 2022-12-06 18:57:37 +08:00
Loki 6264571059 👎 增加所有租户商城的自动取消订单和自动确认收货 2022-12-06 17:37:36 +08:00
Loki 77745360f6 👎 增加取消订单和自动收货的计划任务 2022-12-06 12:02:48 +08:00
Loki 567bda85a6 👎 删除dubbo 修复本地域名 2022-12-05 13:30:14 +08:00
sj 3ba933257e 增加售后退款流程 2022-12-02 19:16:33 +08:00
sj 1e37d646d2 拼团参与中只返回没结束的 2022-12-01 16:49:10 +08:00
sj b79e7f15a5 修复依赖循环问题 2022-12-01 15:26:40 +08:00
sj 769de337e7 修复订单多选查询 2022-12-01 14:53:28 +08:00
sj 8525c5399e 修复仅用积分时订单退款失败 2022-11-30 18:32:23 +08:00
sj bb861d1138 增加优惠券发放方法,修复秒杀排序重复 2022-11-30 16:30:10 +08:00
1304317391@qq.com 2350df80a3 优化订单查询速度 2022-11-29 19:18:31 +08:00
1304317391@qq.com 0e217db2ba 允许未成团订单进行发货,修复退款通知 2022-11-29 17:06:53 +08:00
1304317391@qq.com d9a489d99d 订单支持更多状态 2022-11-29 15:44:17 +08:00
1304317391@qq.com c509e75d4c 拼团秒杀砍价支持单独配置链接图 2022-11-28 19:12:45 +08:00
1304317391@qq.com a5ac267277 商品增加链接图字段 2022-11-28 15:33:12 +08:00
1304317391@qq.com 01c93c3adf 首页独立出秒杀接口 2022-11-25 23:15:43 +08:00
1304317391@qq.com 10ccf471c5 支持后台取消商品,订单号改为时间关联 2022-11-25 18:33:57 +08:00
1304317391@qq.com 5afec37b36 支持库存为0,修复我的页面展示 2022-11-24 18:22:33 +08:00
1304317391@qq.com 7f0a0a43a8 修复评测异常及单独处理拼团订单未支付时间 2022-11-24 12:44:53 +08:00
1304317391@qq.com fd57b0f92f 订单增加审核,秒杀商品显示修改 2022-11-23 20:34:11 +08:00
1304317391@qq.com 22c370c7fb 修复页面错误,订单报表增加功能 2022-11-21 19:34:41 +08:00
sj 1f6a85e17b Merge branch 'sj' of https://git.lotus-wallet.com/sj/shop into sj 2022-11-20 04:19:01 +08:00
sj 410860689d 修改微信用户名 2022-11-20 04:18:34 +08:00
Loki 8948581e47 👎 到处订单列表 2022-11-20 00:52:10 +08:00
sj fc2144b708 ssj 2022-11-19 21:12:42 +08:00
Loki 4b336dfd8e 👎 处理product的list json 2022-11-19 21:12:30 +08:00
sj 167e3eed85 修改EditableTabs类 2022-11-19 18:01:31 +08:00
sj 0a92ac5e67 修改首页接口,增加清除缓存 2022-11-19 14:23:30 +08:00
sj a460ec3ecb 更改线上支付回调 2022-11-18 17:41:56 +08:00
sj 1d249222fa 修复会员等级问题 2022-11-18 13:35:59 +08:00
sj 0b33a011a2 会员等级支持修改 2022-11-18 09:34:30 +08:00
sj e9024508ef 导出修复 2022-11-17 14:12:49 +08:00
Loki 66d4345339 👍 fix 2022-11-16 16:45:28 +08:00
sj 32a84d6155 代码修改 2022-11-16 16:04:13 +08:00
sj 0e7e4dbb80 修改xml文件 2022-11-15 16:04:53 +08:00
sj cd2fca31e2 删除多余代码 2022-11-15 15:16:54 +08:00
sj c424bc0ca8 优化微信用户优化名 2022-11-15 13:50:03 +08:00
sj 010132f237 优化商品分类及子集查询 2022-11-14 16:26:07 +08:00
sj 91188fe1a0 优化优惠券显示并运行最小优惠券设置为无门槛 2022-11-14 13:58:03 +08:00
sj a5cdf202f1 下单时候自动选择优惠金额最大的优惠券 2022-11-14 11:10:22 +08:00
sj a30fb508de 修复优惠券被删除是用户券包报错 2022-11-11 16:27:49 +08:00
sj aa792a10b1 增加品牌搜索 2022-11-11 13:43:43 +08:00
sj bcdd99eb4a 增加商品id搜索,修复规格太长时,数据库报错 2022-11-10 17:19:52 +08:00
sj 50c1f73901 优惠券增加商品图片展示 2022-11-10 15:43:18 +08:00
sj 0d93550e43 增加拼团及砍价活动通知 2022-11-09 18:21:41 +08:00
sj 3b05b235bc 优化榜单排序及定时任务 2022-11-08 13:49:16 +08:00
sj 2fa9950933 修复榜单绑定商品删除问题 2022-11-07 10:56:44 +08:00
sj 76bd360e92 商品修改 2022-11-05 19:17:36 +08:00
sj f459579768 修改榜单页面 2022-11-05 19:16:21 +08:00
sj cb388c8e85 增加品牌馆滚动条 2022-11-03 16:13:47 +08:00
sj b26c158c84 修复专家排序及用户消息修改 2022-11-02 18:18:11 +08:00
sj 26f6a38c6b 领券增加消息推送,修复首页图 2022-11-02 17:06:12 +08:00
sj e80ff782e7 热榜增加时间字段配置 2022-11-01 14:22:57 +08:00
sj 44110bd059 商品增加无理由退款等配置 2022-10-31 18:33:07 +08:00
sj 9bc5869abd 修改订单支付及退款消息通知 2022-10-29 18:39:27 +08:00
sj 2b25c63f4e 品牌排序修改及商品删除下架时判断 2022-10-29 16:23:56 +08:00
sj 5f259398a2 修复订单生成 2022-10-29 10:42:53 +08:00
sj ddc1cddcb9 商品增加商品参数字段 2022-10-28 19:29:03 +08:00
sj a0a6040437 评测去除多余字段 2022-10-28 17:39:58 +08:00
sj f46f56e5b0 微信登录修复及品牌评测优化 2022-10-27 18:52:31 +08:00
sj b6a9c87e2a 会员等级增加成长值 2022-10-25 18:40:32 +08:00
sj ddb28dc5c8 修复优惠券部分 2022-10-25 16:01:42 +08:00
1304317391@qq.com 92e2c99b89 修复依赖错误 2022-10-22 13:33:31 +08:00
1304317391@qq.com a6cf439160 修复依赖错误 2022-10-22 13:12:01 +08:00
sj b0a0785501 优化流程 2022-10-21 19:03:11 +08:00
sj a22fa10cb8 增加评测收藏及订单支付成功群机器人 2022-10-19 18:14:15 +08:00
sj 200346ee71 修复物流状态查询 2022-10-18 14:16:16 +08:00
sj efa1a331bb 修复优惠券发布权限 2022-10-15 13:46:28 +08:00
sj 49d46c391a 修复专家和评测模块 2022-10-14 16:56:35 +08:00
Loki 69c9c00c16 👎 删除代码 2022-10-13 17:41:23 +08:00
sj 226518b3a4 跳过maven测试 2022-10-13 16:54:34 +08:00
sj e265d9620b 合并眼界甄选 2022-10-13 15:14:21 +08:00
sj 4180aa5567 修复因购物车改变引起的订单流程 2022-10-13 14:59:03 +08:00
sj e7bc4f312a 增加商品热榜设置,并购物车按品牌区分 2022-10-13 09:41:37 +08:00
sj 6d15e1fa32 修复小程序登录问题 2022-10-08 13:47:34 +08:00
sj aa8a171ccc 修复营销活动及会员等级 2022-09-30 17:31:08 +08:00
sj 6e7adc20cb 修改评测获取方法 2022-09-29 17:02:55 +08:00
sj 5bb10fe988 增加小程序请求商品词条接口 2022-09-29 11:43:54 +08:00
sj 37acc6ebae 修复小程序请求商品接口 2022-09-27 16:50:45 +08:00
sj ac911e41e1 增加小程序获取评测列表接口 2022-09-26 11:08:20 +08:00
sj 4f69fb5d3a 增加评测模块 2022-09-26 10:27:44 +08:00
sj 61eb0c7177 增加小程序端的专家和品牌查询接口 2022-09-20 16:29:01 +08:00
sj 1f07dde9d2 优惠券时间限制修改 2022-09-20 14:45:25 +08:00
sj 822d5e8207 完善专家模块 2022-09-19 16:58:26 +08:00
sj 0125a6598c 商品品牌修改 2022-09-17 18:44:43 +08:00
sj 173a6145af 修复商品品牌 2022-09-17 10:08:35 +08:00
sj 8d66f53be5 更改配置 2022-09-09 18:30:25 +08:00
Loki 0c83d79684 增加默认主页 修改线上地址 2022-09-07 13:51:23 +08:00
Loki 91741b3224 修复缓存 2022-09-06 20:34:27 +08:00
Loki 4a7e7c2595 菜单增加隐藏 2022-09-06 15:35:59 +08:00
Loki 49c3e34b23 新增租户初始化切面事件 2022-08-29 19:13:24 +08:00
Loki 4686861ad2 准备新租户创建切面 用于同步一些比如七牛,默认轮播,店铺设置等。 2022-08-24 11:28:22 +08:00
zhanyunjiu 10a6b8fc57 报错修复 2022-08-19 12:08:31 +08:00
zhanyunjiu 92a7db0031 dubbo远程调用erp异常处理 2022-08-16 16:38:10 +08:00
zhanyunjiu 2072a3cbc9 处理dubbo没有provider 2022-08-15 15:58:08 +08:00
zhanyunjiu c903ff31e3 dubbo注册到nacos 2022-08-15 14:51:32 +08:00
zhanyunjiu 8f96db1483 消息退送信息 2022-08-08 10:53:33 +08:00
zhanyunjiu 13323608a9 项目、任务 没有成员选择 暂时不发送消息 2022-08-04 11:57:07 +08:00
zhanyunjiu 6a88008a51 dubbo 2022-08-04 11:20:08 +08:00
zhanyunjiu 0b96c77838 项目定时任务注释 2022-08-04 10:48:19 +08:00
zhanyunjiu a2e22e1478 提交代码 2022-08-02 18:49:41 +08:00
zhanyunjiu 18928deb2e 修改流程 2022-07-27 19:01:46 +08:00
zhanyunjiu e44e0e662e 流程优化 2022-07-26 10:46:43 +08:00
zhanyunjiu eaf7faa12b 新增、修改取消任务状态选择,定时检查任务状态 2022-07-22 17:53:25 +08:00
zhanyunjiu 6b2c8ec70c 流程优化 2022-07-22 09:48:46 +08:00
zhanyunjiu 126251e113 优化 2022-07-20 14:31:08 +08:00
zhanyunjiu 04814bbd89 实体类字段优化 2022-07-15 17:52:30 +08:00
zhanyunjiu dc52c30581 业务流程调整 2022-07-14 18:38:57 +08:00
zhanyunjiu 32250adc85 活动新增 工时、采收数量、参与人、执行时间 4个字段 2022-07-14 13:51:32 +08:00
zhanyunjiu afcecc3c73 项目新增字段 , 2022-07-13 09:43:44 +08:00
zhanyunjiu 333c63bb62 采收 作物 同步到ERP 2022-07-12 16:16:17 +08:00
Loki bc6c88d89d 增加出入拦截 2022-07-12 10:25:44 +08:00
Loki f1b8d3b47d 增加dubbo配置和调用案例 2022-07-11 20:11:33 +08:00
zhanyunjiu 4453f13b37 活动 新增作物记录 2022-07-11 18:41:11 +08:00
zhanyunjiu 53316b0b71 权限 2022-07-11 13:50:29 +08:00
zhanyunjiu 1697bd8fef 资源保管人 2022-07-02 10:59:12 +08:00
zhanyunjiu f034ce1b1a 任务创建人和负责人才可以新增修改活动 2022-07-01 18:48:44 +08:00
zhanyunjiu 21a3f1e166 优化提示 2022-07-01 09:52:31 +08:00
zhanyunjiu a3555407a1 问题修复 2022-06-30 16:21:25 +08:00
zhanyunjiu 711bf01e62 删除无效引用 2022-06-30 14:04:42 +08:00
WIN-IDGBLFHC1K6\Administrator 27ba31bf4f 任务 和 项目 actions权限 2022-06-30 13:49:26 +08:00
WIN-IDGBLFHC1K6\Administrator a71bc7d6b2 统计图优化 2022-06-30 10:26:17 +08:00
WIN-IDGBLFHC1K6\Administrator d4c7fa2fc1 问题修复 2022-06-30 09:35:34 +08:00
WIN-IDGBLFHC1K6\Administrator ab09d4a524 资源 占用 库存扣减 2022-06-29 18:32:26 +08:00
WIN-IDGBLFHC1K6\Administrator 8ec6f213d4 一个田区不可以同事被两个进行中的项目占用, 项目更改完成前提下面任务都完成 2022-06-29 16:58:56 +08:00
WIN-IDGBLFHC1K6\Administrator bebca3004d 区域新增 占用字段 2022-06-29 15:27:49 +08:00
WIN-IDGBLFHC1K6\Administrator 103837e0f3 任务通知消息 新增内容 2022-06-29 15:08:06 +08:00
WIN-IDGBLFHC1K6\Administrator 4d011a2a51 问题修复 2022-06-29 13:45:04 +08:00
WIN-IDGBLFHC1K6\Administrator 4011874184 农场 任务列表 根据projectId显示任务所在日期 2022-06-28 15:29:46 +08:00
Loki cd55670218 部分权限 2022-06-24 19:27:41 +08:00
小久哥 59c86e3aca 日期有任务的显示 2022-06-24 19:27:23 +08:00
小久哥 f1232e7981 Merge branch 'zyh' into zyj 2022-06-24 14:00:29 +08:00
Loki 4d7af16b05 给项目增加权限字典,用于小程序展示按钮。 2022-06-24 13:58:48 +08:00
小久哥 510fb3814f 微信登录 2022-06-24 12:30:45 +08:00
小久哥 8d89747fe4 消息 成员同步 2022-06-24 11:36:33 +08:00
小久哥 6063523f30 后台项目搜索 2022-06-23 16:58:16 +08:00
小久哥 e9fe435aef 后台任务列表搜索 2022-06-23 15:56:23 +08:00
小久哥 1a88fca5b6 企业微信小程序消息卡片 2022-06-23 09:35:13 +08:00
小久哥 658663c61a 消息 及bug修复 2022-06-22 18:06:04 +08:00
小久哥 e8a5668da4 消息发送 2022-06-21 19:06:06 +08:00
小久哥 7380a37431 修复 错误 2022-06-21 13:36:46 +08:00
小久哥 cc80a97dba Merge branch 'zyh' into zyj
# Conflicts:
#	zsw-farm/zsw-farm-impl/src/main/java/cn/iocoder/yudao/module/farm/annotation/FarmMsgAspect.java
2022-06-20 16:28:13 +08:00
小久哥 41dd169a97 修改区域 2022-06-20 16:17:48 +08:00
Loki 5dd48e50a0 修改farmmsg注解 2022-06-20 15:08:31 +08:00
Loki f77d753264 fix log 2022-06-20 13:42:46 +08:00
小久哥 fe84dea084 小程序权限访问 2022-06-20 09:33:48 +08:00
Loki 8fc86bebff dict 2022-06-17 21:00:22 +08:00
小久哥 73a8bf4a35 优化农场日志记录 2022-06-17 18:29:18 +08:00
小久哥 d662c8dec1 修复bug 2022-06-17 16:29:58 +08:00
小久哥 4e8c2178ba 项目、任务查看未删除的 2022-06-17 09:44:22 +08:00
小久哥 9e3968cbda 接口新增:查看任务详情 显示活动时长 2022-06-16 18:04:02 +08:00
小久哥 44fee2cf6e 接口:根据taskId查询累计的任务时长 2022-06-16 17:12:58 +08:00
小久哥 13610a7bb6 项目状态枚举 2022-06-16 16:23:07 +08:00
小久哥 f29fa0b508 task新加 unit单位字段,删除不用的projectId 2022-06-16 16:01:18 +08:00
小久哥 79b5eb0836 我参与项目 排序 2022-06-16 14:58:50 +08:00
小久哥 b36951a4fb 时区 2022-06-16 14:58:23 +08:00
小久哥 97901204b7 日志信息和项目 倒序 2022-06-16 11:57:20 +08:00
小久哥 c43bf99629 农场 project/resource/resourceType/task/taskCate/workHour 假删除 2022-06-16 11:17:35 +08:00
小久哥 0ea7bf55b3 农场 area、crop、crop_record、discuss、event 假删除 2022-06-16 10:50:52 +08:00
小久哥 c0e6c171a6 删除无效 任务方法 2022-06-15 18:58:16 +08:00
小久哥 b75d12cb83 优化代码 2022-06-15 18:22:44 +08:00
小久哥 9bc848dd96 企业用户 注册 系统用户 并给权限 2022-06-14 17:56:18 +08:00
小久哥 d18238dbab 企业微信同步 2022-06-14 16:14:15 +08:00
小久哥 63761c9d3a 我的项目 2022-06-14 13:43:51 +08:00
小久哥 ca489012f5 农场日志信息 2022-06-14 11:19:02 +08:00
小久哥 14439a203b 项目 我参与的 2022-06-13 16:45:23 +08:00
小久哥 92e340ae09 权限 2022-06-13 13:40:22 +08:00
小久哥 3de354c111 cpuser表不存在的用户 用test登录 2022-06-11 18:39:14 +08:00
小久哥 e67f42f033 账号密码错误修复 2022-06-11 18:23:49 +08:00
小久哥 8589fd38e0 删除wxcp.farmSecret 2022-06-11 18:04:17 +08:00
小久哥 99eb3bcb7f 添加agentId 2022-06-11 17:57:15 +08:00
小久哥 09eb23318e 删除微信code登录 2022-06-11 16:54:31 +08:00
小久哥 f0084754ca 查看我的项目, 修改配置 2022-06-11 16:43:35 +08:00
小久哥 cc0c7f374e 修改配置 2022-06-11 15:48:16 +08:00
小久哥 f42644e836 修復錯誤 2022-06-11 09:52:02 +08:00
小久哥 d4dc0a25c7 修改 2022-06-10 18:50:57 +08:00
小久哥 b4fc7db6d0 统计接口, prod-erp 2022-06-10 17:11:27 +08:00
小久哥 69a53a5f2a mysql 2022-06-09 16:51:02 +08:00
小久哥 84c53c545f prod mysql name 2022-06-09 11:08:44 +08:00
小久哥 cbfec821da pro 2022-06-09 11:02:04 +08:00
小久哥 d26d0a4ae5 pro mysql ip 2022-06-09 10:45:42 +08:00
小久哥 6d9be42340 pro 数据库 2022-06-09 10:34:34 +08:00
小久哥 71bc4b45dc cj 2022-06-09 09:43:11 +08:00
小久哥 35abd199bc redis 2022-06-08 21:22:27 +08:00
小久哥 e7cc44c502 mysql 驱动 2022-06-08 21:09:15 +08:00
小久哥 d96de82d5f mysql 2022-06-08 20:59:59 +08:00
小久哥 6745fecc6f mysql 2022-06-08 20:55:06 +08:00
小久哥 b188e3dcb9 mysql pom 2022-06-08 20:51:47 +08:00
小久哥 ccada6c25d mysql驱动 2022-06-08 20:43:38 +08:00
小久哥 83c78c96dd mysql 密码 2022-06-08 20:35:51 +08:00
小久哥 2bf333cc90 mysql 2022-06-08 20:25:04 +08:00
小久哥 f51ae91df0 prod.mysql 2022-06-08 20:19:28 +08:00
小久哥 51a987670c prod,redis 2022-06-08 20:13:25 +08:00
小久哥 dff81402bb 活动结构更改 2022-06-08 19:45:43 +08:00
小久哥 672f0e7514 项目、任务草稿 2022-06-08 15:48:15 +08:00
小久哥 a3ff0accb6 项目筛选 2022-06-07 18:14:06 +08:00
小久哥 c14f984d89 Merge branch 'zyh' into zyj
# Conflicts:
#	yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
2022-06-07 16:16:34 +08:00
小久哥 8503e7a69f 优化 2022-06-07 16:10:59 +08:00
Loki aceb2811ef 删除多数据源配置 2022-06-07 15:57:22 +08:00
小久哥 e3843fdad8 id int改long 2022-06-06 20:34:20 +08:00
小久哥 49c4ba8652 Merge branch 'zyh' into zyj 2022-06-06 19:48:56 +08:00
小久哥 e80a44320a 小程序 首页 项目 完成任务最新一条 2022-06-06 19:48:09 +08:00
小久哥 e3098cf8d9 登录返回工时 2022-06-06 19:47:44 +08:00
Loki 7a6f346404 统计接口 2022-06-06 19:01:59 +08:00
小久哥 612e4a428c 实体字段改变 2022-06-02 17:44:11 +08:00
小久哥 fbfaea3926 test删除resouce 2022-06-02 11:22:08 +08:00
小久哥 7ee83372b6 删除项目 resouce字段 2022-06-02 11:15:10 +08:00
小久哥 db52846a6c 删除项目 resouce字段 2022-06-02 11:14:15 +08:00
小久哥 8105b62737 Merge branch 'zyh' into zyj
# Conflicts:
#	zsw-farm/zsw-farm-impl/src/main/java/cn/iocoder/yudao/module/farm/controller/admin/task/vo/TaskBaseVO.java
#	zsw-farm/zsw-farm-impl/src/main/java/cn/iocoder/yudao/module/farm/dal/dataobject/task/TaskDO.java
2022-06-02 11:11:46 +08:00
小久哥 a2d00e3ab7 项目更改 2022-06-02 11:08:59 +08:00
Loki 09b9e92585 任务活动接口-任务状态枚举 2022-06-02 10:56:02 +08:00
小久哥 243b9066cf 任务DO优化 2022-06-02 10:25:03 +08:00
小久哥 461e4e6457 Merge branch 'zyh' into zyj 2022-06-01 17:28:34 +08:00
小久哥 e4d90c4252 农场任务 新增区域 2022-06-01 17:27:41 +08:00
小久哥 4cead507ae 新增:区域二级 2022-06-01 09:56:53 +08:00
Loki e6b21e3a49 适配进销存接口 增加p6spy日志 2022-05-31 17:40:17 +08:00
小久哥 d434b2084f 优化任务 2022-05-31 16:05:50 +08:00
小久哥 c55aa8f97a 任务类型 2022-05-31 11:28:25 +08:00
Loki a059ab9e00 删除了服务端的admin-ui 2022-05-30 19:33:37 +08:00
小久哥 80e0289e4b Merge branch 'zyh' into zyj
# Conflicts:
#	zsw-farm/zsw-farm-api/src/main/java/cn/iocoder/yudao/module/farm/enums/ErrorCodeConstants.java
2022-05-30 19:32:03 +08:00
小久哥 66489db865 工时 2022-05-30 19:22:00 +08:00
Loki 7a1f3c787f 增加任务分类 2022-05-30 19:05:25 +08:00
小久哥 0174e14455 项目 任务 2022-05-30 16:09:34 +08:00
Loki e01f90088f 修复进销存合并 2022-05-27 17:32:40 +08:00
小久哥 9f394d71a1 农场任务 2022-05-27 17:27:38 +08:00
小久哥 1273f011b4 登录返回信息 2022-05-27 14:19:52 +08:00
小久哥 67eb497114 农场项目 2022-05-27 14:19:37 +08:00
小久哥 54772ded8f Merge branch 'zyh' into zyj 2022-05-26 15:14:41 +08:00
小久哥 94eb1b7141 新增:erp、erp-spi 2022-05-26 14:40:32 +08:00
小久哥 7b742c1701 作物出入关联作物库存 2022-05-25 18:04:25 +08:00
Loki 2d23f468df 修改第三方登录 修改企业微信小程序登录 2022-05-24 18:50:16 +08:00
小久哥 d18ce96c45 新增:作物、作物出入记录 2022-05-24 18:14:26 +08:00
小久哥 04bed3ba3d wxcp配置 2022-05-24 11:40:33 +08:00
小久哥 70623c5712 Merge branch 'zyh' into zyj 2022-05-23 18:24:04 +08:00
Loki 73877d675a 测试微信登录 2022-05-23 14:14:36 +08:00
Loki e9d29ef2df 企业微信成员新增 2022-05-10 17:14:00 +08:00
Loki 186feb98da fix 2022-05-06 21:09:40 +08:00
Loki 4d3b8caf21 宝象购修复 2022-05-06 10:16:22 +08:00
Loki a89a69b145 基本完成 2022-04-30 17:44:59 +08:00
Loki 50fa31c6ad app端调整 2022-04-30 14:44:52 +08:00
Loki b1e3fabea3 页面和权限规则调整 2022-04-29 00:57:05 +08:00
小久哥 ac70ccf30f Merge branch 'zyh' into zyj 2022-04-21 18:51:34 +08:00
小久哥 211afafeaf 农场任务事件搜索 2022-04-21 18:43:18 +08:00
Loki 01ab4d2b4f 增加电商带吗 2022-04-21 18:10:14 +08:00
小久哥 1971f3031c 农场任务搜索实现 2022-04-21 17:44:00 +08:00
小久哥 a746c1be84 农场项目管理 搜索条件实现 2022-04-21 17:04:05 +08:00
小久哥 fba23a61e7 农场作业区域 2022-04-21 10:12:51 +08:00
小久哥 2ab2593f45 add redis config 2022-04-18 14:28:51 +08:00
2096 changed files with 83663 additions and 93258 deletions
+2 -1
View File
@@ -1,10 +1,11 @@
###################################################################### ######################################################################
# Build Tools # Build Tools
/logs
.gradle .gradle
/build/ /build/
!gradle/wrapper/gradle-wrapper.jar !gradle/wrapper/gradle-wrapper.jar
.DS_Store
target/ target/
!.mvn/wrapper/maven-wrapper.jar !.mvn/wrapper/maven-wrapper.jar
-228
View File
@@ -1,228 +0,0 @@
**严肃声明:现在、未来都不会有商业版本,所有功能全部开源!**
**拒绝虚假开源,售卖商业版,程序员不骗程序员!!**
**「我喜欢写代码,乐此不疲」**
**「我喜欢做开源,以此为乐」**
## 🐯 平台简介
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
> 有任何问题,或者想要的功能,可以在 _Issues_ 中提给艿艿。
>
> 😜 给项目点点 Star 吧,这对我们真的很重要!
* 前端采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) ,正在支持 Vue 3 + ElementUI Plus 最新方案。
* 后端采用 Spring Boot、MySQL + MyBatis Plus、Redis + Redisson。
* 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统。
* 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能。
* 支持 SaaS 多租户系统,可自定义每个租户的权限,提供透明化的多租户底层封装。
* 工作流使用 Activiti + Flowable,支持动态表单、在线设计流程、多种任务分配方式。
* 高效率开发,使用代码生成器可以一键生成前后端代码 + 单元测试 + Swagger 接口文档 + Validator 参数校验。
* 集成微信小程序、微信公众号、企业微信、钉钉等三方登陆,集成支付宝、微信等支付与退款。
* 集成阿里云、腾讯云、云片等短信渠道,集成 MinIO、阿里云、腾讯云、七牛云等云存储服务。
| 项目名 | 说明 | 传说门 |
|--------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| `ruoyi-vue-pro` | Spring Boot 多模块 | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro)**     [Github](https://github.com/YunaiV/ruoyi-vue-pro) |
| `ruoyi-vue-cloud` | Spring Cloud 微服务 | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-cloud)**     [Github](https://github.com/YunaiV/onemall) |
| `Spring-Boot-Labs` | Spring Boot & Cloud 入门 | **[Gitee](https://gitee.com/zhijiantianya/SpringBoot-Labs)**     [Github](https://github.com/YunaiV/SpringBoot-Labs) |
## 🐶 在线体验
演示地址:<http://dashboard.yudao.iocoder.cn>
* 账号密码:admin/admin123
文档地址:<http://www.iocoder.cn/categories/Yudao/>
* [《如何搭建环境》](http://www.iocoder.cn/categories/Yudao/?yudao)
> 未来会补充文档和视频,方便胖友冲冲冲!
## 🐼 内置功能
分成多种内置功能:
* 系统功能
* 工作流程
* 支付系统
* 商城系统
* 基础设施
> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
>
> * 额外新增的功能,我们使用 🚀 标记。
> * 重新实现的功能,我们使用 ⭐️ 标记。
🙂 所有功能,都通过 **单元测试** 保证高质量。
### 系统功能
| | 功能 | 描述 |
|-----|-------|---------------------------------|
| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 |
| ⭐️ | 在线用户 | 当前系统中活跃用户状态监控,支持手动踢下线 |
| | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 |
| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 |
| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 |
| | 岗位管理 | 配置系统用户所属担任职务 |
| 🚀 | 租户管理 | 配置系统租户,支持 SaaS 场景下的多租户功能 |
| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 |
| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 |
| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、云片等主流短信平台 |
| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
| ⭐️ | 登录日志 | 系统登录日志记录查询,包含登录异常 |
| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 |
| | 通知公告 | 系统通知公告信息发布维护 |
### 工作流程
| | 功能 | 描述 |
|-----|-------|----------------------------------------|
| 🚀 | 流程模型 | 配置工作流的流程模型,支持文件导入与在线设计流程图,提供 7 种任务分配规则 |
| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 |
| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 |
| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 |
| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转发、委派、退回等操作 |
| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,未来会支持回退操作 |
| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
### 支付系统
| | 功能 | 描述 |
|-----|------|---------------------------|
| 🚀 | 商户信息 | 管理商户信息,支持 Saas 场景下的多商户功能 |
| 🚀 | 应用信息 | 配置商户的应用信息,对接支付宝、微信等多个支付渠道 |
| 🚀 | 支付订单 | 查看用户发起的支付宝、微信等的【支付】订单 |
| 🚀 | 退款订单 | 查看用户发起的支付宝、微信等的【退款】订单 |
ps:核心功能已经实现,正在对接微信小程序中...
### 商城系统
正在开发中,大体计划如下:
* 2022 Q2 => 完成对 <https://github.com/YunaiV/onemall> 的迁移,作为 onemall 的 Spring Boot 单体版本。
* 2022 Q4 => 完成对 <https://github.com/YunaiV/onemall>> 的重构,作为 onemall 的 Spring Cloud 微服务版本。
### 会员中心
正在开发中,大体计划如下:
* 2021 Q1 =》完成对 <https://github.com/YunaiV/onemall> 的迁移
### 基础设施
| | 功能 | 描述 |
|-----|----------|----------------------------------------------|
| 🚀 | 代码生成 | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载 |
| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 |
| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 |
| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 |
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等 |
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
| | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 |
| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
| 🚀 | 消息队列 | 基于 Redis 实现消息队列,Stream 提供集群消费,Pub/Sub 提供广播消费 |
| 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 |
| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 |
| 🚀 | 分布式锁 | 基于 Redis 实现分布式锁,满足并发场景 |
| 🚀 | 幂等组件 | 基于 Redis 实现幂等组件,解决重复请求问题 |
| 🚀 | 服务保障 | 基于 Resilience4j 实现服务的稳定性,包括限流、熔断等功能 |
| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
## 🐨 技术栈
| 项目 | 说明 |
|-----------------------|--------------------|
| `yudao-dependencies` | Maven 依赖版本管理 |
| `yudao-framework` | Java 框架拓展 |
| `yudao-server` | 管理后台 + 用户 APP 的服务端 |
| `yudao-admin-ui` | 管理后台的 UI 界面 |
| `yudao-user-ui` | 用户 APP 的 UI 界面 |
| `yudao-module-system` | 系统功能的 Module 模块 |
| `yudao-module-member` | 会员中心的 Module 模块 |
| `yudao-module-infra` | 基础设施的 Module 模块 |
| `yudao-module-tool` | 研发工具的 Module 模块 |
| `yudao-module-bpm` | 工作流程的 Module 模块 |
| `yudao-module-pay` | 支付系统的 Module 模块 |
### 后端
| 框架 | 说明 | 版本 | 学习指南 |
|---------------------------------------------------------------------------------------------|------------------|----------|----------------------------------------------------------------|
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.5.10 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 | |
| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.8 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.1 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
| [Redis](https://redis.io/) | key-value 数据库 | 5.0 | |
| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.16.8 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.16 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.5.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.2 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
| [Activiti](https://github.com/Activiti/Activiti) | 工作流引擎 | 7.1.0.M6 | [文档](TODO) |
| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
| [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.2 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.0 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) |
| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.4.2 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.12.6 | |
| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |
| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.16.14 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.7.2 | - |
| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 3.9.0 | - |
### 前端
| 框架 | 说明 | 版本 |
|------------------------------------------------------------------------------|---------------|--------|
| [Vue](https://cn.vuejs.org/index.html) | JavaScript 框架 | 2.6.12 |
| [Vue Element Admin](https://panjiachen.github.io/vue-element-admin-site/zh/) | 后台前端解决方案 | - |
## 🐷 演示图
### 系统功能
| 模块 | biu | biu | biu |
|----------|--------------------------------------------------------------------|------------------------------------------------------------------|------------------------------------------------------------------|
| 登录 & 首页 | ![登录](https://static.iocoder.cn/images/ruoyi-vue-pro/登录.jpg) | ![首页](https://static.iocoder.cn/images/ruoyi-vue-pro/首页.jpg) | ![个人中心](https://static.iocoder.cn/images/ruoyi-vue-pro/个人中心.jpg) |
| 用户 | ![用户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/用户管理.jpg) | ![在线用户](https://static.iocoder.cn/images/ruoyi-vue-pro/在线用户.jpg) | - |
| 租户 & 套餐 | ![租户管理](https://static.iocoder.cn/images/ruoyi-vue-pro/租户管理.jpg) | ![租户套餐](https://static.iocoder.cn/images/ruoyi-vue-pro/租户套餐.png) | - |
| 部门 & 岗位 | ![部门管理](https://static.iocoder.cn/images/ruoyi-vue-pro/部门管理.jpg) | ![岗位管理](https://static.iocoder.cn/images/ruoyi-vue-pro/岗位管理.jpg) | - |
| 菜单 & 角色 | ![菜单管理](https://static.iocoder.cn/images/ruoyi-vue-pro/菜单管理.jpg) | ![角色管理](https://static.iocoder.cn/images/ruoyi-vue-pro/角色管理.jpg) | - |
| 审计日志 | ![操作日志](https://static.iocoder.cn/images/ruoyi-vue-pro/操作日志.jpg) | ![登录日志](https://static.iocoder.cn/images/ruoyi-vue-pro/登录日志.jpg) | - |
| 短信 | ![短信渠道](https://static.iocoder.cn/images/ruoyi-vue-pro/短信渠道.jpg) | ![短信模板](https://static.iocoder.cn/images/ruoyi-vue-pro/短信模板.jpg) | ![短信日志](https://static.iocoder.cn/images/ruoyi-vue-pro/短信日志.jpg) |
| 字典 | ![字典类型](https://static.iocoder.cn/images/ruoyi-vue-pro/字典类型.jpg) | ![字典数据](https://static.iocoder.cn/images/ruoyi-vue-pro/字典数据.jpg) | - |
| 错误码 & 通知 | ![错误码管理](https://static.iocoder.cn/images/ruoyi-vue-pro/错误码管理.jpg) | ![通知公告](https://static.iocoder.cn/images/ruoyi-vue-pro/通知公告.jpg) | - |
### 工作流程
| 模块 | biu | biu | biu |
|---------|------------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------|
| 流程模型 | ![流程模型-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-列表.jpg) | ![流程模型-设计](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-设计.jpg) | ![流程模型-定义](https://static.iocoder.cn/images/ruoyi-vue-pro/流程模型-定义.jpg) |
| 表单 & 分组 | ![流程表单](https://static.iocoder.cn/images/ruoyi-vue-pro/流程表单.jpg) | ![用户分组](https://static.iocoder.cn/images/ruoyi-vue-pro/用户分组.jpg) | - |
| 我的流程 | ![我的流程-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-列表.jpg) | ![我的流程-发起](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-发起.jpg) | ![我的流程-详情](https://static.iocoder.cn/images/ruoyi-vue-pro/我的流程-详情.jpg) |
| 待办 & 已办 | ![任务列表-审批](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-审批.jpg) | ![任务列表-待办](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-待办.jpg) | ![任务列表-已办](https://static.iocoder.cn/images/ruoyi-vue-pro/任务列表-已办.jpg) |
| OA 请假 | ![OA请假-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-列表.jpg) | ![OA请假-发起](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-发起.jpg) | ![OA请假-详情](https://static.iocoder.cn/images/ruoyi-vue-pro/OA请假-详情.jpg) |
### 支付系统
| 模块 | biu | biu | biu |
|---------|------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------|
| 商家 & 应用 | ![商户信息](https://static.iocoder.cn/images/ruoyi-vue-pro/商户信息.jpg) | ![应用信息-列表](https://static.iocoder.cn/images/ruoyi-vue-pro/应用信息-列表.jpg) | ![应用信息-编辑](https://static.iocoder.cn/images/ruoyi-vue-pro/应用信息-编辑.jpg) |
| 支付 & 退款 | ![支付订单](https://static.iocoder.cn/images/ruoyi-vue-pro/支付订单.jpg) | ![退款订单](https://static.iocoder.cn/images/ruoyi-vue-pro/退款订单.jpg) | --- |
### 基础设施
| 模块 | biu | biu | biu |
|---------------|----------------------------------------------------------------------|--------------------------------------------------------------------|------------------------------------------------------------------|
| 代码生成 | ![代码生成](https://static.iocoder.cn/images/ruoyi-vue-pro/代码生成.jpg) | ![生成效果](https://static.iocoder.cn/images/ruoyi-vue-pro/生成效果.jpg) | - |
| 文档 | ![系统接口](https://static.iocoder.cn/images/ruoyi-vue-pro/系统接口.jpg) | ![数据库文档](https://static.iocoder.cn/images/ruoyi-vue-pro/数据库文档.jpg) | - |
| 文件 & 配置 | ![文件配置](https://static.iocoder.cn/images/ruoyi-vue-pro/文件配置.jpg) | ![文件管理](https://static.iocoder.cn/images/ruoyi-vue-pro/文件管理2.jpg) | ![配置管理](https://static.iocoder.cn/images/ruoyi-vue-pro/配置管理.jpg) |
| 定时任务 | ![定时任务](https://static.iocoder.cn/images/ruoyi-vue-pro/定时任务.jpg) | ![任务日志](https://static.iocoder.cn/images/ruoyi-vue-pro/任务日志.jpg) | - |
| API 日志 | ![访问日志](https://static.iocoder.cn/images/ruoyi-vue-pro/访问日志.jpg) | ![错误日志](https://static.iocoder.cn/images/ruoyi-vue-pro/错误日志.jpg) | - |
| MySQL & Redis | ![MySQL](https://static.iocoder.cn/images/ruoyi-vue-pro/MySQL.jpg) | ![Redis](https://static.iocoder.cn/images/ruoyi-vue-pro/Redis.jpg) | - |
| 监控平台 | ![Java监控](https://static.iocoder.cn/images/ruoyi-vue-pro/Java监控.jpg) | ![链路追踪](https://static.iocoder.cn/images/ruoyi-vue-pro/链路追踪.jpg) | ![日志中心](https://static.iocoder.cn/images/ruoyi-vue-pro/日志中心.jpg) |
+1
View File
@@ -0,0 +1 @@
rsync -v ../yudao-server/target/yudao-server.jar root@47.108.56.75:/www/wwwroot/yudao/yudao-server.jar
+6
View File
@@ -0,0 +1,6 @@
{
"name": "zsw-farm",
"lockfileVersion": 2,
"requires": true,
"packages": {}
}
+12 -2
View File
@@ -13,12 +13,13 @@
<!-- Server 主项目 --> <!-- Server 主项目 -->
<module>yudao-server</module> <module>yudao-server</module>
<!-- 各种 module 拓展 --> <!-- 各种 module 拓展 -->
<module>yudao-module-member</module>
<module>yudao-module-bpm</module> <module>yudao-module-bpm</module>
<module>yudao-module-member</module>
<module>yudao-module-system</module> <module>yudao-module-system</module>
<module>yudao-module-infra</module> <module>yudao-module-infra</module>
<module>yudao-module-pay</module> <module>yudao-module-pay</module>
<module>zsw-farm</module> <module>zsw-bxg</module>
<module>zsw-spi</module>
</modules> </modules>
<name>${project.artifactId}</name> <name>${project.artifactId}</name>
@@ -48,6 +49,12 @@
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.7.4</version>
<optional>true</optional>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
@@ -60,6 +67,9 @@
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version> <version>${maven-surefire-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin> </plugin>
<!-- maven-compiler-plugin 插件,解决 Lombok + MapStruct 组合 --> <!-- maven-compiler-plugin 插件,解决 Lombok + MapStruct 组合 -->
<plugin> <plugin>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
View File
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
只需要 vue_pro 和 vue_pro_bxg 两个数据库
Binary file not shown.
Binary file not shown.
+3 -2
View File
@@ -14,6 +14,7 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url> <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties> <properties>
<skipTest>true</skipTest>
<revision>1.6.2-snapshot</revision> <revision>1.6.2-snapshot</revision>
<!-- 统一依赖管理 --> <!-- 统一依赖管理 -->
<spring.boot.version>2.5.10</spring.boot.version> <spring.boot.version>2.5.10</spring.boot.version>
@@ -22,7 +23,7 @@
<swagger-annotations.version>1.5.22</swagger-annotations.version> <swagger-annotations.version>1.5.22</swagger-annotations.version>
<servlet.versoin>2.5</servlet.versoin> <servlet.versoin>2.5</servlet.versoin>
<!-- DB 相关 --> <!-- DB 相关 -->
<mysql.version>5.1.46</mysql.version> <mysql.version>8.0.23</mysql.version>
<druid.version>1.2.8</druid.version> <druid.version>1.2.8</druid.version>
<mybatis-plus.version>3.4.3.4</mybatis-plus.version> <mybatis-plus.version>3.4.3.4</mybatis-plus.version>
<dynamic-datasource.version>3.5.0</dynamic-datasource.version> <dynamic-datasource.version>3.5.0</dynamic-datasource.version>
@@ -47,7 +48,7 @@
<!-- 工具类相关 --> <!-- 工具类相关 -->
<lombok.version>1.18.20</lombok.version> <lombok.version>1.18.20</lombok.version>
<mapstruct.version>1.4.1.Final</mapstruct.version> <mapstruct.version>1.4.1.Final</mapstruct.version>
<hutool.version>5.6.1</hutool.version> <hutool.version>5.8.0.M1</hutool.version>
<easyexcel.verion>2.2.7</easyexcel.verion> <easyexcel.verion>2.2.7</easyexcel.verion>
<velocity.version>2.2</velocity.version> <velocity.version>2.2</velocity.version>
<screw.version>1.0.5</screw.version> <screw.version>1.0.5</screw.version>
+6
View File
@@ -127,6 +127,12 @@
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId> <artifactId>transmittable-thread-local</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.0</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
@@ -0,0 +1,119 @@
package cn.iocoder.yudao.framework.common.exception;
/**
* API 响应码
* @author hupeng
* @date 2020-04-30
*/
public enum ApiCode {
/**
* 操作成功
**/
SUCCESS(200, "操作成功"),
/**
* 登录状态失效 请重新登录
**/
UNAUTHORIZED(401, "登录状态失效 请重新登录"),
/**
* 没有权限
**/
NOT_PERMISSION(403, "没有权限"),
/**
* 你请求的资源不存在
**/
NOT_FOUND(404, "你请求的资源不存在"),
/**
* 操作失败
**/
FAIL(500, "操作失败"),
/**
* 登录失败
**/
LOGIN_EXCEPTION(4000, "登录失败"),
/**
* 系统异常
**/
SYSTEM_EXCEPTION(5000, "系统异常"),
/**
* 请求参数校验异常
**/
PARAMETER_EXCEPTION(5001, "请求参数校验异常"),
/**
* 请求参数解析异常
**/
PARAMETER_PARSE_EXCEPTION(5002, "请求参数解析异常"),
/**
* HTTP内容类型异常
**/
HTTP_MEDIA_TYPE_EXCEPTION(5003, "HTTP内容类型异常"),
/**
* 系统处理异常
**/
YSHOP_SYSTEM_EXCEPTION(5100, "系统处理异常"),
/**
* 业务处理异常
**/
BUSINESS_EXCEPTION(5101, "业务处理异常"),
/**
* 数据库处理异常
**/
DAO_EXCEPTION(5102, "数据库处理异常"),
/**
* 验证码校验异常
**/
VERIFICATION_CODE_EXCEPTION(5103, "验证码校验异常"),
/**
* 登录授权异常
**/
AUTHENTICATION_EXCEPTION(5104, "登录授权异常"),
/**
* 没有访问权限
**/
UNAUTHENTICATED_EXCEPTION(5105, "没有访问权限"),
/**
* 没有访问权限
**/
UNAUTHORIZED_EXCEPTION(5106, "没有访问权限"),
/**
* JWT Token解析异常
**/
JWTDECODE_EXCEPTION(5107, "Token解析异常"),
HTTP_REQUEST_METHOD_NOT_SUPPORTED_EXCEPTION(5108, "METHOD NOT SUPPORTED"),
/**
* 访问次数受限制
**/
BAD_LIMIT_EXCEPTION(5109, "访问次数受限制"),
;
private final int code;
private final String message;
ApiCode(final int code, final String message) {
this.code = code;
this.message = message;
}
public static ApiCode getApiCode(int code) {
ApiCode[] ecs = ApiCode.values();
for (ApiCode ec : ecs) {
if (ec.getCode() == code) {
return ec;
}
}
return SUCCESS;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
@@ -0,0 +1,52 @@
package cn.iocoder.yudao.framework.common.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 自定义异常
* @author hupeng
* @date 2020-04-30
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ShopException extends RuntimeException{
private static final long serialVersionUID = -2470461654663264392L;
private Integer errorCode;
private String message;
public ShopException() {
super();
}
public ShopException(String message) {
super(message);
this.errorCode = ApiCode.FAIL.getCode();
this.message = message;
}
public ShopException(Integer errorCode, String message) {
super(message);
this.errorCode = errorCode;
this.message = message;
}
public ShopException(ApiCode apiCode) {
super(apiCode.getMessage());
this.errorCode = apiCode.getCode();
this.message = apiCode.getMessage();
}
public ShopException(String message, Throwable cause) {
super(message, cause);
}
public ShopException(Throwable cause) {
super(cause);
}
}
@@ -36,6 +36,9 @@ public interface GlobalErrorCodeConstants {
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误"); ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
//农作物出入管理
ErrorCode CROP_RECORD_CANT_NEGATIVE = new ErrorCode(10001, "出库失败,农作物库存小于出库数量");
static boolean isMatch(Integer code) { static boolean isMatch(Integer code) {
return code != null return code != null
&& code >= SUCCESS.getCode() && code <= UNKNOWN.getCode(); && code >= SUCCESS.getCode() && code <= UNKNOWN.getCode();
@@ -0,0 +1,23 @@
package cn.iocoder.yudao.framework.common.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel(value = "PageDTO", description = "分页对象")
public class PageDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("页码")
private Integer pageNum = 1;
@ApiModelProperty("每页数量")
private Integer pageSize = 10;
@ApiModelProperty("搜索关键字")
private String searchKey;
}
@@ -0,0 +1,98 @@
package cn.iocoder.yudao.framework.common.page;
import cn.hutool.core.bean.BeanUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.experimental.UtilityClass;
import java.util.ArrayList;
import java.util.List;
/**
* 分页工具类
*
* @author xggz <yyimba@qq.com>
* @since 2021/6/4 11:50
*/
@UtilityClass
public class PageUtil<T> {
/**
* 转换PageHelper插件的分页数据
*
* @param pageInfo
* @param <T>
* @return
*/
public <T> PageVO<T> convertPageInfo(PageInfo<T> pageInfo) {
return BeanUtil.copyProperties(pageInfo, PageVO.class);
}
/**
* 转换PageHelper插件的分页数据
*
* @param list
* @param <T>
* @return
*/
public <T> PageVO<T> convertPageInfo(List<T> list) {
return BeanUtil.copyProperties(new PageInfo<T>(list), PageVO.class);
}
/**
* 复制分页数据
*
* @param sourcePage
* @param results
* @param <T>
* @return
*/
public <T> PageVO<T> copyPage(Object sourcePage, List<T> results) {
PageVO<T> targetPage = BeanUtil.copyProperties(sourcePage, PageVO.class);
targetPage.setList(results);
return targetPage;
}
/**
* 返回空白的分页对象
*
* @param pageNum
* @param pageSize
* @param dataClass
* @param <T>
* @return
*/
public <T> PageVO<T> emptyPage(Integer pageNum, Integer pageSize, Class<T> dataClass) {
PageVO page = new PageVO<>();
page.setPageNum(pageNum);
page.setPageSize(pageSize);
page.setSize(0);
page.setPages(0);
page.setTotal(0);
page.setHasNextPage(false);
page.setHasPreviousPage(false);
page.setList(new ArrayList());
return page;
}
/**
* 返回空白的分页对象
*
* @param pageDTO
* @param dataClass
* @param <T>
* @return
*/
public <T> PageVO<T> emptyPage(PageDTO pageDTO, Class<T> dataClass) {
return emptyPage(pageDTO.getPageNum(), pageDTO.getPageSize(), dataClass);
}
/**
* 使用PageHelper设置分页参数
*
* @param pageDTO
*/
public void startPage(PageDTO pageDTO) {
PageHelper.startPage(pageDTO.getPageNum(), pageDTO.getPageSize());
}
}
@@ -0,0 +1,45 @@
package cn.iocoder.yudao.framework.common.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 分页结果
*
* @author xggz <yyimba@qq.com>
* @since 2021/6/4 10:53
*/
@Data
@ApiModel(value = "PageVO", description = "分页结果")
public class PageVO<T> implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("当前页")
private int pageNum;
@ApiModelProperty("每页的数量")
private int pageSize;
@ApiModelProperty("当前页的数量")
private int size;
@ApiModelProperty("总页数")
private int pages;
@ApiModelProperty("是否有前一页")
private boolean hasPreviousPage = false;
@ApiModelProperty("是否有后一页")
private boolean hasNextPage = false;
@ApiModelProperty("总记录数")
private long total;
@ApiModelProperty("结果集")
private List<T> list;
}
@@ -0,0 +1,225 @@
package cn.iocoder.yudao.framework.common.pojo;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ApiCode;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* API 返回结果
* @author hupeng
* @date 2020-04-30
*/
@Data
@Accessors(chain = true)
@Builder
@AllArgsConstructor
public class ApiResult<T> implements Serializable {
private static final long serialVersionUID = 8004487252556526569L;
/**
* 响应码
*/
@ApiModelProperty(value = "响应码")
private int status;
/**
* 是否成功
*/
@ApiModelProperty(value = "是否成功:成功true,失败false")
private boolean success;
/**
* 响应消息
*/
@ApiModelProperty(value = "响应消息")
private String msg;
/**
* 总条数
*/
@ApiModelProperty(value = "总条数")
private Integer total;
/**
* 总页数
*/
@ApiModelProperty(value = "总页数")
private Integer totalPage;
/**
* 响应数据
*/
@ApiModelProperty(value = "响应数据")
private T data;
/**
* 响应时间
*/
@ApiModelProperty(value = "响应时间")
//@JSONField(format = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
private Date time;
public ApiResult() {
time = new Date();
}
public static ApiResult<Boolean> result(boolean flag){
if (flag){
return ok();
}
return fail();
}
public static ApiResult<Boolean> result(ApiCode apiCode){
return result(apiCode,null);
}
public static <T> ApiResult<T> result(ApiCode apiCode, T data){
return result(apiCode,null,data);
}
public static <T> ApiResult<T> resultPage(Integer total, Integer totalPage, T data){
return (ApiResult<T>) ApiResult.builder()
.total(total)
.totalPage(totalPage)
.status(200)
.msg(null)
.data(data)
.success(true)
.time(new Date())
.build();
}
public static <T> ApiResult<T> result(ApiCode apiCode, String message, T data){
boolean success = false;
if (apiCode.getCode() == ApiCode.SUCCESS.getCode()){
success = true;
}
if (StrUtil.isBlank(message)){
message = apiCode.getMessage();
}
return (ApiResult<T>) ApiResult.builder()
.status(apiCode.getCode())
.msg(message)
.data(data)
.success(success)
.time(new Date())
.build();
}
public static ApiResult<Boolean> ok(){
return ok(null);
}
public static <T> ApiResult<T> ok(T data){
return result(ApiCode.SUCCESS,data);
}
public static <T> ApiResult<T> ok(T data, String message){
return result(ApiCode.SUCCESS,message,data);
}
public static ApiResult<Map<String,Object>> okMap(String key, Object value){
Map<String,Object> map = new HashMap<>(1);
map.put(key,value);
return ok(map);
}
public static ApiResult<Boolean> fail(ApiCode apiCode){
return result(apiCode,null);
}
public static ApiResult<String> fail(String message){
return result(ApiCode.FAIL,message,null);
}
public static <T> ApiResult<T> fail(ApiCode apiCode, T data){
if (ApiCode.SUCCESS == apiCode){
throw new RuntimeException("失败结果状态码不能为" + ApiCode.SUCCESS.getCode());
}
return result(apiCode,data);
}
public static ApiResult<String> fail(Integer errorCode, String message){
return new ApiResult<String>()
.setSuccess(false)
.setStatus(errorCode)
.setMsg(message);
}
public static ApiResult<Map<String,Object>> fail(String key, Object value){
Map<String,Object> map = new HashMap<>(1);
map.put(key,value);
return result(ApiCode.FAIL,map);
}
public static <T> ApiResult<T> resultPage(T t, int limit){
List<Object> list = (List<Object>) t;
int count = list.size() / limit;
if (list.size() == 0) {
return (ApiResult<T>) ApiResult.builder()
.total(0)
.totalPage(0)
.status(200)
.msg(null)
.data(list)
.success(true)
.time(new Date())
.build();
}
if (list.size() <= limit) {
return (ApiResult<T>) ApiResult.builder()
.total(list.size())
.totalPage(1)
.status(200)
.msg(null)
.data(list)
.success(true)
.time(new Date())
.build();
} else if (count % limit == 0) {
return (ApiResult<T>) ApiResult.builder()
.total(list.size())
.totalPage(count)
.status(200)
.msg(null)
.data(list)
.success(true)
.time(new Date())
.build();
} else {
return (ApiResult<T>) ApiResult.builder()
.total(list.size())
.totalPage(count+1)
.status(200)
.msg(null)
.data(list)
.success(true)
.time(new Date())
.build();
}
}
public static ApiResult<Boolean> fail() {
return fail(ApiCode.FAIL);
}
}
@@ -21,10 +21,10 @@ public class PageParam implements Serializable {
@Min(value = 1, message = "页码最小值为 1") @Min(value = 1, message = "页码最小值为 1")
private Integer pageNo = PAGE_NO; private Integer pageNo = PAGE_NO;
@ApiModelProperty(value = "每页条数,最大值为 100", required = true, example = "10") @ApiModelProperty(value = "每页条数,最大值为 500", required = true, example = "10")
@NotNull(message = "每页条数不能为空") @NotNull(message = "每页条数不能为空")
@Min(value = 1, message = "页码最小值为 1") @Min(value = 1, message = "页码最小值为 1")
@Max(value = 100, message = "页码最大值为 100") @Max(value = 500, message = "页码最大值为 500")
private Integer pageSize = PAGE_SIZE; private Integer pageSize = PAGE_SIZE;
} }
@@ -40,6 +40,10 @@
<artifactId>yudao-spring-boot-starter-test</artifactId> <artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>
@@ -2,10 +2,10 @@ package cn.iocoder.yudao.framework.datapermission.core.dept.rule;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule; import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
@@ -13,7 +13,7 @@ import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.Expression;
@@ -23,6 +23,7 @@ import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@@ -146,6 +147,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new))); new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)));
} }
@SneakyThrows
private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) { private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) {
// 如果不查看自己,则无需作为条件 // 如果不查看自己,则无需作为条件
if (Boolean.FALSE.equals(self)) { if (Boolean.FALSE.equals(self)) {
@@ -155,6 +157,17 @@ public class DeptDataPermissionRule implements DataPermissionRule {
if (StrUtil.isEmpty(columnName)) { if (StrUtil.isEmpty(columnName)) {
return null; return null;
} }
Long cpUserId = deptDataPermissionService.getCpUserIdBySystemUserId(userId);
if (tableName.equals("farm_project")){
Expression projectSql = CCJSqlParserUtil.parseCondExpression("(creator = " + userId + " or JSON_CONTAINS(members, '"+ cpUserId +"'))");
return projectSql;
}
if (tableName.equals("farm_task")){
Expression taskSql = CCJSqlParserUtil.parseCondExpression(" (creator = " + userId + " or JSON_CONTAINS(executor_person, '"+ cpUserId +"')" +
" or main_person = " + cpUserId +")");
return taskSql;
}
// 拼接条件 // 拼接条件
return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId)); return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId));
} }
@@ -19,4 +19,6 @@ public interface DeptDataPermissionFrameworkService {
*/ */
DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser); DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser);
Long getCpUserIdBySystemUserId(Long id);
} }
@@ -49,7 +49,9 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
*/ */
public final void init() { public final void init() {
doInit(); doInit();
log.info("[init][配置({}) 初始化完成]", config); // PayClientConfig
// log.info("[init][配置({}) 初始化完成]", config);
log.info("[init][配置({}) 初始化完成]", PayClientConfig.class);
} }
/** /**
@@ -2,6 +2,8 @@ package cn.iocoder.yudao.framework.tenant.core.context;
import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.function.Supplier;
/** /**
* 多租户上下文 Holder * 多租户上下文 Holder
* *
@@ -58,6 +60,16 @@ public class TenantContextHolder {
return Boolean.TRUE.equals(IGNORE.get()); return Boolean.TRUE.equals(IGNORE.get());
} }
public static <T> T apply(Long tenantId, Supplier<T> func){
Long oldTenant = TenantContextHolder.getTenantId();
try {
TenantContextHolder.setTenantId(tenantId);
return func.get();
}finally {
TenantContextHolder.setTenantId(oldTenant);
}
}
public static void clear() { public static void clear() {
TENANT_ID.remove(); TENANT_ID.remove();
IGNORE.remove(); IGNORE.remove();
@@ -12,7 +12,8 @@
<packaging>jar</packaging> <packaging>jar</packaging>
<name>${project.artifactId}</name> <name>${project.artifactId}</name>
<description>微信拓展 <description>
微信拓展
1. 基于 weixin-java-mp 库,对接微信公众号平台。目前主要解决微信公众号的支付场景。 1. 基于 weixin-java-mp 库,对接微信公众号平台。目前主要解决微信公众号的支付场景。
</description> </description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url> <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
@@ -31,13 +32,33 @@
</dependency> </dependency>
<!-- 三方云服务相关 --> <!-- 三方云服务相关 -->
<!-- https://mvnrepository.com/artifact/com.github.binarywang/weixin-java-mp -->
<dependency> <dependency>
<groupId>com.github.binarywang</groupId> <groupId>com.github.binarywang</groupId>
<!-- <artifactId>weixin-java-mp</artifactId>--> <artifactId>weixin-java-mp</artifactId>
<artifactId>wx-java-mp-spring-boot-starter</artifactId> <version>4.3.0</version>
<version>4.1.9.B</version>
</dependency> </dependency>
<!-- TODO 芋艿:清理 -->
<!-- https://mvnrepository.com/artifact/com.github.binarywang/weixin-java-miniapp -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>4.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.binarywang/weixin-java-cp -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-cp</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-extension</artifactId>
<version>${revision}</version>
</dependency>
</dependencies> </dependencies>
</project> </project>
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.config;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
@Slf4j
public class WxCpConfigure {
@Value("${wxcp.corpId}")
private String corpId;
@Value("${wxcp.secret}")
private String secret;
@Value("${wxcp.agentId}")
private Integer agentId;
@Bean
@Scope("prototype")
public WxCpService wxCpService(){
WxCpDefaultConfigImpl config = new WxCpDefaultConfigImpl();
config.setCorpId(corpId);
config.setCorpSecret(secret);
config.setAgentId(agentId);
WxCpServiceImpl wxCpService = new WxCpServiceImpl();
wxCpService.setWxCpConfigStorage(config);
log.info("企业微信初始化:{}",wxCpService);
return wxCpService;
}
}
@@ -0,0 +1,42 @@
package cn.iocoder.yudao.config;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
/**
* Created by kellen on 2020/5/3. 微信小程序服务配置
*/
@Configuration
@Slf4j
public class WxMaConfiguration {
@Value("${wxma.app_id}")
private String appId;
@Value("${wxma.app_secret}")
private String appSecret;
@Bean
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public WxMaService wxMaService() {
WxMaDefaultConfigImpl wxMaDefaultConfig = new WxMaDefaultConfigImpl();
wxMaDefaultConfig.setAppid(appId);
wxMaDefaultConfig.setSecret(appSecret);
WxMaServiceImpl wxMaService = new WxMaServiceImpl();
wxMaService.setWxMaConfig(wxMaDefaultConfig);
return wxMaService;
}
}
@@ -0,0 +1,3 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.config.WxCpConfigure,\
cn.iocoder.yudao.config.WxMaConfiguration
@@ -17,7 +17,7 @@ public class S3FileClientTest {
// 配置成你自己的 // 配置成你自己的
config.setAccessKey("admin"); config.setAccessKey("admin");
config.setAccessSecret("password"); config.setAccessSecret("password");
config.setBucket("yudaoyuanma"); config.setBucket("zsw");
config.setDomain(null); config.setDomain(null);
// 默认 9000 endpoint // 默认 9000 endpoint
config.setEndpoint("http://127.0.0.1:9000"); config.setEndpoint("http://127.0.0.1:9000");
@@ -17,6 +17,8 @@
<artifactId>yudao-common</artifactId> <artifactId>yudao-common</artifactId>
</dependency> </dependency>
<!-- Web 相关 --> <!-- Web 相关 -->
<dependency> <dependency>
<groupId>cn.iocoder.boot</groupId> <groupId>cn.iocoder.boot</groupId>
@@ -0,0 +1,69 @@
package cn.iocoder.yudao.framework.quartz.config;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ResourceLoader;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionManager;
import javax.sql.DataSource;
@Configuration
@ConditionalOnProperty(prefix = "spring.quartz", name = "job-store-type", havingValue = "jdbc")
@Import(DatabaseInitializationDependencyConfigurer.class)
@EnableConfigurationProperties(QuartzProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
LiquibaseAutoConfiguration.class, FlywayAutoConfiguration.class })
public class JdbcStoreTypeConfiguration {
@Bean
@Order(0)
public SchedulerFactoryBeanCustomizer dataSourceCustomizer(QuartzProperties properties,
DataSource dataSource,
@QuartzDataSource ObjectProvider<DataSource> quartzDataSource,
TransactionManager transactionManager
) {
return (schedulerFactoryBean) -> {
DataSource dataSourceToUse = getDataSource(dataSource, quartzDataSource);
schedulerFactoryBean.setDataSource(dataSourceToUse);
schedulerFactoryBean.setTransactionManager((PlatformTransactionManager) transactionManager);
};
}
private DataSource getDataSource(DataSource dataSource, ObjectProvider<DataSource> quartzDataSource) {
DataSource dataSourceIfAvailable = quartzDataSource.getIfAvailable();
return (dataSourceIfAvailable != null) ? dataSourceIfAvailable : dataSource;
}
private PlatformTransactionManager getTransactionManager(
ObjectProvider<PlatformTransactionManager> transactionManager,
ObjectProvider<PlatformTransactionManager> quartzTransactionManager) {
PlatformTransactionManager transactionManagerIfAvailable = quartzTransactionManager.getIfAvailable();
return (transactionManagerIfAvailable != null) ? transactionManagerIfAvailable
: transactionManager.getIfUnique();
}
@Bean
@ConditionalOnMissingBean
public QuartzDataSourceInitializer quartzDataSourceInitializer(DataSource dataSource,
@QuartzDataSource ObjectProvider<DataSource> quartzDataSource, ResourceLoader resourceLoader,
QuartzProperties properties) {
DataSource dataSourceToUse = getDataSource(dataSource, quartzDataSource);
return new QuartzDataSourceInitializer(dataSourceToUse, resourceLoader, properties);
}
}
@@ -1,3 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.framework.quartz.config.YudaoQuartzAutoConfiguration,\ cn.iocoder.yudao.framework.quartz.config.YudaoQuartzAutoConfiguration,\
cn.iocoder.yudao.framework.quartz.config.YudaoAsyncAutoConfiguration cn.iocoder.yudao.framework.quartz.config.YudaoAsyncAutoConfiguration,\
cn.iocoder.yudao.framework.quartz.config.JdbcStoreTypeConfiguration
@@ -16,7 +16,7 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url> <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties> <properties>
<mysql.version>5.1.46</mysql.version> <mysql.version>8.0.23</mysql.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -46,10 +46,8 @@
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId> <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
</dependency>
</dependencies> </dependencies>
</project> </project>
@@ -1,22 +1,51 @@
package cn.iocoder.yudao.framework.mybatis.config; package cn.iocoder.yudao.framework.mybatis.config;
import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler; import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.stat.DruidDataSourceStatManager;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.annotation.MapperScans;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.quartz.QuartzDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionManager;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.Map;
/** /**
* MyBaits 配置类 * MyBaits 配置类
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@MapperScans({
@MapperScan(basePackages ={"co.yixiang.**.service.mapper", "co.yixiang.config"},sqlSessionFactoryRef = "shangcheng"),
@MapperScan(basePackages = {"${yudao.info.base-package}", "cn.iocoder.yudao"}, annotationClass = Mapper.class,
lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试
})
@Configuration @Configuration
@MapperScan(value = "${yudao.info.base-package}", annotationClass = Mapper.class, @Slf4j
lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试
public class YudaoMybatisAutoConfiguration { public class YudaoMybatisAutoConfiguration {
@Bean @Bean
@@ -31,4 +60,55 @@ public class YudaoMybatisAutoConfiguration {
return new DefaultDBFieldHandler(); // 自动填充参数类 return new DefaultDBFieldHandler(); // 自动填充参数类
} }
@Bean("dataSource")
@QuartzDataSource
@ConfigurationProperties("spring.datasource.dynamic.datasource.master")
public DataSource masterDataSource(){
return new DruidDataSource();
}
@Bean
@Primary
public SqlSessionFactory sqlSessionFactoryMaster(@Qualifier("dataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setTypeEnumsPackage("cn.iocoder.yudao.module.farm.enums");
factory.setDataSource(dataSource);
return getSqlSessionFactory(factory);
}
@Bean("bxgDataSource")
@ConfigurationProperties("spring.datasource.dynamic.datasource.bxg")
public DataSource bxgDataSource(){
return new DruidDataSource();
}
@Bean("shangcheng")
public SqlSessionFactory sqlSessionFactory(@Qualifier("bxgDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
return getSqlSessionFactory(factory);
}
private SqlSessionFactory getSqlSessionFactory(MybatisSqlSessionFactoryBean factory) throws Exception {
GlobalConfig globalConfig = new GlobalConfig();
GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();
dbConfig.setIdType(IdType.AUTO);
globalConfig.setDbConfig(dbConfig);
globalConfig.setMetaObjectHandler(defaultMetaObjectHandler());
Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*/*.xml");
Arrays.stream(resources).forEach(resource -> {
log.info("master mapper:{}",resource.getFilename());
});
factory.setMapperLocations(resources);
factory.setPlugins(mybatisPlusInterceptor());
factory.setGlobalConfig(globalConfig);
return factory.getObject();
}
@Bean
public TransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(dataSource);
return manager;
}
} }
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.mybatis.core.dataobject;
import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@@ -21,11 +22,13 @@ public abstract class BaseDO implements Serializable {
* 创建时间 * 创建时间
*/ */
@TableField(fill = FieldFill.INSERT) @TableField(fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime; private Date createTime;
/** /**
* 最后更新时间 * 最后更新时间
*/ */
@TableField(fill = FieldFill.INSERT_UPDATE) @TableField(fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime; private Date updateTime;
/** /**
* 创建者,目前使用 SysUser 的 id 编号 * 创建者,目前使用 SysUser 的 id 编号
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.framework.mybatis.core.dataobject;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Date;
/**
* @ClassName 公共模型
* @Author hupeng <610796224@qq.com>
* @Date 2020/6/13
**/
@Getter
@Setter
public class BaseDomain implements Serializable {
private static final long serialVersionUID = 1L;
@TableField(fill= FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
private Date createTime;
@TableField(fill= FieldFill.UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
private Date updateTime;
@TableLogic
@JsonIgnore
@TableField(fill= FieldFill.INSERT)
private Integer isDel;
}
@@ -1,10 +1,13 @@
package cn.iocoder.yudao.framework.mybatis.core.handler; package cn.iocoder.yudao.framework.mybatis.core.handler;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.MetaObject;
import java.sql.Timestamp;
import java.util.Date; import java.util.Date;
import java.util.Objects; import java.util.Objects;
@@ -15,33 +18,61 @@ import java.util.Objects;
* *
* @author hexiaowu * @author hexiaowu
*/ */
@Slf4j
public class DefaultDBFieldHandler implements MetaObjectHandler { public class DefaultDBFieldHandler implements MetaObjectHandler {
@Override @Override
public void insertFill(MetaObject metaObject) { public void insertFill(MetaObject metaObject) {
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) { // if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) {
BaseDO baseDO = (BaseDO) metaObject.getOriginalObject(); // BaseDO baseDO = (BaseDO) metaObject.getOriginalObject();
//
// Date current = new Date();
// // 创建时间为空,则以当前时间为插入时间
// if (Objects.isNull(baseDO.getCreateTime())) {
// baseDO.setCreateTime(current);
// }
// // 更新时间为空,则以当前时间为更新时间
// if (Objects.isNull(baseDO.getUpdateTime())) {
// baseDO.setUpdateTime(current);
// }
//
//
// }
Date current = new Date(); Long userId = WebFrameworkUtils.getLoginUserId();
// 创建时间为空,则当前时间为插入时间 // 当前登录用户不为空,创建人为空,则当前登录用户为创建人
if (Objects.isNull(baseDO.getCreateTime())) { if (metaObject.hasSetter("creator") && ObjectUtil.isNotEmpty(userId)) {
baseDO.setCreateTime(current); this.setFieldValByName("creator", userId.toString(), metaObject);
}
// 更新时间为空,则以当前时间为更新时间
if (Objects.isNull(baseDO.getUpdateTime())) {
baseDO.setUpdateTime(current);
}
Long userId = WebFrameworkUtils.getLoginUserId();
// 当前登录用户不为空,创建人为空,则当前登录用户为创建人
if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) {
baseDO.setCreator(userId.toString());
}
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
if (Objects.nonNull(userId) && Objects.isNull(baseDO.getUpdater())) {
baseDO.setUpdater(userId.toString());
}
} }
if (metaObject.hasSetter("updater") && ObjectUtil.isNotEmpty(userId)) {
this.setFieldValByName("updater", userId.toString(), metaObject);
}
Timestamp time=new Timestamp(System.currentTimeMillis());
if (metaObject.hasSetter("createTime")) {
this.setFieldValByName("createTime", time, metaObject);
}
if (metaObject.hasSetter("updateTime")) {
this.setFieldValByName("updateTime", time, metaObject);
}
if (metaObject.hasSetter("createDate")) {
this.setFieldValByName("createDate", time, metaObject);
}
if (metaObject.hasSetter("updateDate")) {
this.setFieldValByName("updateDate", time, metaObject);
}
if (metaObject.hasSetter("delFlag")) {
this.setFieldValByName("delFlag", false, metaObject);
}
if (metaObject.hasSetter("isDel")) {
this.setFieldValByName("isDel", 0, metaObject);
}
if (metaObject.hasSetter("addTime")) {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
this.setFieldValByName("addTime", Integer.valueOf(timestamp), metaObject);
}
} }
@Override @Override
@@ -58,5 +89,20 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
if (Objects.nonNull(userId) && Objects.isNull(modifier)) { if (Objects.nonNull(userId) && Objects.isNull(modifier)) {
setFieldValByName("updater", userId.toString(), metaObject); setFieldValByName("updater", userId.toString(), metaObject);
} }
Timestamp time=new Timestamp(System.currentTimeMillis());
if (metaObject.hasSetter("updateTime")) {
this.setFieldValByName("updateTime", time, metaObject);
}
if (metaObject.hasSetter("updateDate")) {
this.setFieldValByName("updateDate", time, metaObject);
}
if (metaObject.hasSetter("delFlag")) {
this.setFieldValByName("delFlag", null, metaObject);
}
if (metaObject.hasSetter("createTime")) {
this.setFieldValByName("createTime", null, metaObject);
}
} }
} }
@@ -0,0 +1,68 @@
package cn.iocoder.yudao.framework.mybatis.core.handler;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* zyj
*/
@MappedJdbcTypes(JdbcType.VARCHAR) // 数据库中该字段存储的类型
@MappedTypes(List.class) // 需要转换的对象
public class ListIntToListLongTypeHandler extends BaseTypeHandler<List<Long>> {
private static ObjectMapper mObjectMapper = new ObjectMapper();
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, List<Long> longs, JdbcType jdbcType) throws SQLException {
String json = JSONUtil.toJsonStr(longs);
preparedStatement.setObject(i, json);
}
@Override
public List<Long> getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
String value = resultSet.getString(columnName);
return getLongs(value);
}
@Override
public List<Long> getNullableResult(ResultSet resultSet, int i) throws SQLException {
String value = resultSet.getString(i);
return getLongs(value);
}
@Override
public List<Long> getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
String value = callableStatement.getString(i);
return getLongs(value);
}
private List<Long> getLongs(String value) {
if (ObjectUtil.isNotEmpty(value)) {
try {
CollectionType type = mObjectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Long.class);
List<Long> longs = mObjectMapper.readValue(value , type);
return longs;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
return null;
}
}
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.idempotent.core.keyresolver.impl;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent; import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
import cn.iocoder.yudao.framework.idempotent.core.keyresolver.IdempotentKeyResolver; import cn.iocoder.yudao.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
@@ -18,7 +19,7 @@ public class DefaultIdempotentKeyResolver implements IdempotentKeyResolver {
@Override @Override
public String resolver(JoinPoint joinPoint, Idempotent idempotent) { public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
String methodName = joinPoint.getSignature().toString(); String methodName = joinPoint.getSignature().toString();
String argsStr = StrUtil.join(",", joinPoint.getArgs()); String argsStr = StrUtil.join(",", JSONUtil.toJsonStr(joinPoint.getArgs()));
return SecureUtil.md5(methodName + argsStr); return SecureUtil.md5(methodName + argsStr);
} }
@@ -21,28 +21,28 @@ public class YudaoCacheAutoConfiguration {
* *
* 参考 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration 的 createConfiguration 方法 * 参考 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration 的 createConfiguration 方法
*/ */
@Bean // @Bean
@Primary // @Primary
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) { // public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
// 设置使用 JSON 序列化方式 // // 设置使用 JSON 序列化方式
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); // RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json())); // config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
//
// 设置 CacheProperties.Redis 的属性 // // 设置 CacheProperties.Redis 的属性
CacheProperties.Redis redisProperties = cacheProperties.getRedis(); // CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) { // if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive()); // config = config.entryTtl(redisProperties.getTimeToLive());
} // }
if (redisProperties.getKeyPrefix() != null) { // if (redisProperties.getKeyPrefix() != null) {
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix()); // config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
} // }
if (!redisProperties.isCacheNullValues()) { // if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues(); // config = config.disableCachingNullValues();
} // }
if (!redisProperties.isUseKeyPrefix()) { // if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix(); // config = config.disableKeyPrefix();
} // }
return config; // return config;
} // }
} }
@@ -15,19 +15,19 @@ public class YudaoRedisAutoConfiguration {
/** /**
* 创建 RedisTemplate Bean,使用 JSON 序列化方式 * 创建 RedisTemplate Bean,使用 JSON 序列化方式
*/ */
@Bean // @Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { // public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 创建 RedisTemplate 对象 // // 创建 RedisTemplate 对象
RedisTemplate<String, Object> template = new RedisTemplate<>(); // RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置 RedisConnection 工厂。😈 它就是实现多种 Java Redis 客户端接入的秘密工厂。感兴趣的胖友,可以自己去撸下。 // // 设置 RedisConnection 工厂。😈 它就是实现多种 Java Redis 客户端接入的秘密工厂。感兴趣的胖友,可以自己去撸下。
template.setConnectionFactory(factory); // template.setConnectionFactory(factory);
// 使用 String 序列化方式,序列化 KEY 。 // // 使用 String 序列化方式,序列化 KEY 。
template.setKeySerializer(RedisSerializer.string()); // template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string()); // template.setHashKeySerializer(RedisSerializer.string());
// 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。 // // 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
template.setValueSerializer(RedisSerializer.json()); // template.setValueSerializer(RedisSerializer.json());
template.setHashValueSerializer(RedisSerializer.json()); // template.setHashValueSerializer(RedisSerializer.json());
return template; // return template;
} // }
} }
@@ -41,6 +41,6 @@ public class SecurityProperties {
* 一定要配置密钥,保证安全性 * 一定要配置密钥,保证安全性
*/ */
@NotEmpty(message = "mock 模式的密钥不能为空") // 这里设置了一个默认值,因为实际上只有 mockEnable 为 true 时才需要配置。 @NotEmpty(message = "mock 模式的密钥不能为空") // 这里设置了一个默认值,因为实际上只有 mockEnable 为 true 时才需要配置。
private String mockSecret = "yudaoyuanma"; private String mockSecret = "zsw";
} }
@@ -22,6 +22,7 @@ import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List; import java.util.List;
/** /**
@@ -86,8 +87,11 @@ public class YudaoSecurityAutoConfiguration {
* Token 认证过滤器 Bean * Token 认证过滤器 Bean
*/ */
@Bean @Bean
public JWTAuthenticationTokenFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider, public JWTAuthenticationTokenFilter authenticationTokenFilter(
HttpServletRequest request,
MultiUserDetailsAuthenticationProvider authenticationProvider,
GlobalExceptionHandler globalExceptionHandler) { GlobalExceptionHandler globalExceptionHandler) {
return new JWTAuthenticationTokenFilter(securityProperties, authenticationProvider, globalExceptionHandler); return new JWTAuthenticationTokenFilter(securityProperties, authenticationProvider, globalExceptionHandler);
} }
@@ -130,15 +130,22 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
// 设置 App API 无需认证 // 设置 App API 无需认证
.antMatchers(buildAppApi("/**")).permitAll() .antMatchers(buildAppApi("/**")).permitAll()
.antMatchers("/common/**").permitAll() .antMatchers("/common/**").permitAll()
// 忽略宝享购全部
.antMatchers("/bxgApp/**").permitAll()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.and().httpBasic().and().csrf().disable()
// ②:每个项目的自定义规则 // ②:每个项目的自定义规则
.and().authorizeRequests(registry -> // 下面,循环设置自定义规则 .authorizeRequests(registry -> // 下面,循环设置自定义规则
authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry))) authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry)))
// ③:兜底规则,必须认证 // ③:兜底规则,必须认证
.authorizeRequests() .authorizeRequests()
.anyRequest().authenticated() .anyRequest().authenticated()
; ;
// 添加 JWT Filter // // 添加 JWT Filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
} }
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.framework.security.core.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @ClassName 自定义权限注解
* @Author hupeng <610796224@qq.com>
* @Date 2020/4/30
**/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthCheck {
int value() default 4;
}
@@ -140,7 +140,12 @@ public class MultiUserDetailsAuthenticationProvider extends AbstractUserDetailsA
private UserTypeEnum getUserType(HttpServletRequest request) { private UserTypeEnum getUserType(HttpServletRequest request) {
// log.error("URI:{}",request.getRequestURI()); // log.error("URI:{}",request.getRequestURI());
if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix()) || request.getRequestURI().startsWith("/common/")) { if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())
|| request.getRequestURI().startsWith("/common/")
|| request.getRequestURI().startsWith("/bxg")
|| request.getRequestURI().startsWith("/erp")
|| request.getRequestURI().startsWith("/api/upload")
) {
return UserTypeEnum.ADMIN; return UserTypeEnum.ADMIN;
} }
if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) { if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) {
@@ -36,6 +36,7 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
@SuppressWarnings("NullableProblems") @SuppressWarnings("NullableProblems")
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException { throws ServletException, IOException {
String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
if (StrUtil.isNotEmpty(token)) { if (StrUtil.isNotEmpty(token)) {
try { try {
@@ -59,6 +59,12 @@
<artifactId>resilience4j-ratelimiter</artifactId> <artifactId>resilience4j-ratelimiter</artifactId>
<scope>provided</scope> <!-- 设置为 provided,主要是 GlobalExceptionHandler 使用 --> <scope>provided</scope> <!-- 设置为 provided,主要是 GlobalExceptionHandler 使用 -->
</dependency> </dependency>
<!-- <dependency>-->
<!-- <groupId>org.apache.dubbo</groupId>-->
<!-- <artifactId>dubbo</artifactId>-->
<!-- <version>3.0.9</version>-->
<!-- <scope>compile</scope>-->
<!-- </dependency>-->
</dependencies> </dependencies>
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.jackson.core.databind.LocalDateTimeDeserialize
import cn.iocoder.yudao.framework.jackson.core.databind.LocalDateTimeSerializer; import cn.iocoder.yudao.framework.jackson.core.databind.LocalDateTimeSerializer;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@@ -38,7 +39,8 @@ public class YudaoJacksonAutoConfiguration {
.addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE); .addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
objectMapper.registerModules(simpleModule); objectMapper.registerModules(simpleModule);
// 序列化枚举
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING,true);
JsonUtils.init(objectMapper); JsonUtils.init(objectMapper);
log.info("初始化 jackson 自动配置"); log.info("初始化 jackson 自动配置");
return bean; return bean;
@@ -8,16 +8,15 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.*;
import springfox.documentation.builders.ExampleBuilder; import springfox.documentation.schema.ModelRef;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestParameterBuilder;
import springfox.documentation.service.*; import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2; import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -50,6 +49,7 @@ public class YudaoSwaggerAutoConfiguration {
return new Docket(DocumentationType.SWAGGER_2) return new Docket(DocumentationType.SWAGGER_2)
// 用来创建该 API 的基本信息,展示在文档的页面中(自定义展示的信息) // 用来创建该 API 的基本信息,展示在文档的页面中(自定义展示的信息)
.apiInfo(apiInfo(properties)) .apiInfo(apiInfo(properties))
.groupName("管理系统")
// 设置扫描指定 package 包下的 // 设置扫描指定 package 包下的
.select() .select()
.apis(basePackage(properties.getBasePackage())) .apis(basePackage(properties.getBasePackage()))
@@ -61,6 +61,93 @@ public class YudaoSwaggerAutoConfiguration {
.securityContexts(securityContexts()); .securityContexts(securityContexts());
} }
@Bean("SWAGGER_ADMIN")
@SuppressWarnings("all")
public Docket createRestApi1() {
SwaggerProperties properties = swaggerProperties();
ParameterBuilder ticketPar = new ParameterBuilder();
List<Parameter> pars = new ArrayList<>();
ticketPar.name("token").description("token")
.modelRef(new ModelRef("string"))
.parameterType("header")
.defaultValue("token " + " ")
.required(true)
.build();
pars.add(ticketPar.build());
return new Docket(DocumentationType.SWAGGER_2)
.groupName("商城后台")
.enable(true)
.apiInfo(apiInfo(properties))
.select()
.apis(RequestHandlerSelectors.basePackage("co.yixiang.modules"))
.paths(PathSelectors.regex("/error.*").negate())
.build()
.globalOperationParameters(pars)
//添加登陆认证
.securitySchemes(securitySchemes())
.globalRequestParameters(globalRequestParameters())
.securityContexts(securityContexts());
}
@Bean("SWAGGER_APP")
@SuppressWarnings("all")
public Docket createAppApi() {
SwaggerProperties properties = swaggerProperties();
ParameterBuilder ticketPar = new ParameterBuilder();
List<Parameter> pars = new ArrayList<>();
ticketPar.name("token").description("token")
.modelRef(new ModelRef("string"))
.parameterType("header")
.defaultValue("token" + " ")
.required(true)
.build();
pars.add(ticketPar.build());
return new Docket(DocumentationType.SWAGGER_2)
.groupName("商城前台")
.enable(true)
.apiInfo(apiInfo(properties))
.select()
.apis(RequestHandlerSelectors.basePackage("co.yixiang.app.modules"))
.paths(PathSelectors.regex("/error.*").negate())
.build()
.globalOperationParameters(pars)
//添加登陆认证
.securitySchemes(securitySchemes())
.globalRequestParameters(globalRequestParameters())
.securityContexts(securityContexts());
}
@Bean("SWAGGER_TOOL")
@SuppressWarnings("all")
public Docket createToolApi() {
SwaggerProperties properties = swaggerProperties();
ParameterBuilder ticketPar = new ParameterBuilder();
List<Parameter> pars = new ArrayList<>();
ticketPar.name("token").description("token")
.modelRef(new ModelRef("string"))
.parameterType("header")
.defaultValue("token" + " ")
.required(true)
.build();
pars.add(ticketPar.build());
return new Docket(DocumentationType.SWAGGER_2)
.groupName("电商工具")
.enable(true)
.apiInfo(apiInfo(properties))
.select()
.apis(RequestHandlerSelectors.basePackage("co.yixiang.tools"))
.paths(PathSelectors.regex("/error.*").negate())
.build()
.globalOperationParameters(pars)
//添加登陆认证
.securitySchemes(securitySchemes())
.globalRequestParameters(globalRequestParameters())
.securityContexts(securityContexts());
}
// ========== apiInfo ========== // ========== apiInfo ==========
/** /**
@@ -3,18 +3,21 @@ package cn.iocoder.yudao.framework.web.core.handler;
import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.extra.servlet.ServletUtil; import cn.hutool.extra.servlet.ServletUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService; import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiErrorLogCreateReqDTO; import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiErrorLogCreateReqDTO;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.framework.common.exception.ShopException;
import cn.iocoder.yudao.framework.common.pojo.ApiResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import io.github.resilience4j.ratelimiter.RequestNotPermitted; import io.github.resilience4j.ratelimiter.RequestNotPermitted;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
//import org.apache.dubbo.rpc.RpcException;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.validation.BindException; import org.springframework.validation.BindException;
@@ -92,6 +95,11 @@ public class GlobalExceptionHandler {
if (ex instanceof AccessDeniedException) { if (ex instanceof AccessDeniedException) {
return accessDeniedExceptionHandler(request, (AccessDeniedException) ex); return accessDeniedExceptionHandler(request, (AccessDeniedException) ex);
} }
// if (ex instanceof RpcException){
// log.error("dubbo错误", ex);
// return null;
// }
return defaultExceptionHandler(request, ex); return defaultExceptionHandler(request, ex);
} }
@@ -215,6 +223,13 @@ public class GlobalExceptionHandler {
return CommonResult.error(ex.getCode(), ex.getMessage()); return CommonResult.error(ex.getCode(), ex.getMessage());
} }
@ExceptionHandler(value = ShopException.class)
public ApiResult<?> yshopException(ShopException ex){
log.info("[YshopException]",ex);
return ApiResult.fail(ex.getErrorCode(),ex.getMessage());
//return CommonResult.error(ex.getErrorCode(), ex.getMessage());
}
/** /**
* 处理系统异常,兜底处理所有的一切 * 处理系统异常,兜底处理所有的一切
*/ */
@@ -9,8 +9,6 @@ import cn.smallbun.screw.core.engine.EngineFileType;
import cn.smallbun.screw.core.engine.EngineTemplateType; import cn.smallbun.screw.core.engine.EngineTemplateType;
import cn.smallbun.screw.core.execute.DocumentationExecute; import cn.smallbun.screw.core.execute.DocumentationExecute;
import cn.smallbun.screw.core.process.ProcessConfig; import cn.smallbun.screw.core.process.ProcessConfig;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
@@ -32,9 +30,6 @@ import java.util.Arrays;
@RequestMapping("/infra/db-doc") @RequestMapping("/infra/db-doc")
public class DbDocController { public class DbDocController {
@Resource
private DynamicDataSourceProperties dynamicDataSourceProperties;
private static final String FILE_OUTPUT_DIR = System.getProperty("java.io.tmpdir") + File.separator private static final String FILE_OUTPUT_DIR = System.getProperty("java.io.tmpdir") + File.separator
+ "db-doc"; + "db-doc";
private static final String DOC_FILE_NAME = "数据库文档"; private static final String DOC_FILE_NAME = "数据库文档";
@@ -119,13 +114,11 @@ public class DbDocController {
// TODO 芋艿:screw 暂时不支持 druid,尴尬 // TODO 芋艿:screw 暂时不支持 druid,尴尬
private HikariDataSource buildDataSource() { private HikariDataSource buildDataSource() {
// 获得 DataSource 数据源,目前只支持首个 // 获得 DataSource 数据源,目前只支持首个
String primary = dynamicDataSourceProperties.getPrimary();
DataSourceProperty dataSourceProperty = dynamicDataSourceProperties.getDatasource().get(primary);
// 创建 HikariConfig 配置类 // 创建 HikariConfig 配置类
HikariConfig hikariConfig = new HikariConfig(); HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(dataSourceProperty.getUrl()); hikariConfig.setJdbcUrl("");
hikariConfig.setUsername(dataSourceProperty.getUsername()); hikariConfig.setUsername("");
hikariConfig.setPassword(dataSourceProperty.getPassword()); hikariConfig.setPassword("");
hikariConfig.addDataSourceProperty("useInformationSchema", "true"); // 设置可以获取 tables remarks 信息 hikariConfig.addDataSourceProperty("useInformationSchema", "true"); // 设置可以获取 tables remarks 信息
// 创建数据源 // 创建数据源
return new HikariDataSource(hikariConfig); return new HikariDataSource(hikariConfig);
@@ -1,27 +1,61 @@
package cn.iocoder.yudao.module.infra.framework.security.config; package cn.iocoder.yudao.module.infra.framework.security.config;
import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer; import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
import cn.iocoder.yudao.framework.security.core.annotations.AuthCheck;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/** /**
* Infra 模块的 Security 配置 * Infra 模块的 Security 配置
*/ */
@Configuration("infraSecurityConfiguration") @Configuration("infraSecurityConfiguration")
@Slf4j
public class SecurityConfiguration { public class SecurityConfiguration {
@Value("${spring.boot.admin.context-path:''}") @Value("${spring.boot.admin.context-path:''}")
private String adminSeverContextPath; private String adminSeverContextPath;
@Resource
private ApplicationContext applicationContext;
@Bean("infraAuthorizeRequestsCustomizer") @Bean("infraAuthorizeRequestsCustomizer")
public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
return new AuthorizeRequestsCustomizer() { return new AuthorizeRequestsCustomizer() {
@Override @Override
public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) { public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
// 查找全部宝象购的链接
Map<RequestMappingInfo, HandlerMethod> handlerMethods = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
Set<String> anonymousUrls = new HashSet<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethods.entrySet()) {
HandlerMethod handlerMethod = infoEntry.getValue();
// 宝象购app包下 并且没有登录标识 放行
if (handlerMethod.getBeanType().getPackage().getName().startsWith("co.yixiang.app")
&& !handlerMethod.hasMethodAnnotation(AuthCheck.class)){
PatternsRequestCondition requestCondition = infoEntry.getKey().getPatternsCondition();
Optional.ofNullable(requestCondition).orElseThrow(RuntimeException::new);
anonymousUrls.addAll(requestCondition.getPatterns());
}
}
// anonymousUrls.forEach(s -> log.info("宝象购可以匿名访问的url{}", s));
registry.antMatchers(anonymousUrls.toArray(new String[0])).anonymous();
// Swagger 接口文档 // Swagger 接口文档
registry.antMatchers("/swagger-ui.html").anonymous() registry.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous() .antMatchers("/swagger-resources/**").anonymous()
@@ -32,6 +66,8 @@ public class SecurityConfiguration {
.antMatchers("/actuator/**").anonymous(); .antMatchers("/actuator/**").anonymous();
// Druid 监控 // Druid 监控
registry.antMatchers("/druid/**").anonymous(); registry.antMatchers("/druid/**").anonymous();
// 首页
registry.antMatchers("/").anonymous();
// Spring Boot Admin Server 的安全配置 // Spring Boot Admin Server 的安全配置
registry.antMatchers(adminSeverContextPath).anonymous() registry.antMatchers(adminSeverContextPath).anonymous()
.antMatchers(adminSeverContextPath + "/**").anonymous(); .antMatchers(adminSeverContextPath + "/**").anonymous();
@@ -9,8 +9,8 @@
文档可见:https://www.iocoder.cn/MyBatis/x-plugins/ 文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
--> -->
<select id="selectList2" resultType="TestDemoDO"> <!-- <select id="selectList2" resultType="TestDemoDO">-->
SELECT * FROM infra_test_demo <!-- SELECT * FROM infra_test_demo-->
</select> <!-- </select>-->
</mapper> </mapper>
@@ -14,7 +14,7 @@ import lombok.NoArgsConstructor;
@Builder @Builder
public class AppAuthLoginRespVO { public class AppAuthLoginRespVO {
@ApiModelProperty(value = "token", required = true, example = "yudaoyuanma") @ApiModelProperty(value = "token", required = true, example = "zsw")
private String token; private String token;
} }
@@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.member.controller.app.weixin;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TestConfigure {
@Bean
public WxMpService wxMpService(){
WxMpService wxMpService = new WxMpServiceImpl();
return wxMpService;
}
}
@@ -68,7 +68,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
@Resource @Resource
private PasswordEncoder passwordEncoder; private PasswordEncoder passwordEncoder;
@Resource @Resource
private MemberUserMapper userMapper; private MemberUserMapper memberUserMapper;
@Override @Override
public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
@@ -286,7 +286,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword()); MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword());
// 更新用户密码 // 更新用户密码
userMapper.updateById(MemberUserDO.builder().id(userDO.getId()) memberUserMapper.updateById(MemberUserDO.builder().id(userDO.getId())
.password(passwordEncoder.encode(reqVO.getPassword())).build()); .password(passwordEncoder.encode(reqVO.getPassword())).build());
} }
@@ -300,7 +300,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
getClientIP())); getClientIP()));
// 更新密码 // 更新密码
userMapper.updateById(MemberUserDO.builder().id(userDO.getId()) memberUserMapper.updateById(MemberUserDO.builder().id(userDO.getId())
.password(passwordEncoder.encode(reqVO.getPassword())).build()); .password(passwordEncoder.encode(reqVO.getPassword())).build());
} }
@@ -319,7 +319,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
*/ */
@VisibleForTesting @VisibleForTesting
public MemberUserDO checkOldPassword(Long id, String oldPassword) { public MemberUserDO checkOldPassword(Long id, String oldPassword) {
MemberUserDO user = userMapper.selectById(id); MemberUserDO user = memberUserMapper.selectById(id);
if (user == null) { if (user == null) {
throw exception(USER_NOT_EXISTS); throw exception(USER_NOT_EXISTS);
} }
@@ -331,7 +331,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
} }
public MemberUserDO checkUserIfExists(String mobile) { public MemberUserDO checkUserIfExists(String mobile) {
MemberUserDO user = userMapper.selectByMobile(mobile); MemberUserDO user = memberUserMapper.selectByMobile(mobile);
if (user == null) { if (user == null) {
throw exception(USER_NOT_EXISTS); throw exception(USER_NOT_EXISTS);
} }
@@ -1,137 +1,137 @@
package cn.iocoder.yudao.module.member.service.user; //package cn.iocoder.yudao.module.member.service.user;
//
import cn.hutool.core.util.RandomUtil; //import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; //import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; //import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; //import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; //import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
import cn.iocoder.yudao.module.infra.api.file.FileApi; //import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO; //import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO;
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; //import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper; //import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
import cn.iocoder.yudao.module.member.service.auth.MemberAuthServiceImpl; //import cn.iocoder.yudao.module.member.service.auth.MemberAuthServiceImpl;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; //import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import org.junit.jupiter.api.Test; //import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean; //import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import; //import org.springframework.context.annotation.Import;
import org.springframework.data.redis.core.StringRedisTemplate; //import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder; //import org.springframework.security.crypto.password.PasswordEncoder;
//
import javax.annotation.Resource; //import javax.annotation.Resource;
import java.io.ByteArrayInputStream; //import java.io.ByteArrayInputStream;
import java.util.function.Consumer; //import java.util.function.Consumer;
//
import static cn.hutool.core.util.RandomUtil.*; //import static cn.hutool.core.util.RandomUtil.*;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; //import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; //import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.assertEquals; //import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.eq; //import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.when; //import static org.mockito.Mockito.when;
//
// TODO @芋艿:单测的 review,等逻辑都达成一致后 //// TODO @芋艿:单测的 review,等逻辑都达成一致后
/** ///**
* {@link MemberUserServiceImpl} 的单元测试类 // * {@link MemberUserServiceImpl} 的单元测试类
* // *
* @author 宋天 // * @author 宋天
*/ // */
@Import({MemberUserServiceImpl.class, YudaoRedisAutoConfiguration.class}) //@Import({MemberUserServiceImpl.class, YudaoRedisAutoConfiguration.class})
public class MemberUserServiceImplTest extends BaseDbAndRedisUnitTest { //public class MemberUserServiceImplTest extends BaseDbAndRedisUnitTest {
//
@Resource // @Resource
private MemberUserServiceImpl memberUserService; // private MemberUserServiceImpl memberUserService;
//
@Resource // @Resource
private StringRedisTemplate stringRedisTemplate; // private StringRedisTemplate stringRedisTemplate;
//
@Resource // @Resource
private MemberUserMapper userMapper; // private MemberUserMapper userMapper;
//
@MockBean // @MockBean
private MemberAuthServiceImpl authService; // private MemberAuthServiceImpl authService;
//
@MockBean // @MockBean
private PasswordEncoder passwordEncoder; // private PasswordEncoder passwordEncoder;
//
@MockBean // @MockBean
private SmsCodeApi smsCodeApi; // private SmsCodeApi smsCodeApi;
@MockBean // @MockBean
private FileApi fileApi; // private FileApi fileApi;
//
@Test // @Test
public void testUpdateNickName_success(){ // public void testUpdateNickName_success(){
// mock 数据 // // mock 数据
MemberUserDO userDO = randomUserDO(); // MemberUserDO userDO = randomUserDO();
userMapper.insert(userDO); // userMapper.insert(userDO);
//
// 随机昵称 // // 随机昵称
String newNickName = randomString(); // String newNickName = randomString();
//
// 调用接口修改昵称 // // 调用接口修改昵称
memberUserService.updateUserNickname(userDO.getId(),newNickName); // memberUserService.updateUserNickname(userDO.getId(),newNickName);
// 查询新修改后的昵称 // // 查询新修改后的昵称
String nickname = memberUserService.getUser(userDO.getId()).getNickname(); // String nickname = memberUserService.getUser(userDO.getId()).getNickname();
// 断言 // // 断言
assertEquals(newNickName,nickname); // assertEquals(newNickName,nickname);
} // }
//
@Test // @Test
public void testUpdateAvatar_success() throws Exception { // public void testUpdateAvatar_success() throws Exception {
// mock 数据 // // mock 数据
MemberUserDO dbUser = randomUserDO(); // MemberUserDO dbUser = randomUserDO();
userMapper.insert(dbUser); // userMapper.insert(dbUser);
//
// 准备参数 // // 准备参数
Long userId = dbUser.getId(); // Long userId = dbUser.getId();
byte[] avatarFileBytes = randomBytes(10); // byte[] avatarFileBytes = randomBytes(10);
ByteArrayInputStream avatarFile = new ByteArrayInputStream(avatarFileBytes); // ByteArrayInputStream avatarFile = new ByteArrayInputStream(avatarFileBytes);
// mock 方法 // // mock 方法
String avatar = randomString(); // String avatar = randomString();
when(fileApi.createFile(eq(avatarFileBytes))).thenReturn(avatar); // when(fileApi.createFile(eq(avatarFileBytes))).thenReturn(avatar);
// 调用 // // 调用
String str = memberUserService.updateUserAvatar(userId, avatarFile); // String str = memberUserService.updateUserAvatar(userId, avatarFile);
// 断言 // // 断言
assertEquals(avatar, str); // assertEquals(avatar, str);
} // }
//
@Test // @Test
public void updateMobile_success(){ // public void updateMobile_success(){
// mock数据 // // mock数据
String oldMobile = randomNumbers(11); // String oldMobile = randomNumbers(11);
MemberUserDO userDO = randomUserDO(); // MemberUserDO userDO = randomUserDO();
userDO.setMobile(oldMobile); // userDO.setMobile(oldMobile);
userMapper.insert(userDO); // userMapper.insert(userDO);
//
// TODO 芋艿:需要修复该单元测试,重构多模块带来的 // // TODO 芋艿:需要修复该单元测试,重构多模块带来的
// 旧手机和旧验证码 // // 旧手机和旧验证码
// SmsCodeDO codeDO = new SmsCodeDO(); //// SmsCodeDO codeDO = new SmsCodeDO();
String oldCode = RandomUtil.randomString(4); // String oldCode = RandomUtil.randomString(4);
// codeDO.setMobile(userDO.getMobile()); //// codeDO.setMobile(userDO.getMobile());
// codeDO.setCode(oldCode); //// codeDO.setCode(oldCode);
// codeDO.setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()); //// codeDO.setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene());
// codeDO.setUsed(Boolean.FALSE); //// codeDO.setUsed(Boolean.FALSE);
// when(smsCodeService.checkCodeIsExpired(codeDO.getMobile(),codeDO.getCode(),codeDO.getScene())).thenReturn(codeDO); //// when(smsCodeService.checkCodeIsExpired(codeDO.getMobile(),codeDO.getCode(),codeDO.getScene())).thenReturn(codeDO);
//
// 更新手机号 // // 更新手机号
String newMobile = randomNumbers(11); // String newMobile = randomNumbers(11);
String newCode = randomNumbers(4); // String newCode = randomNumbers(4);
AppUserUpdateMobileReqVO reqVO = new AppUserUpdateMobileReqVO(); // AppUserUpdateMobileReqVO reqVO = new AppUserUpdateMobileReqVO();
reqVO.setMobile(newMobile); // reqVO.setMobile(newMobile);
reqVO.setCode(newCode); // reqVO.setCode(newCode);
reqVO.setOldMobile(oldMobile); // reqVO.setOldMobile(oldMobile);
reqVO.setOldCode(oldCode); // reqVO.setOldCode(oldCode);
memberUserService.updateUserMobile(userDO.getId(),reqVO); // memberUserService.updateUserMobile(userDO.getId(),reqVO);
//
assertEquals(memberUserService.getUser(userDO.getId()).getMobile(),newMobile); // assertEquals(memberUserService.getUser(userDO.getId()).getMobile(),newMobile);
} // }
//
// ========== 随机对象 ========== // // ========== 随机对象 ==========
//
@SafeVarargs // @SafeVarargs
private static MemberUserDO randomUserDO(Consumer<MemberUserDO>... consumers) { // private static MemberUserDO randomUserDO(Consumer<MemberUserDO>... consumers) {
Consumer<MemberUserDO> consumer = (o) -> { // Consumer<MemberUserDO> consumer = (o) -> {
o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 // o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
}; // };
return randomPojo(MemberUserDO.class, ArrayUtils.append(consumer, consumers)); // return randomPojo(MemberUserDO.class, ArrayUtils.append(consumer, consumers));
} // }
//
} //}
@@ -123,4 +123,6 @@ public interface ErrorCodeConstants {
ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1002019000, "系统敏感词在所有标签中都不存在"); ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1002019000, "系统敏感词在所有标签中都不存在");
ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1002019001, "系统敏感词已在标签中存在"); ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1002019001, "系统敏感词已在标签中存在");
ErrorCode CP_USER_NOT_EXISTS = new ErrorCode(1002021001,"微信企业成员不存在");
} }
@@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.system.enums.wxcp;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum WxCpMsgTypeEnum {
project,
task;
}
@@ -97,6 +97,11 @@
<artifactId>yudao-spring-boot-starter-excel</artifactId> <artifactId>yudao-spring-boot-starter-excel</artifactId>
</dependency> </dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-weixin</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>
@@ -0,0 +1,100 @@
package cn.iocoder.yudao.module.system.controller.admin.CpUser;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.system.controller.admin.CpUser.vo.*;
import cn.iocoder.yudao.module.system.dal.dataobject.CpUser.CpUserDO;
import cn.iocoder.yudao.module.system.convert.CpUser.CpUserConvert;
import cn.iocoder.yudao.module.system.service.CpUser.CpUserService;
@Api(tags = "管理后台 - 企业微信成员")
@RestController
@RequestMapping("/system/cp-user")
@Validated
public class CpUserController {
@Resource
private CpUserService cpUserService;
@PostMapping("/create")
@ApiOperation("创建企业微信成员")
@PreAuthorize("@ss.hasPermission('system:cp-user:create')")
public CommonResult<Long> createCpUser(@Valid @RequestBody CpUserCreateReqVO createReqVO) {
return success(cpUserService.createCpUser(createReqVO));
}
@PutMapping("/update")
@ApiOperation("更新企业微信成员")
@PreAuthorize("@ss.hasPermission('system:cp-user:update')")
public CommonResult<Boolean> updateCpUser(@Valid @RequestBody CpUserUpdateReqVO updateReqVO) {
cpUserService.updateCpUser(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除企业微信成员")
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('system:cp-user:delete')")
public CommonResult<Boolean> deleteCpUser(@RequestParam("id") Long id) {
cpUserService.deleteCpUser(id);
return success(true);
}
@GetMapping("/get")
@ApiOperation("获得企业微信成员")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('system:cp-user:query')")
public CommonResult<CpUserRespVO> getCpUser(@RequestParam("id") Long id) {
CpUserDO cpUser = cpUserService.getCpUser(id);
return success(CpUserConvert.INSTANCE.convert(cpUser));
}
@GetMapping("/list")
@ApiOperation("获得企业微信成员列表")
@ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
@PreAuthorize("@ss.hasPermission('system:cp-user:query')")
public CommonResult<List<CpUserRespVO>> getCpUserList(@RequestParam("ids") Collection<Long> ids) {
List<CpUserDO> list = cpUserService.getCpUserList(ids);
return success(CpUserConvert.INSTANCE.convertList(list));
}
@GetMapping("/page")
@ApiOperation("获得企业微信成员分页")
@PreAuthorize("@ss.hasPermission('system:cp-user:query')")
public CommonResult<PageResult<CpUserRespVO>> getCpUserPage(@Valid CpUserPageReqVO pageVO) {
PageResult<CpUserDO> pageResult = cpUserService.getCpUserPage(pageVO);
return success(CpUserConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/export-excel")
@ApiOperation("导出企业微信成员 Excel")
@PreAuthorize("@ss.hasPermission('system:cp-user:export')")
@OperateLog(type = EXPORT)
public void exportCpUserExcel(@Valid CpUserExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<CpUserDO> list = cpUserService.getCpUserList(exportReqVO);
// 导出 Excel
List<CpUserExcelVO> datas = CpUserConvert.INSTANCE.convertList02(list);
ExcelUtils.write(response, "企业微信成员.xls", "数据", CpUserExcelVO.class, datas);
}
}
@@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.system.controller.admin.CpUser.dto;
import cn.iocoder.yudao.module.system.enums.wxcp.WxCpMsgTypeEnum;
import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
@Data
@ApiModel("企业微信发送推送消息")
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CpMessageDto {
private WxCpMsgTypeEnum typeEnum;
private Long id;
// 标题
private String title;
// 子标题
private String description;
private Map<String,String> contentItems;
// 是否放大第一对item
private Boolean emphasisFirstItem;
/**
* 用于微信消息推送的 - 模板ID 不一定适用于企业微信
*/
private String templateId;
/**
* 接收人userid
*/
private List<String> reciveIds;
}
@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.system.controller.admin.CpUser.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
/**
* 企业微信成员 Base VO,提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class CpUserBaseVO {
@ApiModelProperty(value = "用户昵称", required = true)
@NotNull(message = "用户昵称不能为空")
private String userid;
@ApiModelProperty(value = "头像", required = true)
@NotNull(message = "头像不能为空")
private String avatar;
@ApiModelProperty(value = "状态", required = true)
@NotNull(message = "状态不能为空")
private Integer status;
@ApiModelProperty(value = "手机号", required = true)
@NotNull(message = "手机号不能为空")
private String mobile;
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value = "部门")
private Object department;
@ApiModelProperty(value = "职位")
private String position;
@ApiModelProperty(value = "邮件")
private String email;
@ApiModelProperty(value = "企业邮件")
private String bizMail;
@ApiModelProperty(value = "工时")
private Integer workHour;
}
@@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.system.controller.admin.CpUser.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
@ApiModel("管理后台 - 企业微信成员创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CpUserCreateReqVO extends CpUserBaseVO {
}
@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.system.controller.admin.CpUser.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import com.alibaba.excel.annotation.ExcelProperty;
/**
* 企业微信成员 Excel VO
*
* @author 系统管理员
*/
@Data
public class CpUserExcelVO {
@ExcelProperty("编号")
private Long id;
@ExcelProperty("用户昵称")
private String userid;
@ExcelProperty("头像")
private String avatar;
@ExcelProperty("状态")
private Integer status;
@ExcelProperty("手机号")
private String mobile;
@ExcelProperty("创建时间")
private Date createTime;
@ExcelProperty("姓名")
private String name;
@ExcelProperty("部门")
private Object department;
@ExcelProperty("职位")
private String position;
@ExcelProperty("邮件")
private String email;
@ExcelProperty("企业邮件")
private String bizMail;
@ApiModelProperty(value = "工时")
private Integer workHour;
}
@@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.system.controller.admin.CpUser.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel(value = "管理后台 - 企业微信成员 Excel 导出 Request VO", description = "参数和 CpUserPageReqVO 是一致的")
@Data
public class CpUserExportReqVO {
@ApiModelProperty(value = "用户昵称")
private String userid;
@ApiModelProperty(value = "头像")
private String avatar;
@ApiModelProperty(value = "状态")
private Integer status;
@ApiModelProperty(value = "手机号")
private String mobile;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "开始创建时间")
private Date beginCreateTime;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "结束创建时间")
private Date endCreateTime;
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value = "部门")
private Object department;
@ApiModelProperty(value = "职位")
private String position;
@ApiModelProperty(value = "邮件")
private String email;
@ApiModelProperty(value = "企业邮件")
private String bizMail;
@ApiModelProperty(value = "工时")
private Integer workHour;
}
@@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.system.controller.admin.CpUser.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel("管理后台 - 企业微信成员分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CpUserPageReqVO extends PageParam {
@ApiModelProperty(value = "用户昵称")
private String userid;
@ApiModelProperty(value = "头像")
private String avatar;
@ApiModelProperty(value = "状态")
private Integer status;
@ApiModelProperty(value = "手机号")
private String mobile;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "开始创建时间")
private Date beginCreateTime;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "结束创建时间")
private Date endCreateTime;
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value = "部门")
private Object department;
@ApiModelProperty(value = "职位")
private String position;
@ApiModelProperty(value = "邮件")
private String email;
@ApiModelProperty(value = "企业邮件")
private String bizMail;
@ApiModelProperty(value = "工时")
private Integer workHour;
}
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.system.controller.admin.CpUser.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
@ApiModel("管理后台 - 企业微信成员 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CpUserRespVO extends CpUserBaseVO {
@ApiModelProperty(value = "编号", required = true)
private Long id;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
}
@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.system.controller.admin.CpUser.vo;
import lombok.*;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
@ApiModel("管理后台 - 企业微信成员更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CpUserUpdateReqVO extends CpUserBaseVO {
@ApiModelProperty(value = "编号", required = true)
@NotNull(message = "编号不能为空")
private Long id;
}
@@ -1,17 +1,32 @@
package cn.iocoder.yudao.module.system.controller.admin.auth; package cn.iocoder.yudao.module.system.controller.admin.auth;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils; import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.module.system.controller.admin.auth.param.WxCpLoginDto;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserCreateReqVO;
import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
import cn.iocoder.yudao.module.system.convert.user.UserConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.CpUser.CpUserDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper;
import cn.iocoder.yudao.module.system.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum; import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
import cn.iocoder.yudao.module.system.service.CpUser.CpUserService;
import cn.iocoder.yudao.module.system.service.auth.AdminAuthService; import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
import cn.iocoder.yudao.module.system.service.auth.AdminAuthServiceImpl;
import cn.iocoder.yudao.module.system.service.auth.UserSessionService;
import cn.iocoder.yudao.module.system.service.permission.PermissionService; import cn.iocoder.yudao.module.system.service.permission.PermissionService;
import cn.iocoder.yudao.module.system.service.permission.RoleService; import cn.iocoder.yudao.module.system.service.permission.RoleService;
import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.social.SocialUserService;
@@ -21,6 +36,12 @@ import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -46,11 +67,29 @@ public class AuthController {
@Resource @Resource
private AdminUserService userService; private AdminUserService userService;
@Resource @Resource
private AdminUserMapper adminUserMapper;
@Resource
private RoleService roleService; private RoleService roleService;
@Resource @Resource
private PermissionService permissionService; private PermissionService permissionService;
@Resource @Resource
private SocialUserService socialUserService; private SocialUserService socialUserService;
@Resource
private CpUserService cpUserService;
@Resource
private WxCpService wxCpService;
@Resource
private UserSessionService userSessionService;
@Resource
private AdminAuthServiceImpl adminAuthService;
@Resource
private UserRoleMapper userRoleMapper;
@Resource
private PasswordEncoder passwordEncoder;
@Resource
private WxMaService wxMaService;
@PostMapping("/login") @PostMapping("/login")
@ApiOperation("使用账号密码登录") @ApiOperation("使用账号密码登录")
@@ -61,6 +100,69 @@ public class AuthController {
return success(AuthLoginRespVO.builder().token(token).build()); return success(AuthLoginRespVO.builder().token(token).build());
} }
@PostMapping("/loginByCp")
@ApiOperation("微信小程序登录")
public CommonResult<AuthLoginRespVO> loginByMxApp(@RequestBody @Valid WxCpLoginDto dto) throws WxErrorException {
CpUserDO cpuser;
// 企业微信登录
if ("wxcp".equals(dto.getCode())){
cpuser = cpUserService.getByUserId("test");
}else{
if (dto.getPlatform().equalsIgnoreCase("WX")){
//普通微信
WxMaJscode2SessionResult result = wxMaService.jsCode2SessionInfo(dto.getCode());
log.info("微信登录信息,{}", result);
cpuser = cpUserService.getByUserId(result.getUnionid());
if (ObjectUtil.isEmpty(cpuser)){
cpuser = cpUserService.getByUserId("test");
}
}else{
//企业微信
WxCpMaJsCode2SessionResult session = wxCpService.jsCode2Session(dto.getCode());
log.info("企业微信登录信息:{}", session);
cpuser = cpUserService.getByUserId(session.getUserId());
}
}
if (ObjectUtil.isEmpty(cpuser)){
return CommonResult.error(ErrorCodeConstants.USER_NOT_EXISTS.getCode(),"未发现绑定的企业用户,暂时无法登录,请联系管理员");
}
AdminUserDO user = userService.getUserByUsername(cpuser.getUserId());
if (ObjectUtil.isEmpty(user)){
//第一次登录 注册一个系统用户
user = AdminUserDO.builder()
.username(cpuser.getUserId())
.nickname(cpuser.getName())
.cpUserId(cpuser.getUserId())
.password((passwordEncoder.encode("123456")))
.cpUserId(cpuser.getUserId())
.build();
user.setTenantId(1L);
adminUserMapper.insert(user);
//设置默认角色 农场人员
UserRoleDO userRole = new UserRoleDO();
userRole.setUserId(user.getId());
userRole.setRoleId(112L);
userRole.setTenantId(1L);
userRoleMapper.insert(userRole);
}else {
user.setCpUserId(cpuser.getUserId());
userService.updateUser(UserConvert.INSTANCE.convertFromEntity(user));
}
LoginUser login = AuthConvert.INSTANCE.convert(user);
//权限
login.setRoleIds(adminAuthService.getUserRoleIds(login.getId()));
String token = userSessionService.createUserSession(login, getClientIP(), getUserAgent());
log.info("{}:TOKEN:::{}",user.getUsername(),token);
AuthLoginRespVO vo = AuthLoginRespVO.builder().token(token).user(cpuser).build();
return CommonResult.success(vo);
}
@GetMapping("/get-permission-info") @GetMapping("/get-permission-info")
@ApiOperation("获取登录用户的权限信息") @ApiOperation("获取登录用户的权限信息")
public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() { public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {
@@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.system.controller.admin.auth.param;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
@Data
public class WxCpLoginDto {
@NotEmpty(message = "CODE不能为空")
private String code;
private String platform;
}
@@ -18,7 +18,7 @@ import javax.validation.constraints.Pattern;
@Builder @Builder
public class AuthLoginReqVO { public class AuthLoginReqVO {
@ApiModelProperty(value = "账号", required = true, example = "yudaoyuanma") @ApiModelProperty(value = "账号", required = true, example = "zsw")
@NotEmpty(message = "登录账号不能为空") @NotEmpty(message = "登录账号不能为空")
@Length(min = 4, max = 16, message = "账号长度为 4-16 位") @Length(min = 4, max = 16, message = "账号长度为 4-16 位")
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母") @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
@@ -37,6 +37,8 @@ public class AuthLoginReqVO {
@NotEmpty(message = "唯一标识不能为空", groups = CodeEnableGroup.class) @NotEmpty(message = "唯一标识不能为空", groups = CodeEnableGroup.class)
private String uuid; private String uuid;
private String platform;
/** /**
* 开启验证码的 Group * 开启验证码的 Group
*/ */
@@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth; package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth;
import cn.iocoder.yudao.module.system.dal.dataobject.CpUser.CpUserDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -14,7 +16,8 @@ import lombok.NoArgsConstructor;
@Builder @Builder
public class AuthLoginRespVO { public class AuthLoginRespVO {
@ApiModelProperty(value = "token", required = true, example = "yudaoyuanma") @ApiModelProperty(value = "token", required = true, example = "zsw")
private String token; private String token;
private CpUserDO user;
} }
@@ -34,6 +34,9 @@ public class AuthMenuRespVO {
@ApiModelProperty(value = "菜单图标", example = "/menu/list", notes = "仅菜单类型为菜单或者目录时,才需要传") @ApiModelProperty(value = "菜单图标", example = "/menu/list", notes = "仅菜单类型为菜单或者目录时,才需要传")
private String icon; private String icon;
@ApiModelProperty(value = "是否隐藏")
private Boolean hidden;
/** /**
* 子路由 * 子路由
*/ */
@@ -34,7 +34,7 @@ public class AuthSocialLogin2ReqVO {
@NotEmpty(message = "state 不能为空") @NotEmpty(message = "state 不能为空")
private String state; private String state;
@ApiModelProperty(value = "账号", required = true, example = "yudaoyuanma") @ApiModelProperty(value = "账号", required = true, example = "zsw")
@NotEmpty(message = "登录账号不能为空") @NotEmpty(message = "登录账号不能为空")
@Length(min = 4, max = 16, message = "账号长度为 4-16 位") @Length(min = 4, max = 16, message = "账号长度为 4-16 位")
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母") @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
@@ -50,4 +50,6 @@ public class MenuBaseVO {
@NotNull(message = "状态不能为空") @NotNull(message = "状态不能为空")
private Integer status; private Integer status;
private Boolean hidden;
} }
@@ -51,4 +51,7 @@ public class UserBaseVO {
@ApiModelProperty(value = "用户头像", example = "https://www.iocoder.cn/xxx.png") @ApiModelProperty(value = "用户头像", example = "https://www.iocoder.cn/xxx.png")
private String avatar; private String avatar;
@ApiModelProperty("企业微信userid")
private String cpUserId;
} }
@@ -28,4 +28,6 @@ public class UserRespVO extends UserBaseVO {
@ApiModelProperty(value = "创建时间", required = true, example = "时间戳格式") @ApiModelProperty(value = "创建时间", required = true, example = "时间戳格式")
private Date createTime; private Date createTime;
@ApiModelProperty("企业微信userid")
private String cpUserId;
} }
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.system.convert.CpUser;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import me.chanjar.weixin.cp.bean.WxCpUser;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import cn.iocoder.yudao.module.system.controller.admin.CpUser.vo.*;
import cn.iocoder.yudao.module.system.dal.dataobject.CpUser.CpUserDO;
/**
* 企业微信成员 Convert
*
* @author 系统管理员
*/
@Mapper
public interface CpUserConvert {
CpUserConvert INSTANCE = Mappers.getMapper(CpUserConvert.class);
CpUserDO convert(CpUserCreateReqVO bean);
CpUserDO convert(CpUserUpdateReqVO bean);
CpUserRespVO convert(CpUserDO bean);
List<CpUserRespVO> convertList(List<CpUserDO> list);
PageResult<CpUserRespVO> convertPage(PageResult<CpUserDO> page);
List<CpUserExcelVO> convertList02(List<CpUserDO> list);
List<CpUserDO> convertListFromWxApi(List<WxCpUser> list);
}
@@ -10,12 +10,15 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.permission.MenuIdEnum; import cn.iocoder.yudao.module.system.enums.permission.MenuIdEnum;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.*; import java.util.*;
import java.util.function.Function;
@Mapper @Mapper
public interface AuthConvert { public interface AuthConvert {
@@ -31,10 +34,18 @@ public interface AuthConvert {
} }
default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) { default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
Set<String> permissions = CollectionUtils.convertSet(menuList, MenuDO::getPermission);
// 兼容了一个菜单多个权限。包含逗号分为多块写入
menuList.forEach(menuDO -> {
if (menuDO.getPermission().contains(",")){
permissions.remove(menuDO.getPermission());
permissions.addAll( Sets.newHashSet(Arrays.asList(menuDO.getPermission().split(","))));
}
});
return AuthPermissionInfoRespVO.builder() return AuthPermissionInfoRespVO.builder()
.user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build()) .user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build())
.roles(CollectionUtils.convertSet(roleList, RoleDO::getCode)) .roles(CollectionUtils.convertSet(roleList, RoleDO::getCode))
.permissions(CollectionUtils.convertSet(menuList, MenuDO::getPermission)) .permissions(permissions)
.build(); .build();
} }
@@ -23,6 +23,8 @@ public interface UserConvert {
UserPageItemRespVO convert(AdminUserDO bean); UserPageItemRespVO convert(AdminUserDO bean);
UserUpdateReqVO convertFromEntity(AdminUserDO bean);
UserPageItemRespVO.Dept convert(DeptDO bean); UserPageItemRespVO.Dept convert(DeptDO bean);
AdminUserDO convert(UserCreateReqVO bean); AdminUserDO convert(UserCreateReqVO bean);
@@ -0,0 +1,67 @@
package cn.iocoder.yudao.module.system.dal.dataobject.CpUser;
import lombok.*;
import java.util.*;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 企业微信成员 DO
*
* @author 系统管理员
*/
@TableName("wxcp_users")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CpUserDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 用户昵称
*/
@TableField(value = "user_id")
private String userId;
/**
* 头像
*/
private String avatar;
/**
* 状态
*/
private Integer status;
/**
* 手机号
*/
private String mobile;
/**
* 姓名
*/
private String name;
/**
* 部门
*/
private Object department;
/**
* 职位
*/
private String position;
/**
* 邮件
*/
private String email;
/**
* 企业邮件
*/
private String bizMail;
private Integer workHour;
}
@@ -73,4 +73,7 @@ public class MenuDO extends BaseDO {
*/ */
private Integer status; private Integer status;
@TableField(exist = false)
private Boolean hidden;
} }
@@ -1,8 +1,10 @@
package cn.iocoder.yudao.module.system.dal.dataobject.permission; package cn.iocoder.yudao.module.system.dal.dataobject.permission;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@@ -14,7 +16,7 @@ import lombok.EqualsAndHashCode;
@TableName("system_user_role") @TableName("system_user_role")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class UserRoleDO extends BaseDO { public class UserRoleDO extends TenantBaseDO {
/** /**
* 自增主键 * 自增主键
@@ -91,4 +91,9 @@ public class AdminUserDO extends TenantBaseDO {
*/ */
private Date loginDate; private Date loginDate;
/**
* 企业微信用户ID
*/
private String cpUserId;
} }
@@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.system.dal.mysql.CpUser;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.system.dal.dataobject.CpUser.CpUserDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.system.controller.admin.CpUser.vo.*;
/**
* 企业微信成员 Mapper
*
* @author 系统管理员
*/
@Mapper
public interface CpUserMapper extends BaseMapperX<CpUserDO> {
default PageResult<CpUserDO> selectPage(CpUserPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<CpUserDO>()
.eqIfPresent(CpUserDO::getUserId, reqVO.getUserid())
.eqIfPresent(CpUserDO::getAvatar, reqVO.getAvatar())
.eqIfPresent(CpUserDO::getStatus, reqVO.getStatus())
.eqIfPresent(CpUserDO::getMobile, reqVO.getMobile())
.betweenIfPresent(CpUserDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
.likeIfPresent(CpUserDO::getName, reqVO.getName())
.eqIfPresent(CpUserDO::getDepartment, reqVO.getDepartment())
.eqIfPresent(CpUserDO::getPosition, reqVO.getPosition())
.eqIfPresent(CpUserDO::getEmail, reqVO.getEmail())
.eqIfPresent(CpUserDO::getBizMail, reqVO.getBizMail())
.orderByDesc(CpUserDO::getId));
}
default List<CpUserDO> selectList(CpUserExportReqVO reqVO) {
return selectList(new LambdaQueryWrapperX<CpUserDO>()
.eqIfPresent(CpUserDO::getUserId, reqVO.getUserid())
.eqIfPresent(CpUserDO::getAvatar, reqVO.getAvatar())
.eqIfPresent(CpUserDO::getStatus, reqVO.getStatus())
.eqIfPresent(CpUserDO::getMobile, reqVO.getMobile())
.betweenIfPresent(CpUserDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
.likeIfPresent(CpUserDO::getName, reqVO.getName())
.eqIfPresent(CpUserDO::getDepartment, reqVO.getDepartment())
.eqIfPresent(CpUserDO::getPosition, reqVO.getPosition())
.eqIfPresent(CpUserDO::getEmail, reqVO.getEmail())
.eqIfPresent(CpUserDO::getBizMail, reqVO.getBizMail())
.orderByDesc(CpUserDO::getId));
}
}
@@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.system.dal.mysql.dict;
import cn.iocoder.yudao.module.system.dict.DictModel;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface DictMapper extends BaseMapper<DictModel> {
List<DictModel> queryTableDictByKeysAndFilterSql(@Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("filterSql") String filterSql, @Param("codeValues") List<String> codeValues);
}
@@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.system.dict;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 字典注解
* @author: dangzhenghui
* @date: 2019年03月17日-下午9:37:16
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dict {
/**
* 方法描述: 数据code
* 作 者: dangzhenghui
* 日 期: 2019年03月17日-下午9:37:16
*
* @return 返回类型: String
*/
String dicCode();
/**
* 方法描述: 数据Text
* 作 者: dangzhenghui
* 日 期: 2019年03月17日-下午9:37:16
*
* @return 返回类型: String
*/
String dicText() default "";
/**
* 方法描述: 数据字典表
* 作 者: dangzhenghui
* 日 期: 2019年03月17日-下午9:37:16
*
* @return 返回类型: String
*/
String dictTable() default "";
}
@@ -0,0 +1,327 @@
package cn.iocoder.yudao.module.system.dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.service.dict.DictDataService;
import com.alibaba.druid.support.json.JSONUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @Description: 字典aop类
* @Author: dangzhenghui
* @Date: 2019-3-17 21:50
* @Version: 1.0
*/
@Aspect
@Component
@Slf4j
public class DictAspect {
@Autowired
private DictDataService dictDataService;
@Autowired
public RedisTemplate redisTemplate;
public static final String DICT_TEXT_SUFFIX = "_dictText";
/**
* 定义切点Pointcut
*/
@Pointcut("execution(public * cn.iocoder.yudao.module.*.controller..*.*Controller.*(..))")
public void excudeService() {
}
@Around("excudeService()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long time1=System.currentTimeMillis();
Object result = pjp.proceed();
long time2=System.currentTimeMillis();
log.debug("获取JSON数据 耗时:"+(time2-time1)+"ms");
long start=System.currentTimeMillis();
this.parseDictText(result);
long end=System.currentTimeMillis();
log.debug("注入字典到JSON数据 耗时"+(end-start)+"ms");
return result;
}
/**
* 本方法针对返回对象为Result 的IPage的分页列表数据进行动态字典注入
* 字典注入实现 通过对实体类添加注解@dict 来标识需要的字典内容,字典分为单字典code即可 table字典 code table text配合使用与原来jeecg的用法相同
* 示例为SysUser 字段为sex 添加了注解@Dict(dicCode = "sex") 会在字典服务立马查出来对应的text 然后在请求list的时候将这个字典text,已字段名称加_dictText形式返回到前端
* 例输入当前返回值的就会多出一个sex_dictText字段
* {
* sex:1,
* sex_dictText:"男"
* }
* 前端直接取值sext_dictText在table里面无需再进行前端的字典转换了
* customRender:function (text) {
* if(text==1){
* return "男";
* }else if(text==2){
* return "女";
* }else{
* return text;
* }
* }
* 目前vue是这么进行字典渲染到table上的多了就很麻烦了 这个直接在服务端渲染完成前端可以直接用
* @param result
*/
private void parseDictText(Object result) {
if (result instanceof CommonResult) {
if (((CommonResult) result).getData() instanceof PageResult) {
List<JSONObject> items = new ArrayList<>();
//step.1 筛选出加了 Dict 注解的字段列表
List<Field> dictFieldList = new ArrayList<>();
// 字典数据列表, key = 字典code,value=数据列表
Map<String, List<String>> dataListMap = new HashMap<>(5);
for (Object record : ((PageResult) ((CommonResult) result).getData()).getList()) {
ObjectMapper mapper = new ObjectMapper();
// 1String json="{}";
String json = JSONUtil.toJsonStr(record);
// try {
// //解决@JsonFormat注解解析不了的问题详见SysAnnouncement类的@JsonFormat
// json = mapper.writeValueAsString(record);
// } catch (JsonProcessingException e) {
// log.error("json解析失败"+e.getMessage(),e);
// }
//update-begin--Author:scott -- Date:20211223 ----for:【issues/3303】restcontroller返回json数据后key顺序错乱 -----
JSONObject item = JSONObject.parseObject(json, Feature.OrderedField);
//update-end--Author:scott -- Date:20211223 ----for:【issues/3303】restcontroller返回json数据后key顺序错乱 -----
//update-begin--Author:scott -- Date:20190603 ----for:解决继承实体字段无法翻译问题------
//for (Field field : record.getClass().getDeclaredFields()) {
// 遍历所有字段,把字典Code取出来,放到 map 里
for (Field field : getAllFields(record)) {
String value = item.getString(field.getName());
if (ObjectUtil.isEmpty(value)) {
continue;
}
//update-end--Author:scott -- Date:20190603 ----for:解决继承实体字段无法翻译问题------
if (field.getAnnotation(Dict.class) != null) {
if (!dictFieldList.contains(field)) {
dictFieldList.add(field);
}
String code = field.getAnnotation(Dict.class).dicCode();
String text = field.getAnnotation(Dict.class).dicText();
String table = field.getAnnotation(Dict.class).dictTable();
List<String> dataList;
String dictCode = code;
if (!StringUtils.isEmpty(table)) {
dictCode = String.format("%s,%s,%s", table, text, code);
}
dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
}
//date类型默认转换string格式化日期
// if (CommonConstant.JAVA_UTIL_DATE.equals(field.getType().getName())&&field.getAnnotation(JsonFormat.class)==null&&item.get(field.getName())!=null){
// SimpleDateFormat aDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName()))));
// }
}
items.add(item);
}
//step.2 调用翻译方法,一次性翻译
Map<String, List<DictModel>> translText = this.translateAllDict(dataListMap);
//step.3 将翻译结果填充到返回结果里
for (JSONObject record : items) {
for (Field field : dictFieldList) {
String code = field.getAnnotation(Dict.class).dicCode();
String text = field.getAnnotation(Dict.class).dicText();
String table = field.getAnnotation(Dict.class).dictTable();
String fieldDictCode = code;
if (!StringUtils.isEmpty(table)) {
fieldDictCode = String.format("%s,%s,%s", table, text, code);
}
String value = record.getString(field.getName());
if (ObjectUtil.isNotEmpty(value)) {
List<DictModel> dictModels = translText.get(fieldDictCode);
if(dictModels==null || dictModels.size()==0){
continue;
}
String textValue = this.translDictText(dictModels, value);
log.debug(" 字典Val : " + textValue);
log.debug(" __翻译字典字段__ " + field.getName() + DICT_TEXT_SUFFIX + " " + textValue);
// TODO-sun 测试输出,待删
log.debug(" ---- dictCode: " + fieldDictCode);
log.debug(" ---- value: " + value);
log.debug(" ----- text: " + textValue);
log.debug(" ---- dictModels: " + JSON.toJSONString(dictModels));
record.put(field.getName() + DICT_TEXT_SUFFIX, textValue);
}
}
}
((PageResult)((CommonResult) result).getData()).setList(items);
}
}
}
/**
* list 去重添加
*/
private void listAddAllDeduplicate(List<String> dataList, List<String> addList) {
// 筛选出dataList中没有的数据
List<String> filterList = addList.stream().filter(i -> !dataList.contains(i)).collect(Collectors.toList());
dataList.addAll(filterList);
}
/**
* 一次性把所有的字典都翻译了
* 1. 所有的普通数据字典的所有数据只执行一次SQL
* 2. 表字典相同的所有数据只执行一次SQL
* @param dataListMap
* @return
*/
private Map<String, List<DictModel>> translateAllDict(Map<String, List<String>> dataListMap) {
// 翻译后的字典文本,key=dictCode
Map<String, List<DictModel>> translText = new HashMap<>(5);
// 需要翻译的数据(有些可以从redis缓存中获取,就不走数据库查询)
List<String> needTranslData = new ArrayList<>();
//step.1 先通过redis中获取缓存字典数据
for (String dictCode : dataListMap.keySet()) {
List<String> dataList = dataListMap.get(dictCode);
if (dataList.size() == 0) {
continue;
}
// 表字典需要翻译的数据
List<String> needTranslDataTable = new ArrayList<>();
for (String s : dataList) {
String data = s.trim();
if (data.length() == 0) {
continue; //跳过循环
}
if (dictCode.contains(",")) {
String keyString = String.format("sys:cache:dictTable::SimpleKey [%s,%s]", dictCode, data);
if (redisTemplate.hasKey(keyString)) {
try {
String text = redisTemplate.opsForValue().get(keyString).toString();
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
list.add(new DictModel(data, text));
} catch (Exception e) {
log.warn(e.getMessage());
}
} else if (!needTranslDataTable.contains(data)) {
// 去重添加
needTranslDataTable.add(data);
}
} else {
String keyString = String.format("sys:cache:dict::%s:%s", dictCode, data);
if (redisTemplate.hasKey(keyString)) {
try {
String text = redisTemplate.opsForValue().get(keyString).toString();
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
list.add(new DictModel(data, text));
} catch (Exception e) {
log.warn(e.getMessage());
}
} else if (!needTranslData.contains(data)) {
// 去重添加
needTranslData.add(data);
}
}
}
//step.2 调用数据库翻译表字典
if (needTranslDataTable.size() > 0) {
String[] arr = dictCode.split(",");
String table = arr[0], text = arr[1], code = arr[2];
String values = String.join(",", needTranslDataTable);
log.info("translateDictFromTableByKeys.dictCode:" + dictCode);
log.info("translateDictFromTableByKeys.values:" + values);
List<DictModel> texts = dictDataService.queryTableDictTextByKeys(table,text,code,needTranslDataTable);
log.info("translateDictFromTableByKeys.result:" + texts);
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
list.addAll(texts);
// 做 redis 缓存
for (DictModel dict : texts) {
String redisKey = String.format("sys:cache:dictTable::SimpleKey [%s,%s]", dictCode, dict.getValue());
try {
// update-begin-author:taoyan date:20211012 for: 字典表翻译注解缓存未更新 issues/3061
// 保留5分钟
redisTemplate.opsForValue().set(redisKey, dict.getText(), 300, TimeUnit.SECONDS);
// update-end-author:taoyan date:20211012 for: 字典表翻译注解缓存未更新 issues/3061
} catch (Exception e) {
log.warn(e.getMessage(), e);
}
}
}
}
//step.3 调用数据库进行翻译普通字典
return translText;
}
/**
* 字典值替换文本
*
* @param dictModels
* @param values
* @return
*/
private String translDictText(List<DictModel> dictModels, String values) {
List<String> result = new ArrayList<>();
// 允许多个逗号分隔,允许传数组对象
String[] splitVal = values.split(",");
for (String val : splitVal) {
String dictText = val;
for (DictModel dict : dictModels) {
if (val.equals(dict.getValue())) {
dictText = dict.getText();
break;
}
}
result.add(dictText);
}
return String.join(",", result);
}
public static Field[] getAllFields(Object object) {
Class<?> clazz = object.getClass();
List<Field> fieldList = new ArrayList<>();
while (clazz != null) {
fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
clazz = clazz.getSuperclass();
}
Field[] fields = new Field[fieldList.size()];
fieldList.toArray(fields);
return fields;
}
}
@@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.system.dict;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* @Description: 字典类
* @author: jeecg-boot
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@JsonIgnoreProperties(ignoreUnknown = true)
public class DictModel implements Serializable{
private static final long serialVersionUID = 1L;
public DictModel() {
}
public DictModel(String value, String text) {
this.value = value;
this.text = text;
}
/**
* 字典value
*/
private String value;
/**
* 字典文本
*/
private String text;
/**
* 特殊用途: JgEditableTable
* @return
*/
public String getTitle() {
return this.text;
}
/**
* 特殊用途: vue3 Select组件
*/
public String getLabel() {
return this.text;
}
}
@@ -26,6 +26,10 @@ public class SecurityConfiguration {
registry.antMatchers(buildAdminApi("/system/tenant/get-id-by-name")).anonymous(); registry.antMatchers(buildAdminApi("/system/tenant/get-id-by-name")).anonymous();
// 短信回调 API // 短信回调 API
registry.antMatchers(buildAdminApi("/system/sms/callback/**")).anonymous(); registry.antMatchers(buildAdminApi("/system/sms/callback/**")).anonymous();
// 企业微信登录
registry.antMatchers(buildAdminApi("/system/loginByCp")).anonymous();
// 第三方登录
registry.antMatchers(buildAdminApi("/system/social-auth-redirect")).anonymous();
} }
}; };
@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.system.job.cpuser;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.system.service.CpUser.CpUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
@Slf4j
public class CpWeixinUserSyncJob implements JobHandler {
@Resource
private CpUserService cpUserService;
@Override
public String execute(String param) throws Exception {
TenantContextHolder.setTenantId(1L);
cpUserService.cpUserSync();
return "企业微信同步完成...";
}
}
@@ -0,0 +1,79 @@
package cn.iocoder.yudao.module.system.service.CpUser;
import java.util.*;
import javax.validation.*;
import cn.iocoder.yudao.module.system.controller.admin.CpUser.dto.CpMessageDto;
import cn.iocoder.yudao.module.system.controller.admin.CpUser.vo.*;
import cn.iocoder.yudao.module.system.dal.dataobject.CpUser.CpUserDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import com.baomidou.mybatisplus.extension.service.IService;
import me.chanjar.weixin.common.error.WxErrorException;
/**
* 企业微信成员 Service 接口
*
* @author 系统管理员
*/
public interface CpUserService extends IService<CpUserDO> {
/**
* 创建企业微信成员
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createCpUser(@Valid CpUserCreateReqVO createReqVO);
/**
* 更新企业微信成员
*
* @param updateReqVO 更新信息
*/
void updateCpUser(@Valid CpUserUpdateReqVO updateReqVO);
/**
* 删除企业微信成员
*
* @param id 编号
*/
void deleteCpUser(Long id);
/**
* 获得企业微信成员
*
* @param id 编号
* @return 企业微信成员
*/
CpUserDO getCpUser(Long id);
CpUserDO getByUserId(String userId);
/**
* 获得企业微信成员列表
*
* @param ids 编号
* @return 企业微信成员列表
*/
List<CpUserDO> getCpUserList(Collection<Long> ids);
/**
* 获得企业微信成员分页
*
* @param pageReqVO 分页查询
* @return 企业微信成员分页
*/
PageResult<CpUserDO> getCpUserPage(CpUserPageReqVO pageReqVO);
/**
* 获得企业微信成员列表, 用于 Excel 导出
*
* @param exportReqVO 查询条件
* @return 企业微信成员列表
*/
List<CpUserDO> getCpUserList(CpUserExportReqVO exportReqVO);
void cpUserSync() throws WxErrorException;
void sendMessage(CpMessageDto cpMessage) throws WxErrorException;
}
@@ -0,0 +1,164 @@
package cn.iocoder.yudao.module.system.service.CpUser;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.system.controller.admin.CpUser.dto.CpMessageDto;
import cn.iocoder.yudao.module.system.enums.wxcp.WxCpMsgTypeEnum;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpDepart;
import me.chanjar.weixin.cp.bean.WxCpUser;
import me.chanjar.weixin.cp.bean.message.WxCpMessage;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import java.util.*;
import java.util.stream.Collectors;
import cn.iocoder.yudao.module.system.controller.admin.CpUser.vo.*;
import cn.iocoder.yudao.module.system.dal.dataobject.CpUser.CpUserDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.convert.CpUser.CpUserConvert;
import cn.iocoder.yudao.module.system.dal.mysql.CpUser.CpUserMapper;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
/**
* 企业微信成员 Service 实现类
*
* @author 系统管理员
*/
@Service
@Validated
@Slf4j
public class CpUserServiceImpl extends ServiceImpl<CpUserMapper,CpUserDO> implements CpUserService {
@Resource
private CpUserMapper cpUserMapper;
@Autowired
private WxCpService wxCpService;
@Override
public Long createCpUser(CpUserCreateReqVO createReqVO) {
// 插入
CpUserDO cpUser = CpUserConvert.INSTANCE.convert(createReqVO);
cpUserMapper.insert(cpUser);
// 返回
return cpUser.getId();
}
@Override
public void updateCpUser(CpUserUpdateReqVO updateReqVO) {
// 校验存在
this.validateCpUserExists(updateReqVO.getId());
// 更新
CpUserDO updateObj = CpUserConvert.INSTANCE.convert(updateReqVO);
cpUserMapper.updateById(updateObj);
}
@Override
public void deleteCpUser(Long id) {
// 校验存在
this.validateCpUserExists(id);
// 删除
cpUserMapper.deleteById(id);
}
private void validateCpUserExists(Long id) {
if (cpUserMapper.selectById(id) == null) {
throw exception(CP_USER_NOT_EXISTS);
}
}
@Override
public CpUserDO getCpUser(Long id) {
return cpUserMapper.selectById(id);
}
@Override
public CpUserDO getByUserId(String userId) {
return this.getOne(Wrappers.<CpUserDO>lambdaQuery().eq(CpUserDO::getUserId,userId),false);
}
@Override
public List<CpUserDO> getCpUserList(Collection<Long> ids) {
return cpUserMapper.selectBatchIds(ids);
}
@Override
public PageResult<CpUserDO> getCpUserPage(CpUserPageReqVO pageReqVO) {
return cpUserMapper.selectPage(pageReqVO);
}
@Override
public List<CpUserDO> getCpUserList(CpUserExportReqVO exportReqVO) {
return cpUserMapper.selectList(exportReqVO);
}
public void cpUserSync() throws WxErrorException {
WxCpConfigStorage config = wxCpService.getWxCpConfigStorage();
List<WxCpDepart> departList = wxCpService.getDepartmentService().list(null);
//根据部门id获取部门成员信息
//6 旭清回鄉生態農業發展公司
//35 回鄉信息技术公司
List<WxCpUser> userList = wxCpService.getUserService().listByDepartment(6L, true, 0);
userList.addAll(wxCpService.getUserService().listByDepartment(35L, true, 0));
List<CpUserDO> rs = CpUserConvert.INSTANCE.convertListFromWxApi(userList);
List<CpUserDO> nowUsers = this.cpUserMapper.selectList();
// 已经存在的员工
List<String> nowUsersIds = nowUsers.stream().map(CpUserDO::getUserId).collect(Collectors.toList());
rs.removeIf(cpUserDO -> nowUsersIds.contains(cpUserDO.getUserId()));
this.saveBatch(rs);
// 已经离职的员工 设置删除
List<String> leaveUserIds = userList.stream().filter(wxCpUser -> wxCpUser.getStatus() == 0)
.map(WxCpUser::getUserId).collect(Collectors.toList());
if (ObjectUtil.isNotEmpty(leaveUserIds)) {
List<CpUserDO> leaveUsers = this.list(Wrappers.<CpUserDO>lambdaQuery()
.in(CpUserDO::getUserId, leaveUserIds));
leaveUsers.forEach(cpUserDO -> cpUserDO.setDeleted(true));
this.updateBatchById(leaveUsers);
}
}
@Override
public void sendMessage(CpMessageDto cpMessage) throws WxErrorException {
WxCpMessage message = new WxCpMessage();
String agentId = wxCpService.getWxCpConfigStorage().getAgentId().toString();
String page = String.format("/pages/task/%sDetail/index?id=%d",
cpMessage.getTypeEnum().equals(WxCpMsgTypeEnum.project) ? "project" : "task",
cpMessage.getId());
message.setMsgType("miniprogram_notice");
message.setAppId(agentId);
message.setPage(page);
message.setTitle(cpMessage.getTitle());
message.setDescription(cpMessage.getDescription());
message.setContentItems(cpMessage.getContentItems());
message.setToUser(String.join("|", cpMessage.getReciveIds()));
wxCpService.getMessageService().send(message);
}
}
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService; import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.Set;
/** /**
* 管理后台的认证 Service 接口 * 管理后台的认证 Service 接口
@@ -24,6 +25,16 @@ public interface AdminAuthService extends SecurityAuthFrameworkService {
*/ */
String login(@Valid AuthLoginReqVO reqVO, String userIp, String userAgent); String login(@Valid AuthLoginReqVO reqVO, String userIp, String userAgent);
/**
* 获得 User 拥有的角色编号数组
*
* @param userId 用户编号
* @return 角色编号数组
*/
Set<Long> getUserRoleIds(Long userId);
/** /**
* 社交登录,使用 code 授权码 * 社交登录,使用 code 授权码
* *
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.system.service.auth; package cn.iocoder.yudao.module.system.service.auth;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
@@ -13,9 +14,11 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBi
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLogin2ReqVO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLogin2ReqVO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLoginReqVO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLoginReqVO;
import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.CpUser.CpUserDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
import cn.iocoder.yudao.module.system.service.CpUser.CpUserService;
import cn.iocoder.yudao.module.system.service.common.CaptchaService; import cn.iocoder.yudao.module.system.service.common.CaptchaService;
import cn.iocoder.yudao.module.system.service.logger.LoginLogService; import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
import cn.iocoder.yudao.module.system.service.permission.PermissionService; import cn.iocoder.yudao.module.system.service.permission.PermissionService;
@@ -24,7 +27,9 @@ import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
@@ -71,9 +76,17 @@ public class AdminAuthServiceImpl implements AdminAuthService {
@Resource @Resource
private SocialUserService socialUserService; private SocialUserService socialUserService;
@Resource
private RedisTemplate redisTemplate;
@Resource
private CpUserService cpUserService;
@Resource @Resource
private Validator validator; private Validator validator;
@Value("${spring.profiles.active}")
private String profile;
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 获取 username 对应的 AdminUserDO // 获取 username 对应的 AdminUserDO
@@ -100,19 +113,27 @@ public class AdminAuthServiceImpl implements AdminAuthService {
@Override @Override
public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) {
// 判断验证码是否正确 if ("prod".equals(profile)) {
this.verifyCaptcha(reqVO); // 判断验证码是否正确
this.verifyCaptcha(reqVO);
}
// 使用账号密码,进行登录 // 使用账号密码,进行登录
LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());
CpUserDO wxUser = cpUserService.getByUserId(loginUser.getUsername());
if (ObjectUtil.isNotEmpty(wxUser)){
//将cpUserId存入 key为 系统用户id value为 cpUserId
redisTemplate.opsForValue().set("CpUserId::" + loginUser.getId(), wxUser.getId().toString());
}
// 缓存登陆用户到 Redis 中,返回 sessionId 编号 // 缓存登陆用户到 Redis 中,返回 sessionId 编号
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent); return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent);
} }
private void verifyCaptcha(AuthLoginReqVO reqVO) { private void verifyCaptcha(AuthLoginReqVO reqVO) {
// 如果验证码关闭,则不进行校验 // 如果验证码关闭,则不进行校验
if (!captchaService.isCaptchaEnable()) { if (!captchaService.isCaptchaEnable() || "wxcp".equals(reqVO.getPlatform())) {
return; return;
} }
// 校验验证码 // 校验验证码
@@ -187,7 +208,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
* @param userId 用户编号 * @param userId 用户编号
* @return 角色编号数组 * @return 角色编号数组
*/ */
private Set<Long> getUserRoleIds(Long userId) { public Set<Long> getUserRoleIds(Long userId) {
return permissionService.getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus())); return permissionService.getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
} }
@@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataCrea
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataExportReqVO; import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataUpdateReqVO; import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataUpdateReqVO;
import cn.iocoder.yudao.module.system.dict.DictModel;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -94,4 +95,6 @@ public interface DictDataService extends DictDataFrameworkService {
*/ */
void validDictDatas(String dictType, Collection<String> values); void validDictDatas(String dictType, Collection<String> values);
List<DictModel> queryTableDictTextByKeys(String table, String text, String code, List<String> keys);
} }
@@ -13,20 +13,20 @@ import cn.iocoder.yudao.module.system.convert.dict.DictDataConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO; import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO; import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO;
import cn.iocoder.yudao.module.system.dal.mysql.dict.DictDataMapper; import cn.iocoder.yudao.module.system.dal.mysql.dict.DictDataMapper;
import cn.iocoder.yudao.module.system.dal.mysql.dict.DictMapper;
import cn.iocoder.yudao.module.system.dict.DictModel;
import cn.iocoder.yudao.module.system.mq.producer.dict.DictDataProducer; import cn.iocoder.yudao.module.system.mq.producer.dict.DictDataProducer;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableTable; import com.google.common.collect.ImmutableTable;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Collection; import java.util.*;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@@ -40,6 +40,8 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@Slf4j @Slf4j
public class DictDataServiceImpl implements DictDataService { public class DictDataServiceImpl implements DictDataService {
@Autowired
private DictMapper dictMapper;
/** /**
* 排序 dictType > sort * 排序 dictType > sort
*/ */
@@ -277,4 +279,17 @@ public class DictDataServiceImpl implements DictDataService {
}); });
} }
@Override
public List<DictModel> queryTableDictTextByKeys(String table, String text, String code, List<String> keys) {
//update-begin-author:taoyan date:20220113 for: @dict注解支持 dicttable 设置where条件
String filterSql = null;
if(table.toLowerCase().indexOf("where")>0){
String[] arr = table.split(" (?i)where ");
table = arr[0];
filterSql = arr[1];
}
return dictMapper.queryTableDictByKeysAndFilterSql(table, text, code, filterSql, keys);
//update-end-author:taoyan date:20220113 for: @dict注解支持 dicttable 设置where条件
}
} }

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