first commit

This commit is contained in:
dev
2026-02-22 12:12:02 +08:00
commit b236048a24
1467 changed files with 160403 additions and 0 deletions
@@ -0,0 +1,935 @@
# 会员模块结构说明
## 模块概述
`ruoyi-member` 模块是系统会员中心模块,负责 App 端用户管理、认证授权、设备管理、消息推送等核心功能。
- **包名**: `org.dromara.member`
- **状态**: ✅ 核心功能已实现
## 已完成工作
### 1. 模块结构
- ✅ 创建了完整的模块目录结构
- ✅ 创建了 `pom.xml` 配置文件
- ✅ 配置了模块依赖关系
### 2. 配置类
- ✅ 创建了 JWT 过滤器配置 (`AppJwtFilter`)
- ✅ 创建了 JWT Web 配置 (`AppJwtWebConfig`)
- ✅ 配置了组件扫描和过滤器注册
### 3. 数据库
- ✅ 创建了所有业务表的实体类和 Mapper
- ✅ 实现了数据库表的 CRUD 操作
- ✅ 配置了逻辑删除和字段自动填充
### 4. 核心功能
- ✅ 用户管理(注册、登录、信息管理)
- ✅ 角色管理(角色CRUD、用户角色关联)
- ✅ 设备管理(设备绑定、推送令牌管理)
- ✅ 文件管理(文件上传、下载、管理)
- ✅ 消息管理(消息推送、通知管理)
- ✅ 操作日志(日志记录、查询、统计)
- ✅ 登录日志(登录日志记录、查询)
- ✅ 版本管理(App版本管理、更新检查)
- ✅ 社交认证(第三方登录、绑定)
- ✅ JWT认证(令牌生成、刷新、验证)
### 5. 异常处理
- ✅ 创建了统一的异常处理类 (`AppExceptionHandler`)
- ✅ 定义了业务异常类(`DeviceException`, `FileException`, `VersionException`
### 6. API接口
- ✅ 实现了管理端接口(用户、角色、配置管理)
- ✅ 实现了移动端接口(用户、角色、设备、文件、消息、日志、版本管理)
- ✅ 实现了认证接口(JWT登录、注册、刷新)
## 核心功能模块
### 1. 用户管理 (AppUser)
- **职责**: 用户注册、登录、信息管理、密码管理
- **状态**: ✅ 核心功能已实现
- **数据库表**: `app_user`, `app_user_role`
### 2. 角色管理 (AppRole)
- **职责**: 角色管理、用户角色关联
- **状态**: ✅ 核心功能已实现
- **数据库表**: `app_role`, `app_user_role`
### 3. 设备管理 (AppDevice)
- **职责**: 用户设备绑定、推送令牌管理、设备状态管理
- **状态**: ✅ 核心功能已实现
- **数据库表**: `app_device`
### 4. 文件管理 (AppFile)
- **职责**: 文件上传、下载、管理(头像、文档、图片等)
- **状态**: ✅ 核心功能已实现
- **数据库表**: `app_file`
### 5. 消息管理 (AppMessage)
- **职责**: 消息推送、通知管理、用户消息关联
- **状态**: ✅ 核心功能已实现
- **数据库表**: `app_message`, `app_user_message`
### 6. 操作日志 (AppOperLog)
- **职责**: 用户操作日志记录、查询、统计分析
- **状态**: ✅ 核心功能已实现
- **数据库表**: `app_oper_log`
### 7. 登录日志 (AppLoginLog)
- **职责**: 用户登录日志记录、查询
- **状态**: ✅ 核心功能已实现
- **数据库表**: `app_login_log`
### 8. 版本管理 (AppVersion)
- **职责**: App 版本管理、更新检查、强制更新
- **状态**: ✅ 核心功能已实现
- **数据库表**: `app_version`
### 9. 社交认证 (AppSocialAuth)
- **职责**: 第三方登录(微信、QQ、微博等)
- **状态**: ✅ 核心功能已实现
- **数据库表**: `app_social_auth`
### 10. JWT认证 (JwtUtil)
- **职责**: JWT 令牌生成、刷新、验证
- **状态**: ✅ 核心功能已实现
- **配置文件**: `AppJwtFilter`, `AppJwtWebConfig`
## 数据库表结构说明
### 1. 用户表 (app_user)
- **功能**: 存储 App 端用户基本信息
- **关键字段**: `user_id`, `user_name`, `nick_name`, `phone`, `email`, `password`, `status`, `avatar`, `sex`, `login_ip`, `login_date`, `register_time`
- **索引**: `user_name` (唯一), `phone`, `email`
- **状态说明**:
- `status`: 0-正常, 1-停用
- `sex`: 0-男, 1-女, 2-未知
- `del_flag`: 0-存在, 1-删除
### 2. 角色表 (app_role)
- **功能**: 存储 App 端角色信息
- **关键字段**: `role_id`, `role_name`, `role_key`, `role_sort`, `status`
- **索引**: `role_key` (唯一), `status`
- **状态说明**:
- `status`: 0-正常, 1-停用
- `del_flag`: 0-存在, 1-删除
### 3. 用户角色关联表 (app_user_role)
- **功能**: 存储用户和角色的关联关系
- **关键字段**: `user_id`, `role_id`
- **索引**: `user_id`, `role_id`, `uk_user_role` (唯一索引)
- **说明**: 多对多关系,一个用户可以有多个角色,一个角色可以分配给多个用户
### 4. 设备表 (app_device)
- **功能**: 存储用户绑定的设备信息
- **关键字段**: `device_id`, `user_id`, `device_type`, `device_name`, `device_model`, `device_brand`, `os_version`, `app_version`, `push_token`, `is_active`, `last_active_time`
- **索引**: `user_id`, `device_type`, `push_token`
- **状态说明**:
- `device_type`: ios, android, h5
- `is_active`: 0-否, 1-是
- `del_flag`: 0-存在, 2-删除
### 5. 文件表 (app_file)
- **功能**: 存储用户上传的文件信息
- **关键字段**: `file_id`, `user_id`, `file_name`, `original_name`, `file_path`, `file_url`, `file_size`, `file_type`, `file_suffix`, `file_category`, `storage_service`, `status`, `download_count`, `is_public`, `file_md5`
- **索引**: `user_id`, `file_category`, `file_md5`, `status`
- **状态说明**:
- `file_category`: 1-头像, 2-文档, 3-图片, 4-视频, 5-音频, 6-其他
- `status`: 0-正常, 1-删除
- `is_public`: 0-私有, 1-公开
- `del_flag`: 0-存在, 2-删除
### 6. 消息表 (app_message)
- **功能**: 存储系统消息和通知
- **关键字段**: `message_id`, `title`, `content`, `message_type`, `target_type`, `target_ids`, `send_time`, `status`
- **索引**: `message_type`, `target_type`, `status`, `send_time`
- **状态说明**:
- `message_type`: system-系统消息, notice-通知, activity-活动
- `target_type`: all-全部用户, user-指定用户, role-指定角色
- `status`: 0-待发送, 1-已发送, 2-发送失败
- `del_flag`: 0-存在, 2-删除
### 7. 用户消息关联表 (app_user_message)
- **功能**: 存储用户消息的读取记录
- **关键字段**: `id`, `user_id`, `message_id`, `is_read`, `read_time`
- **索引**: `user_id`, `message_id`, `is_read`
- **状态说明**:
- `is_read`: 0-未读, 1-已读
- `del_flag`: 0-存在, 2-删除
### 8. 操作日志表 (app_oper_log)
- **功能**: 存储用户操作日志
- **关键字段**: `oper_id`, `title`, `business_type`, `method`, `request_method`, `oper_name`, `oper_url`, `oper_ip`, `oper_location`, `oper_param`, `json_result`, `status`, `error_msg`, `oper_time`, `cost_time`
- **索引**: `oper_name`, `status`, `oper_time`, `business_type`
- **状态说明**:
- `business_type`: 0-其它, 1-新增, 2-修改, 3-删除
- `status`: 0-正常, 1-异常
### 9. 登录日志表 (app_login_log)
- **功能**: 存储用户登录日志
- **关键字段**: `info_id`, `user_name`, `status`, `ipaddr`, `login_location`, `browser`, `os`, `msg`, `login_time`
- **索引**: `user_name`, `status`, `login_time`, `ipaddr`
- **状态说明**:
- `status`: 0-成功, 1-失败
### 10. 版本表 (app_version)
- **功能**: 存储 App 版本信息
- **关键字段**: `version_id`, `version_code`, `version_name`, `update_content`, `download_url`, `is_force`, `platform`
- **索引**: `platform`, `version_code`
- **状态说明**:
- `platform`: android, ios
- `is_force`: 0-否, 1-是
- `del_flag`: 0-存在, 2-删除
### 11. 社交认证表 (app_social_auth)
- **功能**: 存储第三方登录授权信息
- **关键字段**: `id`, `user_id`, `platform`, `platform_uid`, `open_id`, `union_id`, `access_token`, `expire_in`, `refresh_token`, `scope`
- **索引**: `user_id`, `platform`, `platform_uid`, `open_id`, `union_id`
- **状态说明**:
- `platform`: wechat_mp-微信小程序, wechat_open-微信开放平台, qq-QQ, weibo-微博
- `del_flag`: 0-存在, 2-删除
## 功能清单
### 1. 用户管理
#### 已实现功能 ✅
##### 用户基础功能
- ✅ 用户注册
- 账号注册(支持用户名、手机号、邮箱注册)
- 账号唯一性校验
- 手机号唯一性校验
- 邮箱唯一性校验
- 注册时自动分配默认角色
- ✅ 用户登录
- 账号密码登录
- 手机号验证码登录 ✅(在 ruoyi-server 模块实现)
- 邮箱验证码登录 ✅(在 ruoyi-server 模块实现)
- 验证码发送(短信/邮箱)✅(在 ruoyi-server 模块实现)
- JWT 令牌生成
- 登录日志记录
- ✅ 用户信息管理
- 用户列表查询(分页、条件筛选)
- 用户详情查询
- 用户新增/修改/删除
- 用户状态管理(启用/停用)
- 用户信息导出
- ✅ 用户资料管理
- 用户资料查询
- 用户资料修改
- 头像管理(关联文件表)
- ✅ 密码管理
- 修改密码(需验证旧密码)
- 重置密码(管理员操作)
- 密码加密(BCrypt
- ✅ 第三方账号绑定
- 第三方账号绑定/解绑 ✅(在 ruoyi-server 模块实现)
- 查询绑定列表 ✅
#### 待实现功能 ⏳
- [ ] 忘记密码(通过手机号/邮箱验证码找回密码)
- [ ] 独立的手机号/邮箱绑定功能(当前可通过更新用户资料方式修改)
- [ ] 用户等级体系
- 用户等级定义
- 用户等级升级规则
- 用户积分/经验值管理
- [ ] 用户标签
- 用户标签管理
- 用户标签关联
- 标签筛选用户
### 2. 角色管理
#### 已实现功能 ✅
- ✅ 角色CRUD操作
- 角色列表查询(分页、条件筛选)
- 角色详情查询
- 角色新增/修改/删除
- 角色导出功能
- ✅ 角色状态管理
- 启用/禁用角色
- ✅ 用户角色关联
- 查询用户已分配角色
- 查询用户未分配角色
- 分配角色给用户
- 取消用户角色关联
- 批量分配/取消角色
#### 待实现功能 ⏳
- [ ] 角色权限管理
- 角色权限配置
- 权限点管理
- 权限验证(需集成权限框架)
- [ ] 角色数据权限
- 数据权限范围配置
- 数据权限过滤
### 3. 设备管理
#### 已实现功能 ✅
- ✅ 设备绑定
- 设备信息绑定
- 设备信息更新
- ✅ 设备查询
- 设备列表查询
- 设备详情查询
- 我的设备列表查询
- 活跃设备查询
- ✅ 设备状态管理
- 设备活跃状态更新
- 推送令牌管理
- 设备心跳检测
- ✅ 设备解绑
- 设备解绑功能
#### 待实现功能 ⏳
- [ ] 设备推送管理
- 消息推送(需集成推送服务)
- 推送记录查询
- 推送统计
- [ ] 设备安全
- 设备指纹识别
- 异常设备检测
- 设备黑名单管理
### 4. 文件管理
#### 已实现功能 ✅
- ✅ 文件上传
- 单文件上传
- 批量文件上传
- 头像上传
- ✅ 文件查询
- 文件列表查询(分页)
- 文件详情查询
- 按分类查询文件
- 公开文件查询
- ✅ 文件下载
- 文件下载
- 下载次数统计
- ✅ 文件管理
- 文件信息修改
- 文件状态管理
- 文件公开/私有设置
- 文件删除
- ✅ 文件校验
- 文件MD5校验(秒传功能)
- 文件统计
#### 待实现功能 ⏳
- [ ] 文件存储优化
- 文件存储服务商集成(OSS、COS等)
- 文件CDN加速
- 文件压缩处理
- [ ] 文件预览
- 图片预览
- 文档预览(PDF、Office等)
- 视频播放
- [ ] 文件分享
- 文件分享链接生成
- 分享链接访问控制
- 分享统计
### 5. 消息管理
#### 已实现功能 ✅
- ✅ 消息管理
- 消息列表查询(分页)
- 消息详情查询
- 消息新增/修改/删除
- 消息导出
- ✅ 用户消息
- 我的消息列表查询
- 未读消息数量查询
- 消息标记已读
- 批量标记已读
- 全部标记已读
- ✅ 消息推送
- 系统消息发送
- 个人消息发送
- 消息推送(需集成推送服务)
- 消息广播
#### 待实现功能 ⏳
- [ ] 消息推送集成
- 推送服务集成(极光推送、个推等)
- 推送消息发送
- 推送结果回调
- 推送统计
- [ ] 消息模板
- 消息模板管理
- 模板变量替换
- [ ] 消息分类
- 消息分类管理
- 消息分类筛选
- [ ] 消息设置
- 用户消息推送设置
- 消息免打扰时段
- 消息接收方式设置
### 6. 操作日志
#### 已实现功能 ✅
- ✅ 日志记录
- 操作日志记录
- 批量操作日志记录
- ✅ 日志查询
- 日志列表查询(分页、条件筛选)
- 我的操作日志查询
- 日志详情查询
- 日志导出
- ✅ 日志统计
- 操作日志统计
- 行为分析
- 热门操作统计
- 错误操作统计
- ✅ 日志清理
- 日志清理
- 按时间清理日志
#### 待实现功能 ⏳
- [ ] 日志分析
- 操作趋势分析
- 用户行为分析
- 异常操作分析
- [ ] 日志告警
- 异常操作告警
- 告警规则配置
### 7. 登录日志
#### 已实现功能 ✅
- ✅ 登录日志记录
- 登录成功/失败日志记录
- 登录IP、地点、浏览器等信息记录
- ✅ 登录日志查询
- 登录日志列表查询(分页、条件筛选)
- 登录日志详情查询
- 登录日志导出
#### 待实现功能 ⏳
- [ ] 登录安全
- 异常登录检测
- 异地登录提醒
- 登录IP白名单/黑名单
- [ ] 登录统计
- 登录统计报表
- 登录趋势分析
- 登录设备分析
### 8. 版本管理
#### 已实现功能 ✅
- ✅ 版本管理
- 版本列表查询(分页)
- 版本详情查询
- 版本新增/修改/删除
- 版本导出
- ✅ 版本检查
- 检查更新
- 获取最新版本
- 版本历史查询
- 强制更新检查
- 更新信息查询
- 版本对比
- ✅ 版本统计
- 版本下载记录
- 版本安装记录
#### 待实现功能 ⏳
- [ ] 版本发布流程
- 版本发布审核
- 版本发布通知
- [ ] 版本回退
- 版本回退功能
- 回退记录
### 9. 社交认证
#### 已实现功能 ✅
- ✅ 第三方登录
- 微信小程序登录
- 微信开放平台登录
- QQ登录
- 微博登录
- ✅ 授权管理
- 授权信息存储
- 授权信息查询
- 授权绑定/解绑 ✅(在 ruoyi-server 模块实现)
#### 待实现功能 ⏳
- [ ] 更多第三方登录
- 支付宝登录
- 抖音登录
- 苹果登录(Apple ID
- [ ] 授权管理增强
- 授权过期处理
- 授权刷新
- 授权撤销
### 10. JWT认证
#### 已实现功能 ✅
- ✅ JWT令牌生成
- Access Token生成
- Refresh Token生成
- ✅ JWT令牌验证
- Token验证过滤器
- Token有效性校验
- ✅ JWT令牌刷新
- Token刷新接口
- ✅ 用户登录/登出
- 登录接口(生成Token
- 登出接口(Token失效)
- ✅ 验证码
- 图形验证码生成
#### 待实现功能 ⏳
- [ ] Token安全增强
- Token黑名单(Redis存储)
- Token刷新策略优化
- Token过期时间配置
- [ ] 多端登录管理
- 多设备登录控制
- 登录设备列表查询
- 强制下线功能
## API接口详细说明
### 管理端接口
#### 用户管理 (`/admin/member/user`)
- `GET /admin/member/user/list` - 查询用户列表(分页)
- `GET /admin/member/user/{userId}` - 获取用户详情
- `POST /admin/member/user` - 新增用户
- `PUT /admin/member/user` - 修改用户
- `DELETE /admin/member/user/{userIds}` - 删除用户
- `POST /admin/member/user/export` - 导出用户列表
- `GET /admin/member/user/profile` - 获取当前用户资料
- `PUT /admin/member/user/profile` - 修改当前用户资料
- `PUT /admin/member/user/resetPwd` - 重置用户密码
- `PUT /admin/member/user/changeStatus` - 修改用户状态
- `GET /admin/member/user/authRole/{userId}` - 查询用户角色
- `GET /admin/member/user/deptTree` - 查询部门树(预留)
#### 角色管理 (`/admin/member/role`)
- `GET /admin/member/role/list` - 查询角色列表(分页)
- `GET /admin/member/role/{roleId}` - 获取角色详情
- `POST /admin/member/role` - 新增角色
- `PUT /admin/member/role` - 修改角色
- `DELETE /admin/member/role/{roleIds}` - 删除角色
- `POST /admin/member/role/export` - 导出角色列表
- `GET /admin/member/role/optionselect` - 查询角色选项列表
- `GET /admin/member/role/authUser/allocatedList` - 查询已分配用户角色列表
- `GET /admin/member/role/authUser/unallocatedList` - 查询未分配用户角色列表
- `PUT /admin/member/role/authUser/cancelAll` - 取消授权用户所有角色
- `PUT /admin/member/role/authUser/selectAll` - 批量选择授权用户角色
#### 配置管理 (`/admin/member/config`)
- `GET /admin/member/config/list` - 查询配置列表
- `GET /admin/member/config/{configId}` - 获取配置详情
- `POST /admin/member/config` - 新增配置
- `PUT /admin/member/config` - 修改配置
- `DELETE /admin/member/config/{configIds}` - 删除配置
- `POST /admin/member/config/export` - 导出配置列表
- `DELETE /admin/member/config/refreshCache` - 刷新配置缓存
- `GET /admin/member/config/configKey/{configKey}` - 根据配置键查询配置值
- `GET /admin/member/config/systemInfo` - 获取系统信息
- `GET /admin/member/config/statistics` - 获取统计数据
### 移动端接口
#### 用户管理 (`/app/member/user`)
- `GET /app/member/user/list` - 查询用户列表
- `GET /app/member/user/{userId}` - 获取用户详情
- `POST /app/member/user` - 新增用户
- `PUT /app/member/user` - 修改用户
- `DELETE /app/member/user/{userIds}` - 删除用户
- `POST /app/member/user/export` - 导出用户列表
- `PUT /app/member/user/resetPwd` - 重置密码
- `PUT /app/member/user/changeStatus` - 修改状态
- `GET /app/member/user/profile/{userId}` - 获取用户资料
- `GET /app/member/user/getInfo` - 获取当前用户信息
- `GET /app/member/user/profile` - 获取当前用户资料
- `PUT /app/member/user/profile` - 修改用户资料
- `PUT /app/member/user/profile/updatePwd` - 修改密码
#### 角色管理 (`/app/member/role`)
- `GET /app/member/role/list` - 查询角色列表
- `GET /app/member/role/{roleId}` - 获取角色详情
- `POST /app/member/role` - 新增角色
- `PUT /app/member/role` - 修改角色
- `DELETE /app/member/role/{roleIds}` - 删除角色
- `POST /app/member/role/export` - 导出角色列表
- `GET /app/member/role/optionselect` - 查询角色选项列表
- `GET /app/member/role/authUser/allocatedList` - 查询已分配用户角色列表
- `GET /app/member/role/authUser/unallocatedList` - 查询未分配用户角色列表
- `PUT /app/member/role/authUser/cancel` - 取消授权用户角色
- `PUT /app/member/role/authUser/cancelAll` - 取消授权用户所有角色
- `PUT /app/member/role/authUser/selectAll` - 批量选择授权用户角色
#### 设备管理 (`/app/member/device`)
- `GET /app/member/device/list` - 查询设备列表
- `GET /app/member/device/{deviceId}` - 获取设备详情
- `POST /app/member/device/bind` - 绑定设备
- `PUT /app/member/device` - 修改设备信息
- `DELETE /app/member/device/{deviceId}` - 解绑设备
- `GET /app/member/device/my` - 查询我的设备列表
- `GET /app/member/device/active` - 查询活跃设备列表
- `PUT /app/member/device/{deviceId}/status` - 更新设备状态
- `PUT /app/member/device/{deviceId}/push-token` - 更新推送令牌
- `POST /app/member/device/{deviceId}/heartbeat` - 设备心跳
- `GET /app/member/device/by-push-token` - 根据推送令牌查询设备
#### 文件管理 (`/app/member/file`)
- `GET /app/member/file/list` - 查询文件列表
- `GET /app/member/file/{fileId}` - 获取文件详情
- `POST /app/member/file` - 新增文件记录
- `PUT /app/member/file` - 修改文件信息
- `DELETE /app/member/file/{fileIds}` - 删除文件
- `POST /app/member/file/upload` - 上传文件
- `POST /app/member/file/upload/avatar` - 上传头像
- `POST /app/member/file/upload/batch` - 批量上传文件
- `GET /app/member/file/category/{fileCategory}` - 按分类查询文件
- `GET /app/member/file/download/{fileId}` - 下载文件
- `PUT /app/member/file/status/{fileId}` - 更新文件状态
- `PUT /app/member/file/public/{fileId}` - 更新文件公开状态
- `GET /app/member/file/statistics` - 获取文件统计
- `GET /app/member/file/check/{fileMd5}` - 检查文件是否存在
- `GET /app/member/file/public/category/{fileCategory}` - 查询公开文件
#### 消息管理 (`/app/member/message`)
- `GET /app/member/message/list` - 查询消息列表
- `GET /app/member/message/myMessages` - 查询我的消息列表
- `GET /app/member/message/unreadCount` - 查询未读消息数量
- `GET /app/member/message/{messageId}` - 获取消息详情
- `POST /app/member/message` - 新增消息
- `PUT /app/member/message` - 修改消息
- `DELETE /app/member/message/{messageIds}` - 删除消息
- `POST /app/member/message/export` - 导出消息列表
- `PUT /app/member/message/markRead/{messageId}` - 标记消息已读
- `PUT /app/member/message/markReadBatch` - 批量标记消息已读
- `PUT /app/member/message/markAllRead` - 全部标记已读
- `POST /app/member/message/sendSystem` - 发送系统消息
- `POST /app/member/message/sendPersonal` - 发送个人消息
- `POST /app/member/message/push` - 推送消息
- `POST /app/member/message/broadcast` - 广播消息
#### 操作日志 (`/app/member/operlog`)
- `GET /app/member/operlog/list` - 查询操作日志列表
- `GET /app/member/operlog/my` - 查询我的操作日志
- `GET /app/member/operlog/{operId}` - 获取操作日志详情
- `POST /app/member/operlog` - 新增操作日志
- `PUT /app/member/operlog` - 修改操作日志
- `DELETE /app/member/operlog/{operIds}` - 删除操作日志
- `POST /app/member/operlog/export` - 导出操作日志列表
- `DELETE /app/member/operlog/clean` - 清理操作日志
- `DELETE /app/member/operlog/cleanByTime` - 按时间清理操作日志
- `GET /app/member/operlog/statistics` - 获取操作日志统计
- `GET /app/member/operlog/behavior` - 获取行为分析
- `GET /app/member/operlog/popular` - 获取热门操作
- `GET /app/member/operlog/errors` - 获取错误操作
- `POST /app/member/operlog/record` - 记录操作
- `POST /app/member/operlog/batchRecord` - 批量记录操作
#### 版本管理 (`/app/member/version`)
- `GET /app/member/version/list` - 查询版本列表
- `GET /app/member/version/{versionId}` - 获取版本详情
- `GET /app/member/version/checkUpdate` - 检查更新
- `GET /app/member/version/latest` - 获取最新版本
- `GET /app/member/version/history` - 获取版本历史
- `GET /app/member/version/checkForceUpdate` - 检查强制更新
- `GET /app/member/version/updateInfo` - 获取更新信息
- `GET /app/member/version/compare` - 版本对比
- `POST /app/member/version/download/{versionId}` - 记录下载
- `POST /app/member/version/install/{versionId}` - 记录安装
### 认证接口
#### JWT认证 (`/app/jwt`)
- `GET /app/jwt/captcha` - 获取验证码
- `POST /app/jwt/register` - 用户注册
- `POST /app/jwt/login` - 用户登录
- `POST /app/jwt/refresh` - 刷新Token
- `POST /app/jwt/logout` - 用户登出
#### 第三方账号绑定 (`/app/auth`) - 在 ruoyi-server 模块
- `POST /app/auth/bind/social` - 绑定第三方账号
- `POST /app/auth/unbind/social` - 解绑第三方账号
- `GET /app/auth/social/list` - 获取用户绑定的第三方账号列表
- `GET /app/auth/binding/{source}` - 获取第三方登录跳转URL
- `POST /app/auth/social/binding` - 社交账号绑定回调
## 技术栈与开发规范
### 技术栈
- **框架**: Spring Boot 3.x
- **ORM**: MyBatis-Plus
- **数据库**: MySQL 8.0+
- **认证**: Sa-Token + JWT
- **API文档**: Swagger/OpenAPI 3.0
- **构建工具**: Maven
- **安全**: Spring Security Crypto (BCrypt)
### 开发规范
#### 包结构规范
- `controller` - 控制器层(分为 `admin``app` 两个子包)
- `domain` - 实体类(包含 `bo` 业务对象和 `vo` 视图对象子包)
- `mapper` - MyBatis Mapper 接口
- `service` - 服务接口(实现类放在 `impl` 子包)
- `config` - 配置类(JWT过滤器、Web配置等)
- `exception` - 异常处理类
- `utils` - 工具类(JWT工具等)
#### 代码规范
- 统一使用 Lombok 简化代码
- 统一异常处理,使用 `ServiceException` 抛出业务异常
- 统一返回格式,使用 `R<T>` 封装返回结果
- 统一分页查询,使用 `PageQuery``TableDataInfo<T>`
- 统一日志记录,使用 `@Log` 注解记录操作日志
- 统一权限控制,管理端接口使用 `@SaCheckPermission` 注解
#### 数据库设计规范
- 表名使用 `app_` 前缀
- 字段命名使用下划线命名法(snake_case)
- 必须包含 `create_by`, `create_time`, `update_by`, `update_time` 字段
- 删除标记使用逻辑删除(`del_flag` 字段)
- 状态字段统一使用 `status`0表示启用/正常,1表示禁用/异常
#### API设计规范
- RESTful 风格设计
- 管理端接口路径:`/admin/member/{module}/{resource}`
- 移动端接口路径:`/app/member/{module}/{resource}`
- 使用 HTTP 标准方法:GET(查询)、POST(新增)、PUT(修改)、DELETE(删除)
- 统一使用 JSON 格式进行数据交互
- 使用 Swagger 注解完善接口文档
## 模块依赖关系
```
ruoyi-member (会员中心模块)
├── 依赖: ruoyi-common-* (通用工具模块)
│ ├── ruoyi-common-core (核心工具)
│ ├── ruoyi-common-doc (API文档)
│ ├── ruoyi-common-log (日志)
│ ├── ruoyi-common-mybatis (MyBatis)
│ ├── ruoyi-common-security (安全)
│ ├── ruoyi-common-web (Web)
│ ├── ruoyi-common-excel (Excel)
│ ├── ruoyi-common-translation (翻译)
│ ├── ruoyi-common-social (社交登录)
│ ├── ruoyi-common-satoken (权限认证)
│ └── ruoyi-common-oss (对象存储)
├── 依赖: sa-token-spring-boot3-starter (Sa-Token)
├── 依赖: sa-token-jwt (JWT支持)
└── 依赖: spring-security-crypto (密码加密)
```
## 模块集成说明
### 与 ruoyi-server 模块的集成
- **登录功能集成**: 手机号登录、邮箱登录功能在 `ruoyi-server` 模块实现
- `AppSmsAuthStrategy` - 手机号登录策略
- `AppEmailAuthStrategy` - 邮箱登录策略
- `CaptchaController` - 验证码发送接口
- **第三方账号绑定集成**: 第三方账号绑定/解绑功能在 `ruoyi-server` 模块实现
- `AppAuthController` - 提供绑定/解绑接口
- **用户服务集成**: `ruoyi-server` 模块通过依赖 `ruoyi-member` 模块使用用户服务
### 与其他模块的集成
- **文件存储**: 依赖 `ruoyi-common-oss` 模块,支持对象存储服务集成(OSS、COS等)
- **社交登录**: 依赖 `ruoyi-common-social` 模块,支持第三方登录(微信、QQ、微博等)
- **权限认证**: 依赖 `ruoyi-common-satoken` 模块,使用 Sa-Token 进行权限管理
## 下一步工作
### 功能完善
1. **用户管理增强**
- [x] 手机号/邮箱登录功能 ✅(已在 ruoyi-server 模块实现)
- [x] 第三方账号绑定/解绑功能 ✅(已在 ruoyi-server 模块实现)
- [ ] 忘记密码功能(通过手机号/邮箱验证码找回密码)
- [ ] 独立的手机号/邮箱绑定功能(当前可通过更新用户资料方式修改)
- [ ] 用户等级体系
- [ ] 用户标签管理
2. **角色权限系统**
- [ ] 权限点管理
- [ ] 角色权限配置
- [ ] 数据权限管理
3. **消息推送集成**
- [ ] 集成推送服务(极光推送、个推等)
- [ ] 消息推送功能完善
- [ ] 推送统计功能
4. **文件管理增强**
- [ ] 文件存储服务集成(OSS、COS等)
- [ ] 文件预览功能
- [ ] 文件分享功能
5. **安全增强**
- [ ] Token黑名单机制
- [ ] 多端登录管理
- [ ] 异常登录检测
- [ ] 登录IP白名单/黑名单
6. **日志分析**
- [ ] 操作日志分析功能
- [ ] 登录日志分析功能
- [ ] 日志告警功能
### 性能优化
1. **缓存优化**
- [ ] 用户信息缓存(Redis
- [ ] 角色权限缓存
- [ ] 配置信息缓存
2. **数据库优化**
- [ ] 索引优化
- [ ] 查询优化
- [ ] 分表策略(日志表)
### 测试验证
1. **单元测试**
- [ ] Service层单元测试
- [ ] Controller层单元测试
2. **集成测试**
- [ ] API接口测试
- [ ] 业务流程测试
## 开发指南
### 新增功能开发步骤
1. **数据库设计**
- 设计表结构(遵循数据库设计规范)
- 确保字段命名规范,包含必要的索引
- 添加必要的状态字段和逻辑删除字段
2. **创建实体类**
-`domain` 包下创建实体类
- 创建对应的 `Bo`(业务对象)和 `Vo`(视图对象)
- 使用 Lombok 简化代码
3. **创建 Mapper**
-`mapper` 包下创建 Mapper 接口
- 继承 `BaseMapper<T>` 获得基础 CRUD 功能
- 如需复杂查询,在 `resources/mapper/member` 下创建 XML 文件
4. **创建 Service**
-`service` 包下创建 Service 接口
-`service/impl` 包下创建 Service 实现类
- 实现业务逻辑,使用事务注解保证数据一致性
5. **创建 Controller**
- 管理端接口:在 `controller/admin` 包下创建
- 移动端接口:在 `controller/app` 包下创建
- 添加 Swagger 注解完善接口文档
- 添加权限控制注解(管理端使用 `@SaCheckPermission`
6. **测试验证**
- 编写单元测试(可选)
- 使用 Swagger UI 测试接口
- 验证业务逻辑正确性
### 代码迁移指南
如果要将现有代码迁移到新的模块结构:
1. **创建新模块结构**(如果尚未创建)
2. **复制代码到新模块**
3. **修改包名**(如需要)
4. **更新导入语句**
5. **更新 Mapper XML 文件中的命名空间**
6. **测试验证功能正常**
## 注意事项
1. **登录功能**: 手机号登录、邮箱登录功能已在 `ruoyi-server` 模块实现(通过 `AppSmsAuthStrategy``AppEmailAuthStrategy`),验证码发送功能通过 `CaptchaController` 提供
2. **密码安全**: 用户密码使用 BCrypt 加密,确保密码安全存储
3. **Token安全**: JWT Token 需要设置合理的过期时间,建议使用 Refresh Token 机制
4. **文件上传**: 文件上传需要限制文件大小和类型,防止恶意文件上传
5. **消息推送**: 消息推送需要集成第三方推送服务,注意推送频率限制
6. **日志管理**: 操作日志和登录日志会随着时间增长,建议定期清理或归档
7. **设备管理**: 设备绑定需要考虑设备唯一性,避免重复绑定
8. **社交登录**: 第三方登录需要配置相应的应用ID和密钥
9. **数据一致性**: 用户删除需要考虑关联数据(设备、文件、消息等)的处理
10. **并发安全**: 用户注册、登录等操作需要考虑并发安全性
11. **异常处理**: 需要统一异常处理,避免敏感信息泄露
12. **模块集成**: 注意与 `ruoyi-server` 模块的集成关系,部分功能(如登录、第三方绑定)在 server 模块实现
13. **配置管理**: JWT 过滤器配置在 `AppJwtWebConfig` 中,需要正确配置过滤路径
14. **权限控制**: 管理端接口需要添加权限控制,移动端接口需要添加登录验证
+94
View File
@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-modules</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-member</artifactId>
<description>
ruoyi-member 会员中心模块
</description>
<dependencies>
<!-- 通用工具-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-doc</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-security</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-web</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-excel</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-translation</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-social</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-satoken</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-oss</artifactId>
</dependency>
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
</dependency>
<!-- Sa-Token 整合 jwt -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,81 @@
package org.dromara.member.config;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.member.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* JWT过滤器
*
* @author pengles
*/
@Component
@RequiredArgsConstructor
public class AppJwtFilter extends OncePerRequestFilter {
@Value("${ruoyi.app.jwt.secret:ruoyi-secret}")
private String jwtSecret;
private static final String BLACKLIST_PREFIX = "APP:JWT:BLACKLIST:";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String path = request.getRequestURI();
// 仅保护敏感接口
if (requiresAuth(path)) {
String auth = request.getHeader("Authorization");
if (auth == null || !auth.startsWith("Bearer ")) {
write401(response);
return;
}
String token = auth.substring("Bearer ".length()).trim();
JwtUtil jwt = new JwtUtil(jwtSecret);
if (!jwt.verify(token)) {
write401(response);
return;
}
Map<String, Object> payload = jwt.parsePayload(token);
Object expObj = payload.get("exp");
long now = java.time.Instant.now().getEpochSecond();
long exp = expObj instanceof Number ? ((Number) expObj).longValue() : now;
if (exp < now) {
write401(response);
return;
}
String jti = String.valueOf(payload.get("jti"));
String bl = RedisUtils.getCacheObject(BLACKLIST_PREFIX + jti);
if (bl != null) {
write401(response);
return;
}
}
filterChain.doFilter(request, response);
}
private boolean requiresAuth(String path) {
if (path == null) return false;
// 保护系统会话/消息/用户/文件等接口
return path.startsWith("/system/session")
|| path.startsWith("/system/message")
|| path.startsWith("/app/user")
|| path.startsWith("/app/file");
}
private void write401(HttpServletResponse response) throws IOException {
response.setStatus(401);
response.setContentType("application/json");
response.getOutputStream().write("{\"code\":401,\"msg\":\"Unauthorized\",\"data\":null}".getBytes(StandardCharsets.UTF_8));
}
}
@@ -0,0 +1,30 @@
package org.dromara.member.config;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* JWT Web配置
*
* @author pengles
*/
@Configuration
@RequiredArgsConstructor
public class AppJwtWebConfig {
private final AppJwtFilter appJwtFilter;
@Bean
public FilterRegistrationBean<AppJwtFilter> appJwtFilterRegistration() {
FilterRegistrationBean<AppJwtFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(appJwtFilter);
// 设置过滤路径
bean.addUrlPatterns("/system/session/*", "/system/message/*", "/app/user/*", "/app/file/*");
bean.setName("AppJwtFilter");
// 设置优先级,越小越靠前
bean.setOrder(10);
return bean;
}
}
@@ -0,0 +1,266 @@
package org.dromara.member.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaIgnore;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.member.domain.bo.AppFileBo;
import org.dromara.member.domain.vo.AppFileVo;
import org.dromara.member.service.AppFileService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.List;
/**
* App端文件信息Controller
*
* @author pengles
*/
@Tag(name = "App端文件管理", description = "App端文件信息管理接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/file")
public class AppFileController extends BaseController {
private final AppFileService appFileService;
/**
* 查询App端文件信息列表
*/
@Operation(summary = "查询文件列表", description = "分页查询当前用户的文件信息列表")
@SaCheckLogin
@GetMapping("/list")
public TableDataInfo<AppFileVo> list(
@Parameter(description = "文件查询条件") @Valid AppFileBo bo,
@Parameter(description = "分页参数") @Valid PageQuery pageQuery) {
// 只能查询自己的文件
bo.setUserId(LoginHelper.getUserId());
return appFileService.queryPageList(bo, pageQuery);
}
/**
* 获取App端文件信息详细信息
*/
@Operation(summary = "获取文件详情", description = "根据文件ID获取文件详细信息")
@SaCheckLogin
@GetMapping("/{fileId}")
public R<AppFileVo> getInfo(
@Parameter(description = "文件ID", required = true)
@PathVariable @NotNull(message = "文件ID不能为空") @Positive(message = "文件ID必须为正数") Long fileId) {
AppFileVo fileVo = appFileService.queryById(fileId);
if (fileVo == null) {
return R.fail("文件不存在");
}
// 检查权限
if (!fileVo.getUserId().equals(LoginHelper.getUserId()) && !"1".equals(fileVo.getIsPublic())) {
return R.fail("无权限访问该文件");
}
return R.ok(fileVo);
}
/**
* 新增App端文件信息
*/
@Operation(summary = "新增文件信息", description = "添加新的文件信息记录")
@SaCheckLogin
@Log(title = "App端文件信息", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(
@Parameter(description = "文件信息", required = true)
@Validated(AddGroup.class) @RequestBody AppFileBo bo) {
bo.setUserId(LoginHelper.getUserId());
return toAjax(appFileService.insertByBo(bo));
}
/**
* 修改App端文件信息
*/
@Operation(summary = "修改文件信息", description = "更新文件信息记录")
@SaCheckLogin
@Log(title = "App端文件信息", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(
@Parameter(description = "文件信息", required = true)
@Validated(EditGroup.class) @RequestBody AppFileBo bo) {
// 检查权限
AppFileVo existFile = appFileService.queryById(bo.getFileId());
if (existFile == null) {
return R.fail("文件不存在");
}
if (!existFile.getUserId().equals(LoginHelper.getUserId())) {
return R.fail("无权限修改该文件");
}
return toAjax(appFileService.updateByBo(bo));
}
/**
* 删除App端文件信息
*/
@Operation(summary = "删除文件", description = "批量删除文件信息")
@SaCheckLogin
@Log(title = "App端文件信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{fileIds}")
public R<Void> remove(
@Parameter(description = "文件ID列表", required = true)
@PathVariable @NotEmpty(message = "文件ID列表不能为空") Long[] fileIds) {
return toAjax(appFileService.batchDeleteUserFiles(Arrays.asList(fileIds), LoginHelper.getUserId()));
}
/**
* 上传文件
*/
@Operation(summary = "上传文件", description = "上传单个文件")
@SaCheckLogin
@Log(title = "文件上传", businessType = BusinessType.INSERT)
@PostMapping("/upload")
public R<AppFileVo> uploadFile(
@Parameter(description = "上传的文件", required = true)
@RequestParam("file") @NotNull(message = "上传文件不能为空") MultipartFile file,
@Parameter(description = "文件分类")
@RequestParam(value = "fileCategory", required = false) @Min(value = 0, message = "文件分类不能为负数") Integer fileCategory) {
AppFileVo result = appFileService.uploadFile(file, LoginHelper.getUserId(), fileCategory);
return R.ok(result);
}
/**
* 上传头像
*/
@Operation(summary = "上传头像", description = "上传用户头像文件")
@SaCheckLogin
@Log(title = "头像上传", businessType = BusinessType.INSERT)
@PostMapping("/upload/avatar")
public R<AppFileVo> uploadAvatar(
@Parameter(description = "头像文件", required = true)
@RequestParam("file") @NotNull(message = "头像文件不能为空") MultipartFile file) {
AppFileVo result = appFileService.uploadAvatar(file, LoginHelper.getUserId());
return R.ok(result);
}
/**
* 批量上传文件
*/
@Operation(summary = "批量上传文件", description = "批量上传多个文件")
@SaCheckLogin
@Log(title = "批量文件上传", businessType = BusinessType.INSERT)
@PostMapping("/upload/batch")
public R<List<AppFileVo>> batchUploadFiles(
@Parameter(description = "上传的文件列表", required = true)
@RequestParam("files") @NotEmpty(message = "上传文件列表不能为空") MultipartFile[] files,
@Parameter(description = "文件分类")
@RequestParam(value = "fileCategory", required = false) @Min(value = 0, message = "文件分类不能为负数") Integer fileCategory) {
List<AppFileVo> result = appFileService.batchUploadFiles(files, LoginHelper.getUserId(), fileCategory);
return R.ok(result);
}
/**
* 根据文件分类查询文件列表
*/
@Operation(summary = "根据分类获取文件", description = "获取指定分类的文件列表")
@SaCheckLogin
@GetMapping("/category/{fileCategory}")
public R<List<AppFileVo>> getFilesByCategory(
@Parameter(description = "文件分类", required = true)
@PathVariable @NotNull(message = "文件分类不能为空") @Min(value = 0, message = "文件分类不能为负数") Integer fileCategory) {
List<AppFileVo> result = appFileService.queryByUserId(LoginHelper.getUserId(), fileCategory);
return R.ok(result);
}
/**
* 下载文件
*/
@Operation(summary = "下载文件", description = "根据文件ID下载文件")
@SaCheckLogin
@Log(title = "文件下载", businessType = BusinessType.OTHER)
@GetMapping("/download/{fileId}")
public R<AppFileVo> downloadFile(
@Parameter(description = "文件ID", required = true)
@PathVariable @NotNull(message = "文件ID不能为空") @Positive(message = "文件ID必须为正数") Long fileId) {
AppFileVo result = appFileService.downloadFile(fileId, LoginHelper.getUserId());
return R.ok(result);
}
/**
* 更新文件状态
*/
@Operation(summary = "更新文件状态", description = "更新文件的状态信息")
@SaCheckLogin
@Log(title = "更新文件状态", businessType = BusinessType.UPDATE)
@PutMapping("/status/{fileId}")
public R<Void> updateFileStatus(
@Parameter(description = "文件ID", required = true)
@PathVariable @NotNull(message = "文件ID不能为空") @Positive(message = "文件ID必须为正数") Long fileId,
@Parameter(description = "文件状态(0-正常,1-禁用)", required = true)
@RequestParam @NotBlank(message = "文件状态不能为空") @Pattern(regexp = "^(0|1)$", message = "文件状态只能为0或1") String status) {
return toAjax(appFileService.updateFileStatus(fileId, status, LoginHelper.getUserId()));
}
/**
* 更新文件公开状态
*/
@Operation(summary = "更新文件公开状态", description = "设置文件是否公开")
@SaCheckLogin
@Log(title = "更新文件公开状态", businessType = BusinessType.UPDATE)
@PutMapping("/public/{fileId}")
public R<Void> updateFilePublicStatus(
@Parameter(description = "文件ID", required = true)
@PathVariable @NotNull(message = "文件ID不能为空") @Positive(message = "文件ID必须为正数") Long fileId,
@Parameter(description = "是否公开(0-私有,1-公开)", required = true)
@RequestParam @NotBlank(message = "公开状态不能为空") @Pattern(regexp = "^(0|1)$", message = "公开状态只能为0或1") String isPublic) {
return toAjax(appFileService.updateFilePublicStatus(fileId, isPublic, LoginHelper.getUserId()));
}
/**
* 获取用户文件统计信息
*/
@Operation(summary = "获取文件统计", description = "获取当前用户的文件统计信息")
@SaCheckLogin
@GetMapping("/statistics")
public R<AppFileVo> getUserFileStatistics() {
AppFileVo result = appFileService.getUserFileStatistics(LoginHelper.getUserId());
return R.ok(result);
}
/**
* 检查文件是否存在
*/
@Operation(summary = "检查文件是否存在", description = "根据MD5值检查文件是否已存在")
@SaCheckLogin
@GetMapping("/check/{fileMd5}")
public R<AppFileVo> checkFileExists(
@Parameter(description = "文件MD5值", required = true)
@PathVariable @NotBlank(message = "文件MD5不能为空") @Size(min = 32, max = 32, message = "MD5长度必须为32位") String fileMd5) {
AppFileVo result = appFileService.checkFileExists(fileMd5, LoginHelper.getUserId());
return R.ok(result);
}
/**
* 查询公开文件列表
*/
@Operation(summary = "查询公开文件列表", description = "获取指定分类的公开文件列表")
@SaIgnore
@GetMapping("/public/category/{fileCategory}")
public R<List<AppFileVo>> getPublicFilesByCategory(
@Parameter(description = "文件分类", required = true)
@PathVariable @NotNull(message = "文件分类不能为空") @Min(value = 0, message = "文件分类不能为负数") Integer fileCategory) {
List<AppFileVo> result = appFileService.queryByCategory(fileCategory);
return R.ok(result);
}
}
@@ -0,0 +1,180 @@
package org.dromara.member.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.member.utils.JwtUtil;
import org.dromara.member.domain.AppUser;
import org.dromara.member.mapper.AppUserMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
* JWT认证控制器
*
* @author pengles
*/
@SaIgnore
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/auth/jwt")
public class AppJwtController {
private final AppUserMapper appUserMapper;
@Value("${ruoyi.app.jwt.secret:ruoyi-secret}")
private String jwtSecret;
@Value("${ruoyi.app.jwt.access-ttl-seconds:7200}")
private long accessTtlSeconds;
@Value("${ruoyi.app.jwt.refresh-ttl-seconds:604800}")
private long refreshTtlSeconds;
private static final String CAPTCHA_PREFIX = "APP:JWT:CAPTCHA:";
private static final String BLACKLIST_PREFIX = "APP:JWT:BLACKLIST:";
private static final String TOKEN_JTI_PREFIX = "APP:JWT:JTI:";
private static final String USER_TOKENS_PREFIX = "APP:JWT:USER_TOKENS:";
private static final String LOGIN_FAIL_PREFIX = "APP:JWT:LOGIN_FAIL:";
@Operation(summary = "生成图形验证码(4位字母数字)")
@GetMapping("/captcha")
public R<Map<String, String>> captcha() {
String code = randomCode(4);
String uuid = UUID.randomUUID().toString();
RedisUtils.setCacheObject(CAPTCHA_PREFIX + uuid, code, Duration.ofMinutes(5));
// 这里简化为直接返回文本验证码;实际可返回Base64图片
return R.ok(Map.of("uuid", uuid, "code", code));
}
@Operation(summary = "注册")
@PostMapping("/register")
public R<Void> register(
@Parameter(description = "手机号(+86或11位)") @RequestParam @Pattern(regexp = "^(\\+86\\d{11}|\\d{11})$") String phone,
@Parameter(description = "密码") @RequestParam @Size(min = 6, max = 64) String password,
@Parameter(description = "验证码uuid") @RequestParam @NotBlank String captchaUuid,
@Parameter(description = "验证码") @RequestParam @NotBlank String captchaCode) {
if (!verifyCaptcha(captchaUuid, captchaCode)) {
return R.fail("验证码错误");
}
AppUser exist = findByPhone(phone);
if (exist != null) {
return R.fail("手机号已注册");
}
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder enc = new org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder(12);
AppUser u = new AppUser();
u.setPhone(phone);
u.setPassword(enc.encode(password));
boolean ok = appUserMapper.insert(u) > 0;
return ok ? R.ok() : R.fail("注册失败");
}
@Operation(summary = "登录")
@PostMapping("/login")
public R<Map<String, Object>> login(
@Parameter(description = "手机号(+86或11位)") @RequestParam @Pattern(regexp = "^(\\+86\\d{11}|\\d{11})$") String phone,
@Parameter(description = "密码") @RequestParam @Size(min = 6, max = 64) String password,
@Parameter(description = "验证码uuid") @RequestParam @NotBlank String captchaUuid,
@Parameter(description = "验证码") @RequestParam @NotBlank String captchaCode) {
if (!verifyCaptcha(captchaUuid, captchaCode)) {
return R.fail("验证码错误");
}
String failKey = LOGIN_FAIL_PREFIX + phone;
Integer cnt = RedisUtils.getCacheObject(failKey);
if (cnt != null && cnt >= 5) {
return R.fail("登录失败次数过多,请稍后再试");
}
AppUser user = findByPhone(phone);
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder enc = new org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder(12);
if (user == null || !enc.matches(password, user.getPassword())) {
int n = (cnt == null ? 0 : cnt) + 1;
RedisUtils.setCacheObject(failKey, n, Duration.ofHours(1));
return R.fail("手机号或密码错误");
}
JwtUtil jwt = new JwtUtil(jwtSecret);
String jti = UUID.randomUUID().toString();
String access = jwt.generateToken(String.valueOf(user.getUserId()), accessTtlSeconds, jti);
String refresh = jwt.generateToken("refresh:" + user.getUserId(), refreshTtlSeconds, UUID.randomUUID().toString());
RedisUtils.setCacheObject(TOKEN_JTI_PREFIX + jti, "1", Duration.ofSeconds(accessTtlSeconds));
limitUserDevices(user.getUserId(), jti);
Map<String, Object> data = new LinkedHashMap<>();
data.put("accessToken", access);
data.put("refreshToken", refresh);
data.put("expiresIn", accessTtlSeconds);
return R.ok(data);
}
@Operation(summary = "刷新Token")
@PostMapping("/refresh")
public R<Map<String, Object>> refresh(@Parameter(description = "刷新令牌") @RequestParam String refreshToken) {
JwtUtil jwt = new JwtUtil(jwtSecret);
if (!jwt.verify(refreshToken)) return R.fail("刷新令牌无效");
Map<String, Object> payload = jwt.parsePayload(refreshToken);
String sub = String.valueOf(payload.get("sub"));
if (sub == null || !sub.startsWith("refresh:")) return R.fail("刷新令牌错误");
String userId = sub.substring("refresh:".length());
String jti = UUID.randomUUID().toString();
String access = jwt.generateToken(userId, accessTtlSeconds, jti);
RedisUtils.setCacheObject(TOKEN_JTI_PREFIX + jti, "1", Duration.ofSeconds(accessTtlSeconds));
limitUserDevices(Long.parseLong(userId), jti);
return R.ok(Map.of("accessToken", access, "expiresIn", accessTtlSeconds));
}
@Operation(summary = "注销")
@PostMapping("/logout")
public R<Void> logout(@Parameter(description = "访问令牌") @RequestParam String accessToken) {
JwtUtil jwt = new JwtUtil(jwtSecret);
if (!jwt.verify(accessToken)) return R.fail("令牌无效");
Map<String, Object> payload = jwt.parsePayload(accessToken);
String jti = String.valueOf(payload.get("jti"));
Object expObj = payload.get("exp");
long now = java.time.Instant.now().getEpochSecond();
long exp = expObj instanceof Number ? ((Number) expObj).longValue() : now;
long remain = Math.max(0, exp - now);
RedisUtils.setCacheObject(BLACKLIST_PREFIX + jti, "1", Duration.ofSeconds(remain));
RedisUtils.deleteObject(TOKEN_JTI_PREFIX + jti);
return R.ok();
}
private boolean verifyCaptcha(String uuid, String code) {
String real = RedisUtils.getCacheObject(CAPTCHA_PREFIX + uuid);
return real != null && real.equalsIgnoreCase(code);
}
private String randomCode(int n) {
String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
StringBuilder sb = new StringBuilder();
java.util.Random r = new java.util.Random();
for (int i = 0; i < n; i++) sb.append(chars.charAt(r.nextInt(chars.length())));
return sb.toString();
}
private void limitUserDevices(Long userId, String jti) {
String listKey = USER_TOKENS_PREFIX + userId;
java.util.List<String> list = RedisUtils.getCacheObject(listKey);
if (list == null) list = new java.util.ArrayList<>();
list.add(0, jti);
while (list.size() > 3) {
String old = list.remove(list.size() - 1);
RedisUtils.setCacheObject(BLACKLIST_PREFIX + old, "1", Duration.ofHours(2));
RedisUtils.deleteObject(TOKEN_JTI_PREFIX + old);
}
RedisUtils.setCacheObject(listKey, list, Duration.ofDays(8));
}
private AppUser findByPhone(String phone) {
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<AppUser> lqw = com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery();
lqw.eq(AppUser::getPhone, phone);
return appUserMapper.selectOne(lqw);
}
}
@@ -0,0 +1,175 @@
package org.dromara.member.controller.admin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 管理端系统配置
*
* @author pengles
*/
@Tag(name = "管理端系统配置", description = "系统配置管理接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/admin/config")
public class AdminConfigController extends BaseController {
/**
* 查询系统配置列表
*/
@Operation(summary = "查询配置列表", description = "分页查询系统配置列表")
@SaCheckPermission("admin:config:list")
@GetMapping("/list")
public TableDataInfo<Map<String, Object>> list(
@Parameter(description = "分页参数") PageQuery pageQuery) {
// 模拟配置数据
TableDataInfo<Map<String, Object>> dataInfo = new TableDataInfo<>();
dataInfo.setCode(200);
dataInfo.setMsg("查询成功");
dataInfo.setTotal(0L);
dataInfo.setRows(List.of());
return dataInfo;
}
/**
* 导出系统配置列表
*/
@Operation(summary = "导出配置列表", description = "导出系统配置到Excel")
@SaCheckPermission("admin:config:export")
@Log(title = "系统配置", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response) {
// 导出逻辑
}
/**
* 获取系统配置详细信息
*/
@Operation(summary = "获取配置详情", description = "根据配置ID获取详细信息")
@SaCheckPermission("admin:config:query")
@GetMapping("/{configId}")
public R<Map<String, Object>> getInfo(
@Parameter(description = "配置ID", required = true)
@NotNull(message = "主键不能为空") @PathVariable Long configId) {
Map<String, Object> config = new HashMap<>();
config.put("configId", configId);
config.put("configName", "示例配置");
config.put("configKey", "example.config");
config.put("configValue", "示例值");
return R.ok(config);
}
/**
* 新增系统配置
*/
@Operation(summary = "新增配置", description = "创建新的系统配置")
@SaCheckPermission("admin:config:add")
@Log(title = "系统配置", businessType = BusinessType.INSERT)
@PostMapping()
public R<Void> add(
@Parameter(description = "配置信息", required = true)
@Validated(AddGroup.class) @RequestBody Map<String, Object> config) {
return R.ok();
}
/**
* 修改系统配置
*/
@Operation(summary = "修改配置", description = "更新系统配置信息")
@SaCheckPermission("admin:config:edit")
@Log(title = "系统配置", businessType = BusinessType.UPDATE)
@PutMapping()
public R<Void> edit(
@Parameter(description = "配置信息", required = true)
@Validated(EditGroup.class) @RequestBody Map<String, Object> config) {
return R.ok();
}
/**
* 删除系统配置
*/
@Operation(summary = "删除配置", description = "批量删除系统配置")
@SaCheckPermission("admin:config:remove")
@Log(title = "系统配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{configIds}")
public R<Void> remove(
@Parameter(description = "配置ID列表", required = true)
@NotEmpty(message = "主键不能为空") @PathVariable Long[] configIds) {
return R.ok();
}
/**
* 刷新参数缓存
*/
@Operation(summary = "刷新缓存", description = "刷新系统配置缓存")
@SaCheckPermission("admin:config:remove")
@Log(title = "系统配置", businessType = BusinessType.CLEAN)
@DeleteMapping("/refreshCache")
public R<Void> refreshCache() {
return R.ok();
}
/**
* 根据参数键名查询参数值
*/
@Operation(summary = "根据键名查询配置", description = "根据配置键名获取配置值")
@GetMapping("/configKey/{configKey}")
public R<String> getConfigKey(
@Parameter(description = "配置键名", required = true)
@PathVariable String configKey) {
return R.ok("配置值");
}
/**
* 获取系统信息
*/
@Operation(summary = "获取系统信息", description = "获取系统运行信息")
@SaCheckPermission("admin:config:query")
@GetMapping("/systemInfo")
public R<Map<String, Object>> getSystemInfo() {
Map<String, Object> systemInfo = new HashMap<>();
systemInfo.put("systemName", "RuoYi-Unity-AI管理系统");
systemInfo.put("version", "5.X");
systemInfo.put("author", "RuoYi");
systemInfo.put("javaVersion", System.getProperty("java.version"));
systemInfo.put("osName", System.getProperty("os.name"));
return R.ok(systemInfo);
}
/**
* 获取系统统计信息
*/
@Operation(summary = "获取统计信息", description = "获取系统统计数据")
@SaCheckPermission("admin:config:query")
@GetMapping("/statistics")
public R<Map<String, Object>> getStatistics() {
Map<String, Object> statistics = new HashMap<>();
statistics.put("totalUsers", 0);
statistics.put("totalRoles", 0);
statistics.put("onlineUsers", 0);
statistics.put("todayVisits", 0);
return R.ok(statistics);
}
}
@@ -0,0 +1,172 @@
package org.dromara.member.controller.admin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.member.domain.bo.AppRoleBo;
import org.dromara.member.domain.vo.AppRoleVo;
import org.dromara.member.service.AppRoleService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 管理端角色信息
*
* @author pengles
*/
@Tag(name = "管理端角色管理", description = "管理端角色信息管理接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/admin/role")
public class AdminRoleController extends BaseController {
private final AppRoleService appRoleService;
/**
* 查询管理端角色信息列表
*/
@Operation(summary = "查询角色列表", description = "分页查询管理端角色信息列表")
@SaCheckPermission("admin:role:list")
@GetMapping("/list")
public TableDataInfo<AppRoleVo> list(@Parameter(description = "角色查询条件") AppRoleBo bo,
@Parameter(description = "分页参数") PageQuery pageQuery) {
return appRoleService.queryPageList(bo, pageQuery);
}
/**
* 导出管理端角色信息列表
*/
@Operation(summary = "导出角色列表", description = "导出管理端角色信息到Excel")
@SaCheckPermission("admin:role:export")
@Log(title = "管理端角色信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(@Parameter(description = "角色查询条件") AppRoleBo bo,
@Parameter(description = "响应对象") HttpServletResponse response) {
List<AppRoleVo> list = appRoleService.queryList(bo);
ExcelUtil.exportExcel(list, "管理端角色信息", AppRoleVo.class, response);
}
/**
* 获取管理端角色信息详细信息
*/
@Operation(summary = "获取角色详情", description = "根据角色ID获取详细信息")
@SaCheckPermission("admin:role:query")
@GetMapping("/{roleId}")
public R<AppRoleVo> getInfo(@Parameter(description = "角色ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long roleId) {
return R.ok(appRoleService.queryById(roleId));
}
/**
* 新增管理端角色信息
*/
@Operation(summary = "新增角色", description = "新增管理端角色信息")
@SaCheckPermission("admin:role:add")
@Log(title = "管理端角色信息", businessType = BusinessType.INSERT)
@PostMapping()
public R<Void> add(@Parameter(description = "角色信息", required = true)
@Validated(AddGroup.class) @RequestBody AppRoleBo bo) {
return toAjax(appRoleService.insertByBo(bo));
}
/**
* 修改管理端角色信息
*/
@Operation(summary = "修改角色", description = "修改管理端角色信息")
@SaCheckPermission("admin:role:edit")
@Log(title = "管理端角色信息", businessType = BusinessType.UPDATE)
@PutMapping()
public R<Void> edit(@Parameter(description = "角色信息", required = true)
@Validated(EditGroup.class) @RequestBody AppRoleBo bo) {
return toAjax(appRoleService.updateByBo(bo));
}
/**
* 删除管理端角色信息
*/
@Operation(summary = "删除角色", description = "批量删除管理端角色信息")
@SaCheckPermission("admin:role:remove")
@Log(title = "管理端角色信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{roleIds}")
public R<Void> remove(@Parameter(description = "角色ID数组", required = true)
@NotEmpty(message = "主键不能为空")
@PathVariable Long[] roleIds) {
return toAjax(appRoleService.deleteWithValidByIds(List.of(roleIds), true));
}
/**
* 获取角色选择框列表
*/
@Operation(summary = "获取角色选项", description = "获取角色选择框列表")
@SaCheckPermission("admin:role:query")
@GetMapping("/optionselect")
public R<List<AppRoleVo>> optionselect() {
List<AppRoleVo> roles = appRoleService.queryList(new AppRoleBo());
return R.ok(roles);
}
/**
* 查询已分配用户角色列表
*/
@Operation(summary = "查询已分配用户", description = "查询已分配用户角色列表")
@SaCheckPermission("admin:role:list")
@GetMapping("/authUser/allocatedList")
public TableDataInfo<AppRoleVo> allocatedList(@Parameter(description = "角色查询条件") AppRoleBo bo,
@Parameter(description = "分页参数") PageQuery pageQuery) {
return appRoleService.queryPageList(bo, pageQuery);
}
/**
* 查询未分配用户角色列表
*/
@Operation(summary = "查询未分配用户", description = "查询未分配用户角色列表")
@SaCheckPermission("admin:role:list")
@GetMapping("/authUser/unallocatedList")
public TableDataInfo<AppRoleVo> unallocatedList(@Parameter(description = "角色查询条件") AppRoleBo bo,
@Parameter(description = "分页参数") PageQuery pageQuery) {
return appRoleService.queryPageList(bo, pageQuery);
}
/**
* 取消授权用户
*/
@Operation(summary = "取消授权用户", description = "批量取消用户角色授权")
@SaCheckPermission("admin:role:edit")
@Log(title = "管理端角色信息", businessType = BusinessType.GRANT)
@PutMapping("/authUser/cancelAll")
public R<Void> cancelAuthUserAll(@Parameter(description = "角色ID", required = true) Long roleId,
@Parameter(description = "用户ID数组", required = true) Long[] userIds) {
return toAjax(appRoleService.deleteAuthUsers(roleId, userIds));
}
/**
* 批量选择用户授权
*/
@Operation(summary = "批量授权用户", description = "批量选择用户进行角色授权")
@SaCheckPermission("admin:role:edit")
@Log(title = "管理端角色信息", businessType = BusinessType.GRANT)
@PutMapping("/authUser/selectAll")
public R<Void> selectAuthUserAll(@Parameter(description = "角色ID", required = true) Long roleId,
@Parameter(description = "用户ID数组", required = true) Long[] userIds) {
return toAjax(appRoleService.insertAuthUsers(roleId, userIds));
}
}
@@ -0,0 +1,191 @@
package org.dromara.member.controller.admin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.member.domain.bo.AppUserBo;
import org.dromara.member.domain.vo.AppUserVo;
import org.dromara.member.service.AppUserService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 管理端用户信息
*
* @author pengles
*/
@Tag(name = "管理端用户管理", description = "管理端用户信息管理接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/admin/user")
public class AdminUserController extends BaseController {
private final AppUserService appUserService;
/**
* 查询管理端用户信息列表
*/
@Operation(summary = "查询用户列表", description = "分页查询管理端用户信息列表")
@SaCheckPermission("admin:user:list")
@GetMapping("/list")
public TableDataInfo<AppUserVo> list(@Parameter(description = "用户查询条件") AppUserBo bo,
@Parameter(description = "分页参数") PageQuery pageQuery) {
return appUserService.queryPageList(bo, pageQuery);
}
/**
* 导出管理端用户信息列表
*/
@Operation(summary = "导出用户列表", description = "导出管理端用户信息到Excel")
@SaCheckPermission("admin:user:export")
@Log(title = "管理端用户信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(@Parameter(description = "用户查询条件") AppUserBo bo,
@Parameter(description = "响应对象") HttpServletResponse response) {
List<AppUserVo> list = appUserService.queryList(bo);
ExcelUtil.exportExcel(list, "管理端用户信息", AppUserVo.class, response);
}
/**
* 获取管理端用户信息详细信息
*/
@Operation(summary = "获取用户详情", description = "根据用户ID获取详细信息")
@SaCheckPermission("admin:user:query")
@GetMapping("/{userId}")
public R<AppUserVo> getInfo(@Parameter(description = "用户ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long userId) {
return R.ok(appUserService.queryById(userId));
}
/**
* 新增管理端用户信息
*/
@Operation(summary = "新增用户", description = "新增管理端用户信息")
@SaCheckPermission("admin:user:add")
@Log(title = "管理端用户信息", businessType = BusinessType.INSERT)
@PostMapping()
public R<Void> add(@Parameter(description = "用户信息", required = true)
@Validated(AddGroup.class) @RequestBody AppUserBo bo) {
return toAjax(appUserService.insertByBo(bo));
}
/**
* 修改管理端用户信息
*/
@Operation(summary = "修改用户", description = "修改管理端用户信息")
@SaCheckPermission("admin:user:edit")
@Log(title = "管理端用户信息", businessType = BusinessType.UPDATE)
@PutMapping()
public R<Void> edit(@Parameter(description = "用户信息", required = true)
@Validated(EditGroup.class) @RequestBody AppUserBo bo) {
return toAjax(appUserService.updateByBo(bo));
}
/**
* 删除管理端用户信息
*/
@Operation(summary = "删除用户", description = "批量删除管理端用户信息")
@SaCheckPermission("admin:user:remove")
@Log(title = "管理端用户信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{userIds}")
public R<Void> remove(@Parameter(description = "用户ID数组", required = true)
@NotEmpty(message = "主键不能为空")
@PathVariable Long[] userIds) {
return toAjax(appUserService.deleteWithValidByIds(List.of(userIds), true));
}
/**
* 获取个人信息
*/
@Operation(summary = "获取个人信息", description = "获取当前登录用户的个人信息")
@GetMapping("/profile")
public R<AppUserVo> profile() {
LoginUser loginUser = LoginHelper.getLoginUser();
AppUserVo user = appUserService.queryById(loginUser.getUserId());
return R.ok(user);
}
/**
* 修改个人信息
*/
@Operation(summary = "修改个人信息", description = "修改当前登录用户的个人信息")
@Log(title = "个人信息", businessType = BusinessType.UPDATE)
@PutMapping("/profile")
public R<Void> updateProfile(@Parameter(description = "用户信息", required = true)
@RequestBody AppUserBo bo) {
LoginUser loginUser = LoginHelper.getLoginUser();
bo.setUserId(loginUser.getUserId());
return toAjax(appUserService.updateByBo(bo));
}
/**
* 重置密码
*/
@Operation(summary = "重置密码", description = "重置用户密码")
@SaCheckPermission("admin:user:resetPwd")
@Log(title = "管理端用户信息", businessType = BusinessType.UPDATE)
@PutMapping("/resetPwd")
public R<Void> resetPwd(@Parameter(description = "用户信息", required = true)
@RequestBody AppUserBo bo) {
AppUserBo userBo = new AppUserBo();
userBo.setUserId(bo.getUserId());
userBo.setPassword(bo.getPassword());
return toAjax(appUserService.updateByBo(userBo));
}
/**
* 状态修改
*/
@Operation(summary = "修改用户状态", description = "修改用户启用/禁用状态")
@SaCheckPermission("admin:user:edit")
@Log(title = "管理端用户信息", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus")
public R<Void> changeStatus(@Parameter(description = "用户信息", required = true)
@RequestBody AppUserBo bo) {
return toAjax(appUserService.updateUserStatus(bo.getUserId(), bo.getStatus()));
}
/**
* 根据用户编号获取授权角色
*/
@Operation(summary = "获取用户角色", description = "根据用户ID获取授权角色信息")
@SaCheckPermission("admin:user:query")
@GetMapping("/authRole/{userId}")
public R<AppUserVo> authRole(@Parameter(description = "用户ID", required = true)
@PathVariable Long userId) {
AppUserVo user = appUserService.queryById(userId);
return R.ok(user);
}
/**
* 获取部门树列表
*/
@Operation(summary = "获取部门树", description = "获取部门树形结构列表")
@SaCheckPermission("admin:user:list")
@GetMapping("/deptTree")
public R<List<Object>> deptTree(@Parameter(description = "用户查询条件") AppUserBo bo) {
// 这里可以根据实际需求实现部门树逻辑
return R.ok();
}
}
@@ -0,0 +1,247 @@
package org.dromara.member.controller.app;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.stp.StpUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.member.domain.bo.AppDeviceBo;
import org.dromara.member.domain.vo.AppDeviceVo;
import org.dromara.member.service.AppDeviceService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* App端设备管理
*
* @author pengles
*/
@Tag(name = "App端设备管理", description = "App端设备信息管理接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/device")
public class AppDeviceController extends BaseController {
private final AppDeviceService appDeviceService;
/**
* 查询App端设备信息列表
*/
@Operation(summary = "查询设备列表", description = "分页查询当前用户的设备信息列表")
@SaCheckLogin
@GetMapping("/list")
public TableDataInfo<AppDeviceVo> list(@Parameter(description = "设备查询条件") @Valid AppDeviceBo bo,
@Parameter(description = "分页参数") @Valid PageQuery pageQuery) {
// 只能查询当前用户的设备
bo.setUserId(StpUtil.getLoginIdAsLong());
return appDeviceService.queryPageList(bo, pageQuery);
}
/**
* 获取App端设备信息详细信息
*/
@Operation(summary = "获取设备详情", description = "根据设备ID获取详细信息")
@SaCheckLogin
@GetMapping("/{deviceId}")
public R<AppDeviceVo> getInfo(@Parameter(description = "设备ID", required = true)
@PathVariable @NotNull(message = "设备ID不能为空") @Positive(message = "设备ID必须为正数") Long deviceId) {
AppDeviceVo deviceVo = appDeviceService.queryById(deviceId);
// 验证设备是否属于当前用户
if (deviceVo != null && !deviceVo.getUserId().equals(StpUtil.getLoginIdAsLong())) {
return R.fail("无权限访问该设备信息");
}
return R.ok(deviceVo);
}
/**
* 绑定设备
*/
@Operation(summary = "绑定设备", description = "绑定新设备到当前用户")
@SaCheckLogin
@Log(title = "App端设备信息", businessType = BusinessType.INSERT)
@PostMapping("/bind")
public R<Void> bind(@Parameter(description = "设备信息", required = true)
@Validated(AddGroup.class) @RequestBody @NotNull(message = "设备信息不能为空") AppDeviceBo bo) {
// 设置当前用户ID
bo.setUserId(StpUtil.getLoginIdAsLong());
// 验证必要字段
if (StringUtils.isBlank(bo.getDeviceType())) {
return R.fail("设备类型不能为空");
}
if (StringUtils.isBlank(bo.getDeviceName())) {
return R.fail("设备名称不能为空");
}
return toAjax(appDeviceService.bindDevice(bo));
}
/**
* 修改设备信息
*/
@Operation(summary = "修改设备信息", description = "修改设备信息")
@SaCheckLogin
@Log(title = "App端设备信息", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Parameter(description = "设备信息", required = true)
@Validated(EditGroup.class) @RequestBody @NotNull(message = "设备信息不能为空") AppDeviceBo bo) {
// 验证设备是否属于当前用户
AppDeviceVo deviceVo = appDeviceService.queryById(bo.getDeviceId());
if (deviceVo == null) {
return R.fail("设备不存在");
}
if (!deviceVo.getUserId().equals(StpUtil.getLoginIdAsLong())) {
return R.fail("无权限修改该设备信息");
}
// 设置当前用户ID
bo.setUserId(StpUtil.getLoginIdAsLong());
return toAjax(appDeviceService.updateByBo(bo));
}
/**
* 解绑设备
*/
@Operation(summary = "解绑设备", description = "解绑指定设备")
@SaCheckLogin
@Log(title = "App端设备信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{deviceId}")
public R<Void> unbind(@Parameter(description = "设备ID", required = true)
@PathVariable @NotNull(message = "设备ID不能为空") @Positive(message = "设备ID必须为正数") Long deviceId) {
return toAjax(appDeviceService.unbindDevice(deviceId, StpUtil.getLoginIdAsLong()));
}
/**
* 获取我的设备列表
*/
@Operation(summary = "获取我的设备", description = "获取当前用户的所有设备列表")
@SaCheckLogin
@GetMapping("/my")
public R<List<AppDeviceVo>> getMyDevices() {
List<AppDeviceVo> devices = appDeviceService.queryByUserId(StpUtil.getLoginIdAsLong());
return R.ok(devices);
}
/**
* 获取活跃设备列表
*/
@Operation(summary = "获取活跃设备", description = "获取当前用户的活跃设备列表")
@SaCheckLogin
@GetMapping("/active")
public R<List<AppDeviceVo>> getActiveDevices() {
List<AppDeviceVo> devices = appDeviceService.getActiveDevices(StpUtil.getLoginIdAsLong());
return R.ok(devices);
}
/**
* 更新设备状态
*/
@Operation(summary = "更新设备状态", description = "更新设备的激活状态")
@SaCheckLogin
@Log(title = "App端设备信息", businessType = BusinessType.UPDATE)
@PutMapping("/{deviceId}/status")
public R<Void> updateStatus(@Parameter(description = "设备ID", required = true)
@PathVariable @NotNull(message = "设备ID不能为空") @Positive(message = "设备ID必须为正数") Long deviceId,
@Parameter(description = "设备状态(0-禁用,1-启用)", required = true)
@RequestParam @NotBlank(message = "设备状态不能为空") @Pattern(regexp = "^[01]$", message = "设备状态只能为0或1") String isActive) {
// 验证设备是否属于当前用户
AppDeviceVo deviceVo = appDeviceService.queryById(deviceId);
if (deviceVo == null) {
return R.fail("设备不存在");
}
if (!deviceVo.getUserId().equals(StpUtil.getLoginIdAsLong())) {
return R.fail("无权限修改该设备状态");
}
// 验证状态值
if (!"0".equals(isActive) && !"1".equals(isActive)) {
return R.fail("状态值无效");
}
return toAjax(appDeviceService.updateActiveStatus(deviceId, isActive));
}
/**
* 更新设备推送令牌
*/
@Operation(summary = "更新推送令牌", description = "更新设备的推送令牌")
@SaCheckLogin
@Log(title = "App端设备信息", businessType = BusinessType.UPDATE)
@PutMapping("/{deviceId}/push-token")
public R<Void> updatePushToken(@Parameter(description = "设备ID", required = true)
@PathVariable @NotNull(message = "设备ID不能为空") @Positive(message = "设备ID必须为正数") Long deviceId,
@Parameter(description = "推送令牌", required = true)
@RequestParam @NotBlank(message = "推送令牌不能为空") @Size(max = 255, message = "推送令牌长度不能超过255个字符") String pushToken) {
// 验证设备是否属于当前用户
AppDeviceVo deviceVo = appDeviceService.queryById(deviceId);
if (deviceVo == null) {
return R.fail("设备不存在");
}
if (!deviceVo.getUserId().equals(StpUtil.getLoginIdAsLong())) {
return R.fail("无权限修改该设备推送令牌");
}
if (StringUtils.isBlank(pushToken)) {
return R.fail("推送令牌不能为空");
}
return toAjax(appDeviceService.updatePushToken(deviceId, pushToken));
}
/**
* 设备心跳
*/
@Operation(summary = "设备心跳", description = "更新设备心跳时间")
@SaCheckLogin
@PostMapping("/{deviceId}/heartbeat")
public R<Void> heartbeat(@Parameter(description = "设备ID", required = true)
@PathVariable @NotNull(message = "设备ID不能为空") @Positive(message = "设备ID必须为正数") Long deviceId) {
// 验证设备是否属于当前用户
AppDeviceVo deviceVo = appDeviceService.queryById(deviceId);
if (deviceVo == null) {
return R.fail("设备不存在");
}
if (!deviceVo.getUserId().equals(StpUtil.getLoginIdAsLong())) {
return R.fail("无权限操作该设备");
}
return toAjax(appDeviceService.heartbeat(deviceId));
}
/**
* 根据推送令牌获取设备信息
*/
@Operation(summary = "根据推送令牌获取设备", description = "根据推送令牌获取设备信息")
@SaCheckLogin
@GetMapping("/by-push-token")
public R<AppDeviceVo> getDeviceByPushToken(@Parameter(description = "推送令牌", required = true)
@RequestParam @NotBlank(message = "推送令牌不能为空") @Size(max = 255, message = "推送令牌长度不能超过255个字符") String pushToken) {
if (StringUtils.isBlank(pushToken)) {
return R.fail("推送令牌不能为空");
}
AppDeviceVo deviceVo = appDeviceService.queryByPushToken(pushToken);
// 验证设备是否属于当前用户
if (deviceVo != null && !deviceVo.getUserId().equals(StpUtil.getLoginIdAsLong())) {
return R.fail("无权限访问该设备信息");
}
return R.ok(deviceVo);
}
}
@@ -0,0 +1,222 @@
package org.dromara.member.controller.app;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.member.domain.bo.AppMessageBo;
import org.dromara.member.domain.vo.AppMessageVo;
import org.dromara.member.service.AppMessageService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* App端消息管理
*
* @author pengles
*/
@Tag(name = "App端消息管理", description = "App端消息管理相关接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/message")
public class AppMessageController extends BaseController {
private final AppMessageService appMessageService;
/**
* 查询App端消息列表
*/
@Operation(summary = "查询消息列表", description = "查询App端消息列表")
@SaCheckPermission("app:message:list")
@GetMapping("/list")
public TableDataInfo<AppMessageVo> list(@Parameter(description = "查询条件") @Valid AppMessageBo bo,
@Parameter(description = "分页参数") @Valid PageQuery pageQuery) {
return appMessageService.queryPageList(bo, pageQuery);
}
/**
* 获取当前用户的消息列表
*/
@Operation(summary = "获取我的消息", description = "获取当前用户的消息列表")
@GetMapping("/myMessages")
public TableDataInfo<AppMessageVo> getMyMessages(@Parameter(description = "分页参数") @Valid PageQuery pageQuery) {
LoginUser loginUser = LoginHelper.getLoginUser();
AppMessageBo bo = new AppMessageBo();
bo.setReceiverId(loginUser.getUserId());
return appMessageService.queryPageList(bo, pageQuery);
}
/**
* 获取当前用户未读消息数量
*/
@Operation(summary = "获取未读消息数量", description = "获取当前用户未读消息数量")
@GetMapping("/unreadCount")
public R<Long> getUnreadCount() {
LoginUser loginUser = LoginHelper.getLoginUser();
Long count = appMessageService.getUnreadCount(loginUser.getUserId());
return R.ok(count);
}
/**
* 导出App端消息列表
*/
@Operation(summary = "导出消息列表", description = "导出App端消息列表")
@SaCheckPermission("app:message:export")
@Log(title = "App端消息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(@Parameter(description = "查询条件") @Valid AppMessageBo bo, HttpServletResponse response) {
List<AppMessageVo> list = appMessageService.queryList(bo);
ExcelUtil.exportExcel(list, "App端消息", AppMessageVo.class, response);
}
/**
* 获取App端消息详细信息
*/
@Operation(summary = "获取消息详情", description = "获取App端消息详细信息")
@SaCheckPermission("app:message:query")
@GetMapping("/{messageId}")
public R<AppMessageVo> getInfo(@Parameter(description = "消息ID", required = true)
@PathVariable @NotNull(message = "消息ID不能为空") @Positive(message = "消息ID必须为正数") Long messageId) {
return R.ok(appMessageService.queryById(messageId));
}
/**
* 新增App端消息
*/
@Operation(summary = "新增消息", description = "新增App端消息")
@SaCheckPermission("app:message:add")
@Log(title = "App端消息", businessType = BusinessType.INSERT)
@PostMapping()
public R<Void> add(@Parameter(description = "消息信息", required = true)
@Validated(AddGroup.class) @RequestBody @NotNull(message = "消息信息不能为空") AppMessageBo bo) {
return toAjax(appMessageService.insertByBo(bo));
}
/**
* 修改App端消息
*/
@Operation(summary = "修改消息", description = "修改App端消息")
@SaCheckPermission("app:message:edit")
@Log(title = "App端消息", businessType = BusinessType.UPDATE)
@PutMapping()
public R<Void> edit(@Parameter(description = "消息信息", required = true)
@Validated(EditGroup.class) @RequestBody @NotNull(message = "消息信息不能为空") AppMessageBo bo) {
return toAjax(appMessageService.updateByBo(bo));
}
/**
* 删除App端消息
*/
@Operation(summary = "删除消息", description = "删除App端消息")
@SaCheckPermission("app:message:remove")
@Log(title = "App端消息", businessType = BusinessType.DELETE)
@DeleteMapping("/{messageIds}")
public R<Void> remove(@Parameter(description = "消息ID数组", required = true)
@PathVariable @NotEmpty(message = "消息ID不能为空") Long[] messageIds) {
return toAjax(appMessageService.deleteWithValidByIds(List.of(messageIds), true));
}
/**
* 标记消息为已读
*/
@Operation(summary = "标记消息已读", description = "标记指定消息为已读")
@Log(title = "标记消息已读", businessType = BusinessType.UPDATE)
@PutMapping("/markRead/{messageId}")
public R<Void> markRead(@Parameter(description = "消息ID", required = true)
@PathVariable @NotNull(message = "消息ID不能为空") @Positive(message = "消息ID必须为正数") Long messageId) {
LoginUser loginUser = LoginHelper.getLoginUser();
return toAjax(appMessageService.markAsRead(messageId, loginUser.getUserId()));
}
/**
* 批量标记消息为已读
*/
@Operation(summary = "批量标记已读", description = "批量标记消息为已读")
@Log(title = "批量标记消息已读", businessType = BusinessType.UPDATE)
@PutMapping("/markReadBatch")
public R<Void> markReadBatch(@Parameter(description = "消息ID数组", required = true)
@RequestBody @NotEmpty(message = "消息ID列表不能为空") Long[] messageIds) {
LoginUser loginUser = LoginHelper.getLoginUser();
return toAjax(appMessageService.markAsReadBatch(List.of(messageIds), loginUser.getUserId()));
}
/**
* 标记所有消息为已读
*/
@Operation(summary = "标记全部已读", description = "标记所有消息为已读")
@Log(title = "标记所有消息已读", businessType = BusinessType.UPDATE)
@PutMapping("/markAllRead")
public R<Void> markAllRead() {
LoginUser loginUser = LoginHelper.getLoginUser();
return toAjax(appMessageService.markAllAsRead(loginUser.getUserId()));
}
/**
* 发送系统消息
*/
@Operation(summary = "发送系统消息", description = "发送系统消息")
@SaCheckPermission("app:message:send")
@Log(title = "发送系统消息", businessType = BusinessType.INSERT)
@PostMapping("/sendSystem")
public R<Void> sendSystemMessage(@Parameter(description = "消息信息", required = true)
@RequestBody @NotNull(message = "消息信息不能为空") @Valid AppMessageBo bo) {
return toAjax(appMessageService.sendSystemMessage(bo));
}
/**
* 发送个人消息
*/
@Operation(summary = "发送个人消息", description = "发送个人消息")
@Log(title = "发送个人消息", businessType = BusinessType.INSERT)
@PostMapping("/sendPersonal")
public R<Void> sendPersonalMessage(@Parameter(description = "消息信息", required = true)
@RequestBody @NotNull(message = "消息信息不能为空") @Valid AppMessageBo bo) {
LoginUser loginUser = LoginHelper.getLoginUser();
bo.setSenderId(loginUser.getUserId());
return toAjax(appMessageService.sendPersonalMessage(bo));
}
/**
* 推送消息
*/
@Operation(summary = "推送消息", description = "推送消息")
@SaCheckPermission("app:message:push")
@Log(title = "推送消息", businessType = BusinessType.OTHER)
@PostMapping("/push")
public R<Void> pushMessage(@Parameter(description = "消息信息", required = true)
@Validated @RequestBody @NotNull(message = "消息信息不能为空") AppMessageBo bo) {
return toAjax(appMessageService.pushMessage(bo));
}
/**
* 群发消息
*/
@Operation(summary = "群发消息", description = "群发消息")
@SaCheckPermission("app:message:broadcast")
@Log(title = "群发消息", businessType = BusinessType.INSERT)
@PostMapping("/broadcast")
public R<Void> broadcastMessage(@Parameter(description = "消息信息", required = true)
@Validated @RequestBody @NotNull(message = "消息信息不能为空") AppMessageBo bo) {
return toAjax(appMessageService.broadcastMessage(bo));
}
}
@@ -0,0 +1,257 @@
package org.dromara.member.controller.app;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.member.domain.bo.AppOperLogBo;
import org.dromara.member.domain.vo.AppOperLogVo;
import org.dromara.member.service.AppOperLogService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;
/**
* App端操作日志
*
* @author pengles
*/
@Tag(name = "App端操作日志管理", description = "App端操作日志管理相关接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/operLog")
public class AppOperLogController extends BaseController {
private final AppOperLogService appOperLogService;
/**
* 查询App端操作日志列表
*/
@Operation(summary = "查询操作日志列表", description = "查询App端操作日志列表")
@SaCheckPermission("app:operLog:list")
@GetMapping("/list")
public TableDataInfo<AppOperLogVo> list(@Parameter(description = "查询条件") @Valid AppOperLogBo bo,
@Parameter(description = "分页参数") @Valid PageQuery pageQuery) {
return appOperLogService.queryPageList(bo, pageQuery);
}
/**
* 获取当前用户的操作日志列表
*/
@Operation(summary = "获取我的操作日志", description = "获取当前用户的操作日志列表")
@SaCheckLogin
@GetMapping("/my")
public TableDataInfo<AppOperLogVo> getMyOperLogs(@Parameter(description = "查询条件") @Valid AppOperLogBo bo,
@Parameter(description = "分页参数") @Valid PageQuery pageQuery) {
// 只查询当前用户的操作日志
bo.setOperName(StpUtil.getLoginIdAsString());
return appOperLogService.queryPageList(bo, pageQuery);
}
/**
* 导出App端操作日志列表
*/
@Operation(summary = "导出操作日志", description = "导出App端操作日志列表")
@SaCheckPermission("app:operLog:export")
@Log(title = "App端操作日志", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(@Parameter(description = "查询条件") @Valid AppOperLogBo bo, HttpServletResponse response) {
List<AppOperLogVo> list = appOperLogService.queryList(bo);
ExcelUtil.exportExcel(list, "App端操作日志", AppOperLogVo.class, response);
}
/**
* 获取App端操作日志详细信息
*/
@Operation(summary = "获取操作日志详情", description = "获取App端操作日志详细信息")
@SaCheckPermission("app:operLog:query")
@GetMapping("/{operId}")
public R<AppOperLogVo> getInfo(@Parameter(description = "操作日志ID", required = true)
@PathVariable @NotNull(message = "操作日志ID不能为空") @Positive(message = "操作日志ID必须为正数") Long operId) {
return R.ok(appOperLogService.queryById(operId));
}
/**
* 新增App端操作日志
*/
@Operation(summary = "新增操作日志", description = "新增App端操作日志")
@SaCheckPermission("app:operLog:add")
@Log(title = "App端操作日志", businessType = BusinessType.INSERT)
@PostMapping()
public R<Void> add(@Parameter(description = "操作日志信息", required = true)
@Validated(AddGroup.class) @RequestBody @NotNull(message = "操作日志信息不能为空") AppOperLogBo bo) {
return toAjax(appOperLogService.insertByBo(bo));
}
/**
* 修改App端操作日志
*/
@Operation(summary = "修改操作日志", description = "修改App端操作日志")
@SaCheckPermission("app:operLog:edit")
@Log(title = "App端操作日志", businessType = BusinessType.UPDATE)
@PutMapping()
public R<Void> edit(@Parameter(description = "操作日志信息", required = true)
@Validated(EditGroup.class) @RequestBody @NotNull(message = "操作日志信息不能为空") AppOperLogBo bo) {
return toAjax(appOperLogService.updateByBo(bo));
}
/**
* 删除App端操作日志
*/
@Operation(summary = "删除操作日志", description = "删除App端操作日志")
@SaCheckPermission("app:operLog:remove")
@Log(title = "App端操作日志", businessType = BusinessType.DELETE)
@DeleteMapping("/{operIds}")
public R<Void> remove(@Parameter(description = "操作日志ID数组", required = true)
@PathVariable @NotEmpty(message = "操作日志ID不能为空") Long[] operIds) {
return toAjax(appOperLogService.deleteWithValidByIds(List.of(operIds), true));
}
/**
* 清空App端操作日志
*/
@Operation(summary = "清空操作日志", description = "清空App端操作日志")
@SaCheckPermission("app:operLog:remove")
@Log(title = "App端操作日志", businessType = BusinessType.CLEAN)
@DeleteMapping("/clean")
public R<Void> clean() {
appOperLogService.cleanOperLog();
return R.ok();
}
/**
* 根据时间范围删除操作日志
*/
@Operation(summary = "按时间删除操作日志", description = "根据时间范围删除App端操作日志")
@SaCheckPermission("app:operLog:remove")
@Log(title = "App端操作日志", businessType = BusinessType.DELETE)
@DeleteMapping("/cleanByTime")
public R<Void> cleanByTime(@Parameter(description = "开始时间")
@RequestParam(required = false) LocalDateTime beginTime,
@Parameter(description = "结束时间")
@RequestParam(required = false) LocalDateTime endTime) {
appOperLogService.deleteOperLogByTime(beginTime, endTime);
return R.ok();
}
/**
* 获取操作统计信息
*/
@Operation(summary = "获取操作统计信息", description = "获取操作日志统计信息")
@SaCheckLogin
@GetMapping("/statistics")
public R<Long> getStatistics(@Parameter(description = "操作人员名称")
@RequestParam(required = false) @Size(max = 50, message = "操作人员名称长度不能超过50个字符") String operName,
@Parameter(description = "开始时间")
@RequestParam(required = false) LocalDateTime beginTime,
@Parameter(description = "结束时间")
@RequestParam(required = false) LocalDateTime endTime) {
// 如果没有指定操作人,则查询当前用户的统计
if (operName == null) {
operName = StpUtil.getLoginIdAsString();
}
Long count = appOperLogService.getOperLogCount(operName, beginTime, endTime);
return R.ok(count);
}
/**
* 获取用户行为分析数据
*/
@Operation(summary = "获取用户行为分析", description = "获取用户行为分析数据")
@SaCheckLogin
@GetMapping("/behavior")
public R<List<AppOperLogVo>> getBehaviorAnalysis(@Parameter(description = "操作人员名称")
@RequestParam(required = false) @Size(max = 50, message = "操作人员名称长度不能超过50个字符") String operName,
@Parameter(description = "分析天数")
@RequestParam(defaultValue = "7") @Min(value = 1, message = "天数必须大于0") @Max(value = 365, message = "天数不能超过365天") Integer days) {
// 如果没有指定操作人,则查询当前用户的行为分析
if (operName == null) {
operName = StpUtil.getLoginIdAsString();
}
List<AppOperLogVo> result = appOperLogService.getUserBehaviorAnalysis(operName, days);
return R.ok(result);
}
/**
* 获取热门操作统计
*/
@Operation(summary = "获取热门操作统计", description = "获取热门操作统计")
@SaCheckPermission("app:operLog:list")
@GetMapping("/popular")
public R<List<AppOperLogVo>> getPopularOperations(@Parameter(description = "统计天数")
@RequestParam(defaultValue = "7") @Min(value = 1, message = "天数必须大于0") @Max(value = 365, message = "天数不能超过365天") Integer days,
@Parameter(description = "返回数量限制")
@RequestParam(defaultValue = "10") @Min(value = 1, message = "限制数量必须大于0") @Max(value = 100, message = "限制数量不能超过100") Integer limit) {
List<AppOperLogVo> result = appOperLogService.getPopularOperations(days, limit);
return R.ok(result);
}
/**
* 获取错误操作统计
*/
@Operation(summary = "获取错误操作统计", description = "获取错误操作统计")
@SaCheckPermission("app:operLog:list")
@GetMapping("/errors")
public R<List<AppOperLogVo>> getErrorOperations(@Parameter(description = "统计天数")
@RequestParam(defaultValue = "7") @Min(value = 1, message = "天数必须大于0") @Max(value = 365, message = "天数不能超过365天") Integer days) {
List<AppOperLogVo> result = appOperLogService.getErrorOperations(days);
return R.ok(result);
}
/**
* 记录用户操作日志(供App端调用)
*/
@Operation(summary = "记录操作日志", description = "记录用户操作日志(供App端调用)")
@SaCheckLogin
@Log(title = "App端操作日志", businessType = BusinessType.INSERT)
@PostMapping("/record")
public R<Void> recordOperation(@Parameter(description = "操作日志信息", required = true)
@RequestBody @NotNull(message = "操作日志信息不能为空") @Valid AppOperLogBo bo) {
// 设置操作人为当前登录用户
bo.setOperName(StpUtil.getLoginIdAsString());
bo.setOperTime(LocalDateTime.now());
// 异步记录操作日志
appOperLogService.asyncRecordOperLog(bo);
return R.ok();
}
/**
* 批量记录操作日志
*/
@Operation(summary = "批量记录操作日志", description = "批量记录操作日志")
@SaCheckLogin
@Log(title = "App端操作日志", businessType = BusinessType.INSERT)
@PostMapping("/batchRecord")
public R<Void> batchRecordOperations(@Parameter(description = "操作日志列表", required = true)
@RequestBody @NotEmpty(message = "操作日志列表不能为空") @Valid List<AppOperLogBo> operLogs) {
// 设置操作人为当前登录用户
String currentUser = StpUtil.getLoginIdAsString();
operLogs.forEach(bo -> {
bo.setOperName(currentUser);
bo.setOperTime(LocalDateTime.now());
});
appOperLogService.batchInsertOperLog(operLogs);
return R.ok();
}
}
@@ -0,0 +1,189 @@
package org.dromara.member.controller.app;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.member.domain.bo.AppRoleBo;
import org.dromara.member.domain.vo.AppRoleVo;
import org.dromara.member.service.AppRoleService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* App端角色信息
*
* @author pengles
*/
@Tag(name = "App端角色管理", description = "App端角色信息的增删改查操作")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/role")
public class AppRoleController extends BaseController {
private final AppRoleService appRoleService;
/**
* 查询App端角色信息列表
*/
@Operation(summary = "查询App端角色信息列表", description = "分页查询App端角色信息列表")
@SaCheckPermission("app:role:list")
@GetMapping("/list")
public TableDataInfo<AppRoleVo> list(
@Parameter(description = "角色查询条件") @Valid AppRoleBo bo,
@Parameter(description = "分页查询参数") @Valid PageQuery pageQuery) {
return appRoleService.queryPageList(bo, pageQuery);
}
/**
* 导出App端角色信息列表
*/
@Operation(summary = "导出App端角色信息列表", description = "导出App端角色信息到Excel文件")
@SaCheckPermission("app:role:export")
@Log(title = "App端角色信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(
@Parameter(description = "角色查询条件") @Valid AppRoleBo bo,
HttpServletResponse response) {
List<AppRoleVo> list = appRoleService.queryList(bo);
ExcelUtil.exportExcel(list, "App端角色信息", AppRoleVo.class, response);
}
/**
* 获取App端角色信息详细信息
*/
@Operation(summary = "获取App端角色信息详细信息", description = "根据角色ID获取App端角色详细信息")
@SaCheckPermission("app:role:query")
@GetMapping("/{roleId}")
public R<AppRoleVo> getInfo(
@Parameter(description = "角色ID", required = true) @PathVariable @NotNull(message = "角色ID不能为空") @Positive(message = "角色ID必须为正数") Long roleId) {
return R.ok(appRoleService.queryById(roleId));
}
/**
* 新增App端角色信息
*/
@Operation(summary = "新增App端角色信息", description = "新增App端角色信息")
@SaCheckPermission("app:role:add")
@Log(title = "App端角色信息", businessType = BusinessType.INSERT)
@PostMapping()
public R<Void> add(
@Parameter(description = "角色信息", required = true) @Validated(AddGroup.class) @RequestBody @NotNull(message = "角色信息不能为空") AppRoleBo bo) {
return toAjax(appRoleService.insertByBo(bo));
}
/**
* 修改App端角色信息
*/
@Operation(summary = "修改App端角色信息", description = "修改App端角色信息")
@SaCheckPermission("app:role:edit")
@Log(title = "App端角色信息", businessType = BusinessType.UPDATE)
@PutMapping()
public R<Void> edit(
@Parameter(description = "角色信息", required = true) @Validated(EditGroup.class) @RequestBody @NotNull(message = "角色信息不能为空") AppRoleBo bo) {
return toAjax(appRoleService.updateByBo(bo));
}
/**
* 删除App端角色信息
*/
@Operation(summary = "删除App端角色信息", description = "批量删除App端角色信息")
@SaCheckPermission("app:role:remove")
@Log(title = "App端角色信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{roleIds}")
public R<Void> remove(
@Parameter(description = "角色ID数组", required = true) @PathVariable @NotEmpty(message = "角色ID不能为空") Long[] roleIds) {
return toAjax(appRoleService.deleteWithValidByIds(List.of(roleIds), true));
}
/**
* 获取角色选择框列表
*/
@Operation(summary = "获取角色选择框列表", description = "获取所有角色的选择框列表")
@SaCheckPermission("app:role:query")
@GetMapping("/optionselect")
public R<List<AppRoleVo>> optionselect() {
return R.ok(appRoleService.selectRoleAll());
}
/**
* 查询已分配用户角色列表
*/
@Operation(summary = "查询已分配用户角色列表", description = "分页查询已分配用户的角色列表")
@SaCheckPermission("app:role:list")
@GetMapping("/authUser/allocatedList")
public TableDataInfo<AppRoleVo> allocatedList(
@Parameter(description = "角色查询条件") @Valid AppRoleBo bo,
@Parameter(description = "分页查询参数") @Valid PageQuery pageQuery) {
return appRoleService.queryPageList(bo, pageQuery);
}
/**
* 查询未分配用户角色列表
*/
@Operation(summary = "查询未分配用户角色列表", description = "分页查询未分配用户的角色列表")
@SaCheckPermission("app:role:list")
@GetMapping("/authUser/unallocatedList")
public TableDataInfo<AppRoleVo> unallocatedList(
@Parameter(description = "角色查询条件") @Valid AppRoleBo bo,
@Parameter(description = "分页查询参数") @Valid PageQuery pageQuery) {
return appRoleService.queryPageList(bo, pageQuery);
}
/**
* 取消授权用户
*/
@Operation(summary = "取消授权用户", description = "取消用户的角色授权")
@SaCheckPermission("app:role:edit")
@Log(title = "App端角色信息", businessType = BusinessType.GRANT)
@PutMapping("/authUser/cancel")
public R<Void> cancelAuthUser(
@Parameter(description = "角色信息", required = true) @RequestBody @NotNull(message = "角色信息不能为空") @Valid AppRoleBo bo) {
return toAjax(appRoleService.deleteAuthUser(bo.getUserId(), bo.getRoleId()));
}
/**
* 批量取消授权用户
*/
@Operation(summary = "批量取消授权用户", description = "批量取消用户的角色授权")
@SaCheckPermission("app:role:edit")
@Log(title = "App端角色信息", businessType = BusinessType.GRANT)
@PutMapping("/authUser/cancelAll")
public R<Void> cancelAuthUserAll(
@Parameter(description = "角色ID", required = true) @NotNull(message = "角色ID不能为空") @Positive(message = "角色ID必须为正数") Long roleId,
@Parameter(description = "用户ID数组", required = true) @NotEmpty(message = "用户ID不能为空") Long[] userIds) {
return toAjax(appRoleService.deleteAuthUsers(roleId, userIds));
}
/**
* 批量选择用户授权
*/
@Operation(summary = "批量选择用户授权", description = "批量为用户分配角色授权")
@SaCheckPermission("app:role:edit")
@Log(title = "App端角色信息", businessType = BusinessType.GRANT)
@PutMapping("/authUser/selectAll")
public R<Void> selectAuthUserAll(
@Parameter(description = "角色ID", required = true) @NotNull(message = "角色ID不能为空") @Positive(message = "角色ID必须为正数") Long roleId,
@Parameter(description = "用户ID数组", required = true) @NotEmpty(message = "用户ID不能为空") Long[] userIds) {
return toAjax(appRoleService.insertAuthUsers(roleId, userIds));
}
}
@@ -0,0 +1,220 @@
package org.dromara.member.controller.app;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.member.domain.bo.AppUserBo;
import org.dromara.member.domain.vo.AppUserVo;
import org.dromara.member.service.AppUserService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* App端用户信息
*
* @author pengles
*/
@Tag(name = "App用户管理", description = "App端用户信息管理接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/user")
public class AppUserController extends BaseController {
private final AppUserService appUserService;
/**
* 查询App端用户信息列表
*/
@Operation(summary = "查询用户列表", description = "分页查询App端用户信息列表")
@SaCheckPermission("app:user:list")
@GetMapping("/list")
public TableDataInfo<AppUserVo> list(
@Parameter(description = "查询条件") @Valid AppUserBo bo,
@Parameter(description = "分页参数") @Valid PageQuery pageQuery) {
return appUserService.queryPageList(bo, pageQuery);
}
/**
* 导出App端用户信息列表
*/
@Operation(summary = "导出用户列表", description = "导出App端用户信息到Excel")
@SaCheckPermission("app:user:export")
@Log(title = "App端用户信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(
@Parameter(description = "查询条件") @Valid AppUserBo bo,
HttpServletResponse response) {
List<AppUserVo> list = appUserService.queryList(bo);
ExcelUtil.exportExcel(list, "App端用户信息", AppUserVo.class, response);
}
/**
* 获取App端用户信息详细信息
*/
@Operation(summary = "获取用户详情", description = "根据用户ID获取详细信息")
@SaCheckPermission("app:user:query")
@GetMapping("/{userId}")
public R<AppUserVo> getInfo(
@Parameter(description = "用户ID", required = true)
@PathVariable @NotNull(message = "用户ID不能为空") @Positive(message = "用户ID必须为正数") Long userId) {
return R.ok(appUserService.queryById(userId));
}
/**
* 新增App端用户信息
*/
@Operation(summary = "新增用户", description = "创建新的App端用户")
@SaCheckPermission("app:user:add")
@Log(title = "App端用户信息", businessType = BusinessType.INSERT)
@PostMapping()
public R<Void> add(
@Parameter(description = "用户信息", required = true)
@Validated(AddGroup.class) @RequestBody AppUserBo bo) {
return toAjax(appUserService.insertByBo(bo));
}
/**
* 修改App端用户信息
*/
@Operation(summary = "修改用户", description = "更新App端用户信息")
@SaCheckPermission("app:user:edit")
@Log(title = "App端用户信息", businessType = BusinessType.UPDATE)
@PutMapping()
public R<Void> edit(
@Parameter(description = "用户信息", required = true)
@Validated(EditGroup.class) @RequestBody AppUserBo bo) {
return toAjax(appUserService.updateByBo(bo));
}
/**
* 删除App端用户信息
*/
@Operation(summary = "删除用户", description = "批量删除App端用户")
@SaCheckPermission("app:user:remove")
@Log(title = "App端用户信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{userIds}")
public R<Void> remove(
@Parameter(description = "用户ID列表", required = true)
@PathVariable @NotEmpty(message = "用户ID列表不能为空") Long[] userIds) {
return toAjax(appUserService.deleteWithValidByIds(List.of(userIds), true));
}
/**
* 重置密码
*/
@Operation(summary = "重置密码", description = "重置用户密码")
@SaCheckPermission("app:user:resetPwd")
@Log(title = "App端用户信息", businessType = BusinessType.UPDATE)
@PutMapping("/resetPwd")
public R<Void> resetPwd(
@Parameter(description = "用户信息", required = true)
@Validated(EditGroup.class) @RequestBody AppUserBo bo) {
return toAjax(appUserService.resetPassword(bo.getUserId(), bo.getPassword()));
}
/**
* 状态修改
*/
@Operation(summary = "修改用户状态", description = "启用或禁用用户")
@SaCheckPermission("app:user:edit")
@Log(title = "App端用户信息", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus")
public R<Void> changeStatus(
@Parameter(description = "用户信息", required = true)
@Validated(EditGroup.class) @RequestBody AppUserBo bo) {
// 如果用户状态变为禁用,则冻结用户的所有token
if ("1".equals(bo.getStatus())) {
StpUtil.disable(bo.getUserId(), -1); // -1表示永久冻结,直到手动解冻
} else if ("0".equals(bo.getStatus())) {
// 如果用户状态变为正常,则解冻用户
StpUtil.untieDisable(bo.getUserId());
}
return toAjax(appUserService.updateUserStatus(bo.getUserId(), bo.getStatus()));
}
/**
* 根据用户编号获取详细信息
*/
@Operation(summary = "获取用户档案", description = "根据用户ID获取用户档案信息")
@SaCheckPermission("app:user:query")
@GetMapping("/profile/{userId}")
public R<AppUserVo> profile(
@Parameter(description = "用户ID", required = true)
@PathVariable @NotNull(message = "用户ID不能为空") @Positive(message = "用户ID必须为正数") Long userId) {
AppUserVo user = appUserService.queryById(userId);
return R.ok(user);
}
/**
* 获取当前用户信息
*/
@Operation(summary = "获取当前用户信息", description = "获取当前登录用户的详细信息")
@GetMapping("/getInfo")
public R<AppUserVo> getInfo() {
LoginUser loginUser = LoginHelper.getLoginUser();
AppUserVo user = appUserService.queryById(loginUser.getUserId());
return R.ok(user);
}
/**
* 获取用户个人信息
*/
@Operation(summary = "获取个人信息", description = "获取当前用户的个人信息")
@GetMapping("/profile")
public R<AppUserVo> getUserProfile() {
LoginUser loginUser = LoginHelper.getLoginUser();
AppUserVo user = appUserService.queryById(loginUser.getUserId());
return R.ok(user);
}
/**
* 修改用户个人信息
*/
@Operation(summary = "修改个人信息", description = "更新当前用户的个人信息")
@Log(title = "个人信息", businessType = BusinessType.UPDATE)
@PutMapping("/profile")
public R<Void> updateUserProfile(
@Parameter(description = "用户信息", required = true)
@Validated(EditGroup.class) @RequestBody AppUserBo bo) {
LoginUser loginUser = LoginHelper.getLoginUser();
bo.setUserId(loginUser.getUserId());
return toAjax(appUserService.updateByBo(bo));
}
/**
* 重置密码
*/
@Operation(summary = "修改密码", description = "修改当前用户的登录密码")
@Log(title = "个人信息", businessType = BusinessType.UPDATE)
@PutMapping("/profile/updatePwd")
public R<Void> updateUserPwd(
@Parameter(description = "原密码", required = true)
@RequestParam @NotBlank(message = "原密码不能为空") @Size(min = 6, max = 20, message = "密码长度必须在6-20位之间") String oldPassword,
@Parameter(description = "新密码", required = true)
@RequestParam @NotBlank(message = "新密码不能为空") @Size(min = 6, max = 20, message = "密码长度必须在6-20位之间") String newPassword) {
LoginUser loginUser = LoginHelper.getLoginUser();
return toAjax(appUserService.updatePassword(loginUser.getUserId(), oldPassword, newPassword));
}
}
@@ -0,0 +1,255 @@
package org.dromara.member.controller.app;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.member.domain.bo.AppVersionBo;
import org.dromara.member.domain.vo.AppVersionVo;
import org.dromara.member.service.AppVersionService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* App端版本管理
*
* @author pengles
*/
@Tag(name = "App端版本管理", description = "App端版本管理相关接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/version")
public class AppVersionController extends BaseController {
private final AppVersionService appVersionService;
/**
* 查询App版本信息列表
*/
@Operation(summary = "查询版本列表", description = "查询App版本信息列表")
@GetMapping("/list")
public TableDataInfo<AppVersionVo> list(@Parameter(description = "查询条件") @Valid AppVersionBo bo,
@Parameter(description = "分页参数") @Valid PageQuery pageQuery) {
return appVersionService.queryPageList(bo, pageQuery);
}
/**
* 获取App版本信息详细信息
*/
@Operation(summary = "获取版本详情", description = "获取App版本信息详细信息")
@GetMapping("/{versionId}")
public R<AppVersionVo> getInfo(@Parameter(description = "版本ID", required = true)
@PathVariable @NotNull(message = "版本ID不能为空") @Positive(message = "版本ID必须为正数") Long versionId) {
return R.ok(appVersionService.queryById(versionId));
}
/**
* 检查版本更新
*/
@Operation(summary = "检查版本更新", description = "检查是否有新版本可用")
@GetMapping("/checkUpdate")
public R<Map<String, Object>> checkUpdate(@Parameter(description = "平台类型(android/ios/web)", required = true)
@RequestParam @NotBlank(message = "平台类型不能为空") @Pattern(regexp = "^(android|ios|web)$", message = "平台类型只能为android、ios或web") String platform,
@Parameter(description = "当前版本号", required = true)
@RequestParam @NotNull(message = "当前版本号不能为空") @Positive(message = "当前版本号必须为正数") Integer currentVersionCode) {
if (StringUtils.isBlank(platform)) {
return R.fail("平台类型不能为空");
}
if (currentVersionCode == null || currentVersionCode <= 0) {
return R.fail("当前版本号无效");
}
AppVersionVo updateInfo = appVersionService.getUpdateInfo(platform, currentVersionCode);
Map<String, Object> result = new HashMap<>();
if (updateInfo != null) {
result.put("hasUpdate", true);
result.put("versionInfo", updateInfo);
result.put("isForce", "1".equals(updateInfo.getIsForce()));
result.put("needUpdate", true);
} else {
result.put("hasUpdate", false);
result.put("needUpdate", false);
result.put("isForce", false);
result.put("message", "当前已是最新版本");
}
return R.ok(result);
}
/**
* 获取最新版本信息
*/
@Operation(summary = "获取最新版本", description = "获取指定平台的最新版本信息")
@GetMapping("/latest")
public R<AppVersionVo> getLatestVersion(@Parameter(description = "平台类型(android/ios/web)", required = true)
@RequestParam @NotBlank(message = "平台类型不能为空") @Pattern(regexp = "^(android|ios|web)$", message = "平台类型只能为android、ios或web") String platform) {
if (StringUtils.isBlank(platform)) {
return R.fail("平台类型不能为空");
}
AppVersionVo latestVersion = appVersionService.getLatestVersion(platform);
if (latestVersion == null) {
return R.fail("未找到该平台的版本信息");
}
return R.ok(latestVersion);
}
/**
* 获取版本历史记录
*/
@Operation(summary = "获取版本历史", description = "获取指定平台的版本历史记录")
@GetMapping("/history")
public R<List<AppVersionVo>> getVersionHistory(@Parameter(description = "平台类型(android/ios/web)", required = true)
@RequestParam @NotBlank(message = "平台类型不能为空") @Pattern(regexp = "^(android|ios|web)$", message = "平台类型只能为android、ios或web") String platform,
@Parameter(description = "返回数量限制")
@RequestParam(defaultValue = "10") @Min(value = 1, message = "限制数量必须大于0") @Max(value = 100, message = "限制数量不能超过100") Integer limit) {
if (StringUtils.isBlank(platform)) {
return R.fail("平台类型不能为空");
}
List<AppVersionVo> versionHistory = appVersionService.getVersionHistory(platform, limit);
return R.ok(versionHistory);
}
/**
* 检查是否强制更新
*/
@Operation(summary = "检查强制更新", description = "检查当前版本是否需要强制更新")
@GetMapping("/checkForceUpdate")
public R<Map<String, Object>> checkForceUpdate(@Parameter(description = "平台类型(android/ios/web)", required = true)
@RequestParam @NotBlank(message = "平台类型不能为空") @Pattern(regexp = "^(android|ios|web)$", message = "平台类型只能为android、ios或web") String platform,
@Parameter(description = "当前版本号", required = true)
@RequestParam @NotNull(message = "当前版本号不能为空") @Positive(message = "当前版本号必须为正数") Integer currentVersionCode) {
if (StringUtils.isBlank(platform)) {
return R.fail("平台类型不能为空");
}
if (currentVersionCode == null || currentVersionCode <= 0) {
return R.fail("当前版本号无效");
}
Boolean forceUpdate = appVersionService.checkForceUpdate(platform, currentVersionCode);
Map<String, Object> result = new HashMap<>();
result.put("forceUpdate", forceUpdate);
if (forceUpdate) {
AppVersionVo updateInfo = appVersionService.getUpdateInfo(platform, currentVersionCode);
result.put("versionInfo", updateInfo);
}
return R.ok(result);
}
/**
* 获取更新信息
*/
@Operation(summary = "获取更新信息", description = "获取详细的版本更新信息")
@GetMapping("/updateInfo")
public R<Map<String, Object>> getUpdateInfo(@Parameter(description = "平台类型(android/ios/web)", required = true)
@RequestParam @NotBlank(message = "平台类型不能为空") @Pattern(regexp = "^(android|ios|web)$", message = "平台类型只能为android、ios或web") String platform,
@Parameter(description = "当前版本号", required = true)
@RequestParam @NotNull(message = "当前版本号不能为空") @Positive(message = "当前版本号必须为正数") Integer currentVersionCode) {
if (StringUtils.isBlank(platform)) {
return R.fail("平台类型不能为空");
}
if (currentVersionCode == null || currentVersionCode <= 0) {
return R.fail("当前版本号无效");
}
AppVersionVo updateInfo = appVersionService.getUpdateInfo(platform, currentVersionCode);
Map<String, Object> result = new HashMap<>();
if (updateInfo != null) {
result.put("hasUpdate", true);
result.put("versionCode", updateInfo.getVersionCode());
result.put("versionName", updateInfo.getVersionName());
result.put("updateContent", updateInfo.getUpdateContent());
result.put("downloadUrl", updateInfo.getDownloadUrl());
result.put("isForce", "1".equals(updateInfo.getIsForce()));
result.put("platform", updateInfo.getPlatform());
result.put("createTime", updateInfo.getCreateTime());
} else {
result.put("hasUpdate", false);
result.put("message", "当前已是最新版本");
}
return R.ok(result);
}
/**
* 版本比较
*/
@Operation(summary = "版本比较", description = "比较两个版本之间的差异")
@GetMapping("/compare")
public R<Map<String, Object>> compareVersion(@Parameter(description = "平台类型(android/ios/web)", required = true)
@RequestParam @NotBlank(message = "平台类型不能为空") @Pattern(regexp = "^(android|ios|web)$", message = "平台类型只能为android、ios或web") String platform,
@Parameter(description = "当前版本号", required = true)
@RequestParam @NotNull(message = "当前版本号不能为空") @Positive(message = "当前版本号必须为正数") Integer currentVersionCode,
@Parameter(description = "目标版本号", required = true)
@RequestParam @NotNull(message = "目标版本号不能为空") @Positive(message = "目标版本号必须为正数") Integer targetVersionCode) {
if (StringUtils.isBlank(platform)) {
return R.fail("平台类型不能为空");
}
if (currentVersionCode == null || targetVersionCode == null) {
return R.fail("版本号不能为空");
}
Map<String, Object> result = new HashMap<>();
result.put("platform", platform);
result.put("currentVersionCode", currentVersionCode);
result.put("targetVersionCode", targetVersionCode);
if (targetVersionCode > currentVersionCode) {
result.put("needUpdate", true);
result.put("updateType", "upgrade");
} else if (targetVersionCode < currentVersionCode) {
result.put("needUpdate", false);
result.put("updateType", "downgrade");
} else {
result.put("needUpdate", false);
result.put("updateType", "same");
}
return R.ok(result);
}
/**
* 记录版本下载
*/
@Operation(summary = "记录版本下载", description = "记录用户下载版本的行为")
@Log(title = "App版本下载", businessType = BusinessType.OTHER)
@PostMapping("/download/{versionId}")
public R<Void> recordDownload(@Parameter(description = "版本ID", required = true)
@PathVariable @NotNull(message = "版本ID不能为空") @Positive(message = "版本ID必须为正数") Long versionId) {
// 这里可以记录下载统计等信息
return R.ok();
}
/**
* 记录版本安装
*/
@Operation(summary = "记录版本安装", description = "记录用户安装版本的行为")
@Log(title = "App版本安装", businessType = BusinessType.OTHER)
@PostMapping("/install/{versionId}")
public R<Void> recordInstall(@Parameter(description = "版本ID", required = true)
@PathVariable @NotNull(message = "版本ID不能为空") @Positive(message = "版本ID必须为正数") Long versionId) {
// 这里可以记录安装统计等信息
return R.ok();
}
}
@@ -0,0 +1,109 @@
package org.dromara.member.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端设备信息对象 app_device
*
* @author pengles
*/
@Data
@TableName("app_device")
public class AppDevice implements Serializable {
/**
* 设备ID
*/
@TableId(value = "device_id")
private Long deviceId;
/**
* App用户ID
*/
private Long userId;
/**
* 设备类型(ios android h5
*/
private String deviceType;
/**
* 设备名称
*/
private String deviceName;
/**
* 设备型号
*/
private String deviceModel;
/**
* 设备品牌
*/
private String deviceBrand;
/**
* 操作系统版本
*/
private String osVersion;
/**
* App版本
*/
private String appVersion;
/**
* 推送令牌
*/
private String pushToken;
/**
* 是否活跃(0否 1是)
*/
private String isActive;
/**
* 最后活跃时间
*/
private LocalDateTime lastActiveTime;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志(0代表存在 2代表删除)
*/
@TableLogic
private String delFlag;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,107 @@
package org.dromara.member.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
/**
* App端文件信息对象 app_file
*
* @author pengles
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("app_file")
public class AppFile extends BaseEntity {
/**
* 文件ID
*/
@TableId(value = "file_id")
private Long fileId;
/**
* 用户ID
*/
private Long userId;
/**
* 文件名
*/
private String fileName;
/**
* 原始文件名
*/
private String originalName;
/**
* 文件路径
*/
private String filePath;
/**
* 文件URL
*/
private String fileUrl;
/**
* 文件大小(字节)
*/
private Long fileSize;
/**
* 文件类型(MIME类型)
*/
private String fileType;
/**
* 文件后缀
*/
private String fileSuffix;
/**
* 文件分类(1头像 2文档 3图片 4视频 5音频 6其他)
*/
private Integer fileCategory;
/**
* 存储服务商
*/
private String storageService;
/**
* 文件状态(0正常 1删除)
*/
private String status;
/**
* 下载次数
*/
private Long downloadCount;
/**
* 是否公开(0私有 1公开)
*/
private String isPublic;
/**
* 文件MD5值
*/
private String fileMd5;
/**
* 备注
*/
private String remark;
/**
* 删除标志(0代表存在 2代表删除)
*/
@TableLogic
private String delFlag;
}
@@ -0,0 +1,100 @@
package org.dromara.member.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端登录日志对象 app_login_log
*
* @author pengles
*/
@Data
@TableName("app_login_log")
public class AppLoginLog implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 访问ID
*/
@TableId(value = "info_id")
private Long infoId;
/**
* 用户账号
*/
private String userName;
/**
* 登录状态(0成功 1失败)
*/
private String status;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地点
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 提示消息
*/
private String msg;
/**
* 登录时间
*/
private LocalDateTime loginTime;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,98 @@
package org.dromara.member.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端消息推送对象 app_message
*
* @author pengles
*/
@Data
@TableName("app_message")
public class AppMessage implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 消息ID
*/
@TableId(value = "message_id")
private Long messageId;
/**
* 消息标题
*/
private String title;
/**
* 消息内容
*/
private String content;
/**
* 消息类型(system:系统消息 notice:通知 activity:活动)
*/
private String messageType;
/**
* 推送目标(all:全部用户 user:指定用户 role:指定角色)
*/
private String targetType;
/**
* 目标ID列表(用户ID或角色ID,逗号分隔)
*/
private String targetIds;
/**
* 发送时间
*/
private LocalDateTime sendTime;
/**
* 状态(0待发送 1已发送 2发送失败)
*/
private String status;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志(0代表存在 2代表删除)
*/
@TableLogic
private String delFlag;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,130 @@
package org.dromara.member.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端操作日志对象 app_oper_log
*
* @author pengles
*/
@Data
@TableName("app_oper_log")
public class AppOperLog implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 日志主键
*/
@TableId(value = "oper_id")
private Long operId;
/**
* 模块标题
*/
private String title;
/**
* 业务类型(0其它 1新增 2修改 3删除)
*/
private Integer businessType;
/**
* 方法名称
*/
private String method;
/**
* 请求方式
*/
private String requestMethod;
/**
* 操作人员
*/
private String operName;
/**
* 请求URL
*/
private String operUrl;
/**
* 主机地址
*/
private String operIp;
/**
* 操作地点
*/
private String operLocation;
/**
* 请求参数
*/
private String operParam;
/**
* 返回参数
*/
private String jsonResult;
/**
* 操作状态(0正常 1异常)
*/
private Integer status;
/**
* 错误消息
*/
private String errorMsg;
/**
* 操作时间
*/
private LocalDateTime operTime;
/**
* 消耗时间
*/
private Long costTime;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,83 @@
package org.dromara.member.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端角色信息对象 app_role
*
* @author pengles
*/
@Data
@TableName("app_role")
public class AppRole implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* App角色ID
*/
@TableId(value = "role_id")
private Long roleId;
/**
* 角色名称
*/
private String roleName;
/**
* 角色权限字符串
*/
private String roleKey;
/**
* 显示顺序
*/
private Integer roleSort;
/**
* 角色状态(0正常 1停用)
*/
private String status;
/**
* 删除标志(0代表存在 1代表删除)
*/
@TableLogic
private String delFlag;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,114 @@
package org.dromara.member.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* App端第三方登录授权对象 app_social_auth
*
* @author pengles
*/
@Data
@TableName("app_social_auth")
public class AppSocialAuth implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* App用户ID
*/
private Long userId;
/**
* 用户来源平台(wechat_mp:微信小程序 wechat_open:微信开放平台 qq:QQ weibo:微博)
*/
private String platform;
/**
* 表示平台唯一用户标识
*/
private String platformUid;
/**
* 微信openid
*/
private String openId;
/**
* 微信unionid
*/
private String unionId;
/**
* 用户的授权令牌
*/
private String accessToken;
/**
* 用户的授权令牌的有效期
*/
private Integer expireIn;
/**
* 刷新令牌
*/
private String refreshToken;
/**
* 授予的权限
*/
private String scope;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 删除标志(0代表存在 2代表删除)
*/
@TableLogic
private String delFlag;
/**
* 备注
*/
private String remark;
/**
* 创建部门
*/
@TableField(exist = false)
private Long createDept;
}
@@ -0,0 +1,118 @@
package org.dromara.member.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端用户信息对象 app_user
*
* @author pengles
*/
@Data
@TableName("app_user")
public class AppUser implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* App用户ID
*/
@TableId(value = "user_id")
private Long userId;
/**
* 用户账号
*/
private String userName;
/**
* 用户昵称
*/
private String nickName;
/**
* 手机号码
*/
private String phone;
/**
* 用户邮箱
*/
private String email;
/**
* 用户性别(0男 1女 2未知)
*/
private String sex;
/**
* 头像地址
*/
private Long avatar;
/**
* 密码
*/
private String password;
/**
* 帐号状态(0正常 1停用)
*/
private String status;
/**
* 删除标志(0代表存在 1代表删除)
*/
@TableLogic
private String delFlag;
/**
* 最后登录IP
*/
private String loginIp;
/**
* 最后登录时间
*/
private LocalDateTime loginDate;
/**
* 注册时间
*/
private LocalDateTime registerTime;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,83 @@
package org.dromara.member.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端用户消息读取记录对象 app_user_message
*
* @author pengles
*/
@Data
@TableName("app_user_message")
public class AppUserMessage implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* App用户ID
*/
private Long userId;
/**
* 消息ID
*/
private Long messageId;
/**
* 是否已读(0未读 1已读)
*/
private String isRead;
/**
* 读取时间
*/
private LocalDateTime readTime;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志(0代表存在 2代表删除)
*/
@TableLogic
private String delFlag;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,70 @@
package org.dromara.member.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端用户和角色关联对象 app_user_role
*
* @author pengles
*/
@Data
@TableName("app_user_role")
public class AppUserRole implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* App用户ID
*/
private Long userId;
/**
* App角色ID
*/
private Long roleId;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志(0代表存在 2代表删除)
*/
@TableLogic
private String delFlag;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,93 @@
package org.dromara.member.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App版本信息对象 app_version
*
* @author pengles
*/
@Data
@TableName("app_version")
public class AppVersion implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 版本ID
*/
@TableId(value = "version_id")
private Long versionId;
/**
* 版本号(数字)
*/
private Integer versionCode;
/**
* 版本名(如1.0.0
*/
private String versionName;
/**
* 更新内容
*/
private String updateContent;
/**
* 下载地址
*/
private String downloadUrl;
/**
* 是否强制更新(0否 1是)
*/
private String isForce;
/**
* 平台类型(android ios
*/
private String platform;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标志(0代表存在 2代表删除)
*/
@TableLogic
private String delFlag;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,87 @@
package org.dromara.member.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.time.LocalDateTime;
/**
* App端设备信息业务对象 app_device
*
* @author pengles
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = org.dromara.member.domain.AppDevice.class, reverseConvertGenerate = false)
public class AppDeviceBo extends BaseEntity {
/**
* 设备ID
*/
@NotNull(message = "设备ID不能为空", groups = {EditGroup.class})
private Long deviceId;
/**
* App用户ID
*/
@NotNull(message = "用户ID不能为空", groups = {AddGroup.class, EditGroup.class})
private Long userId;
/**
* 设备类型(ios android h5
*/
@NotBlank(message = "设备类型不能为空", groups = {AddGroup.class, EditGroup.class})
private String deviceType;
/**
* 设备名称
*/
private String deviceName;
/**
* 设备型号
*/
private String deviceModel;
/**
* 设备品牌
*/
private String deviceBrand;
/**
* 操作系统版本
*/
private String osVersion;
/**
* App版本
*/
private String appVersion;
/**
* 推送令牌
*/
private String pushToken;
/**
* 是否活跃(0否 1是)
*/
private String isActive;
/**
* 最后活跃时间
*/
private LocalDateTime lastActiveTime;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,108 @@
package org.dromara.member.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.member.domain.AppFile;
/**
* App端文件信息业务对象 app_file
*
* @author pengles
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = AppFile.class, reverseConvertGenerate = false)
public class AppFileBo extends BaseEntity {
/**
* 文件ID
*/
@NotNull(message = "文件ID不能为空", groups = {EditGroup.class})
private Long fileId;
/**
* 用户ID
*/
private Long userId;
/**
* 文件名
*/
@NotBlank(message = "文件名不能为空", groups = {AddGroup.class, EditGroup.class})
private String fileName;
/**
* 原始文件名
*/
@NotBlank(message = "原始文件名不能为空", groups = {AddGroup.class, EditGroup.class})
private String originalName;
/**
* 文件路径
*/
private String filePath;
/**
* 文件URL
*/
@NotBlank(message = "文件URL不能为空", groups = {AddGroup.class, EditGroup.class})
private String fileUrl;
/**
* 文件大小(字节)
*/
@NotNull(message = "文件大小不能为空", groups = {AddGroup.class, EditGroup.class})
private Long fileSize;
/**
* 文件类型(MIME类型)
*/
private String fileType;
/**
* 文件后缀
*/
private String fileSuffix;
/**
* 文件分类(1头像 2文档 3图片 4视频 5音频 6其他)
*/
private Integer fileCategory;
/**
* 存储服务商
*/
private String storageService;
/**
* 文件状态(0正常 1删除)
*/
private String status;
/**
* 下载次数
*/
private Long downloadCount;
/**
* 是否公开(0私有 1公开)
*/
private String isPublic;
/**
* 文件MD5值
*/
private String fileMd5;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,82 @@
package org.dromara.member.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.time.LocalDateTime;
/**
* App端消息业务对象 app_message
*
* @author pengles
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = org.dromara.member.domain.AppMessage.class, reverseConvertGenerate = false)
public class AppMessageBo extends BaseEntity {
/**
* 消息ID
*/
@NotNull(message = "消息ID不能为空", groups = {EditGroup.class})
private Long messageId;
/**
* 消息标题
*/
@NotBlank(message = "消息标题不能为空", groups = {AddGroup.class, EditGroup.class})
private String title;
/**
* 消息内容
*/
@NotBlank(message = "消息内容不能为空", groups = {AddGroup.class, EditGroup.class})
private String content;
/**
* 消息类型(system:系统消息 notice:通知 activity:活动)
*/
private String messageType;
/**
* 推送目标(all:全部用户 user:指定用户 role:指定角色)
*/
private String targetType;
/**
* 目标ID列表(用户ID或角色ID,逗号分隔)
*/
private String targetIds;
/**
* 发送时间
*/
private LocalDateTime sendTime;
/**
* 状态(0待发送 1已发送 2发送失败)
*/
private String status;
/**
* 备注
*/
private String remark;
/**
* 接收者ID(用于查询当前用户的消息)
*/
private Long receiverId;
/**
* 发送者ID
*/
private Long senderId;
}
@@ -0,0 +1,107 @@
package org.dromara.member.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.member.domain.AppOperLog;
import java.time.LocalDateTime;
/**
* App端操作日志业务对象 app_oper_log
*
* @author pengles
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = AppOperLog.class, reverseConvertGenerate = false)
public class AppOperLogBo extends BaseEntity {
/**
* 日志主键
*/
@NotNull(message = "日志主键不能为空", groups = {EditGroup.class})
private Long operId;
/**
* 模块标题
*/
@NotBlank(message = "模块标题不能为空", groups = {AddGroup.class, EditGroup.class})
private String title;
/**
* 业务类型(0其它 1新增 2修改 3删除)
*/
private Integer businessType;
/**
* 方法名称
*/
private String method;
/**
* 请求方式
*/
private String requestMethod;
/**
* 操作人员
*/
private String operName;
/**
* 请求URL
*/
private String operUrl;
/**
* 主机地址
*/
private String operIp;
/**
* 操作地点
*/
private String operLocation;
/**
* 请求参数
*/
private String operParam;
/**
* 返回参数
*/
private String jsonResult;
/**
* 操作状态(0正常 1异常)
*/
private Integer status;
/**
* 错误消息
*/
private String errorMsg;
/**
* 操作时间
*/
private LocalDateTime operTime;
/**
* 消耗时间
*/
private Long costTime;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,61 @@
package org.dromara.member.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.member.domain.AppRole;
/**
* App端角色信息业务对象 app_role
*
* @author pengles
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = AppRole.class, reverseConvertGenerate = false)
public class AppRoleBo extends BaseEntity {
/**
* App角色ID
*/
@NotNull(message = "App角色ID不能为空", groups = {EditGroup.class})
private Long roleId;
/**
* 角色名称
*/
@NotBlank(message = "角色名称不能为空", groups = {AddGroup.class, EditGroup.class})
@Size(max = 30, message = "角色名称长度不能超过30个字符")
private String roleName;
/**
* 角色权限字符串
*/
@NotBlank(message = "角色权限字符串不能为空", groups = {AddGroup.class, EditGroup.class})
@Size(max = 100, message = "角色权限字符串长度不能超过100个字符")
private String roleKey;
/**
* 显示顺序
*/
@NotNull(message = "显示顺序不能为空", groups = {AddGroup.class, EditGroup.class})
private Integer roleSort;
/**
* 角色状态(0正常 1停用)
*/
@NotBlank(message = "角色状态不能为空", groups = {AddGroup.class, EditGroup.class})
private String status;
/**
* 用户ID(用于用户角色授权操作)
*/
private Long userId;
}
@@ -0,0 +1,96 @@
package org.dromara.member.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.member.domain.AppUser;
import java.time.LocalDateTime;
/**
* App端用户信息业务对象 app_user
*
* @author pengles
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = AppUser.class, reverseConvertGenerate = false)
public class AppUserBo extends BaseEntity {
/**
* App用户ID
*/
@NotNull(message = "App用户ID不能为空", groups = {EditGroup.class})
private Long userId;
/**
* 用户账号
*/
@NotBlank(message = "用户账号不能为空", groups = {AddGroup.class, EditGroup.class})
@Size(max = 30, message = "用户账号长度不能超过30个字符")
private String userName;
/**
* 用户昵称
*/
@NotBlank(message = "用户昵称不能为空", groups = {AddGroup.class, EditGroup.class})
@Size(max = 30, message = "用户昵称长度不能超过30个字符")
private String nickName;
/**
* 手机号码
*/
@Size(max = 11, message = "手机号码长度不能超过11个字符")
private String phone;
/**
* 用户邮箱
*/
@Email(message = "邮箱格式不正确")
@Size(max = 50, message = "用户邮箱长度不能超过50个字符")
private String email;
/**
* 用户性别(0男 1女 2未知)
*/
private String sex;
/**
* 头像地址
*/
private Long avatar;
/**
* 密码
*/
@Size(max = 100, message = "密码长度不能超过100个字符")
private String password;
/**
* 帐号状态(0正常 1停用)
*/
private String status;
/**
* 最后登录IP
*/
private String loginIp;
/**
* 最后登录时间
*/
private LocalDateTime loginDate;
/**
* 注册时间
*/
private LocalDateTime registerTime;
}
@@ -0,0 +1,67 @@
package org.dromara.member.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.member.domain.AppVersion;
/**
* App版本信息业务对象 app_version
*
* @author pengles
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = AppVersion.class, reverseConvertGenerate = false)
public class AppVersionBo extends BaseEntity {
/**
* 版本ID
*/
@NotNull(message = "版本ID不能为空", groups = {EditGroup.class})
private Long versionId;
/**
* 版本号(数字)
*/
@NotNull(message = "版本号不能为空", groups = {AddGroup.class, EditGroup.class})
private Integer versionCode;
/**
* 版本名(如1.0.0
*/
@NotBlank(message = "版本名不能为空", groups = {AddGroup.class, EditGroup.class})
private String versionName;
/**
* 更新内容
*/
private String updateContent;
/**
* 下载地址
*/
private String downloadUrl;
/**
* 是否强制更新(0否 1是)
*/
private String isForce;
/**
* 平台类型(android ios
*/
@NotBlank(message = "平台类型不能为空", groups = {AddGroup.class, EditGroup.class})
private String platform;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,108 @@
package org.dromara.member.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.member.domain.AppDevice;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端设备信息视图对象 app_device
*
* @author pengles
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = AppDevice.class)
public class AppDeviceVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 设备ID
*/
@ExcelProperty(value = "设备ID")
private Long deviceId;
/**
* App用户ID
*/
@ExcelProperty(value = "App用户ID")
private Long userId;
/**
* 设备类型(ios android h5
*/
@ExcelProperty(value = "设备类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "app_device_type")
private String deviceType;
/**
* 设备名称
*/
@ExcelProperty(value = "设备名称")
private String deviceName;
/**
* 设备型号
*/
@ExcelProperty(value = "设备型号")
private String deviceModel;
/**
* 设备品牌
*/
@ExcelProperty(value = "设备品牌")
private String deviceBrand;
/**
* 操作系统版本
*/
@ExcelProperty(value = "操作系统版本")
private String osVersion;
/**
* App版本
*/
@ExcelProperty(value = "App版本")
private String appVersion;
/**
* 推送令牌
*/
@ExcelProperty(value = "推送令牌")
private String pushToken;
/**
* 是否活跃(0否 1是)
*/
@ExcelProperty(value = "是否活跃", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_yes_no")
private String isActive;
/**
* 最后活跃时间
*/
@ExcelProperty(value = "最后活跃时间")
private LocalDateTime lastActiveTime;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private LocalDateTime createTime;
/**
* 更新时间
*/
@ExcelProperty(value = "更新时间")
private LocalDateTime updateTime;
}
@@ -0,0 +1,123 @@
package org.dromara.member.domain.vo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.member.domain.AppFile;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端文件信息视图对象 app_file
*
* @author pengles
*/
@Data
@AutoMapper(target = AppFile.class)
public class AppFileVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 文件ID
*/
private Long fileId;
/**
* 用户ID
*/
private Long userId;
/**
* 文件名
*/
private String fileName;
/**
* 原始文件名
*/
private String originalName;
/**
* 文件路径
*/
private String filePath;
/**
* 文件URL
*/
private String fileUrl;
/**
* 文件大小(字节)
*/
private Long fileSize;
/**
* 文件类型(MIME类型)
*/
private String fileType;
/**
* 文件后缀
*/
private String fileSuffix;
/**
* 文件分类(1头像 2文档 3图片 4视频 5音频 6其他)
*/
private Integer fileCategory;
/**
* 存储服务商
*/
private String storageService;
/**
* 文件状态(0正常 1删除)
*/
private String status;
/**
* 下载次数
*/
private Long downloadCount;
/**
* 是否公开(0私有 1公开)
*/
private String isPublic;
/**
* 文件MD5值
*/
private String fileMd5;
/**
* 备注
*/
private String remark;
/**
* 创建者
*/
private String createBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新者
*/
private String updateBy;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
@@ -0,0 +1,83 @@
package org.dromara.member.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.member.domain.AppLoginLog;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端登录日志视图对象 app_login_log
*
* @author pengles
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = AppLoginLog.class)
public class AppLoginLogVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 访问ID
*/
@ExcelProperty(value = "访问ID")
private Long infoId;
/**
* 用户账号
*/
@ExcelProperty(value = "用户账号")
private String userName;
/**
* 登录状态(0成功 1失败)
*/
@ExcelProperty(value = "登录状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_common_status")
private String status;
/**
* 登录IP地址
*/
@ExcelProperty(value = "登录IP地址")
private String ipaddr;
/**
* 登录地点
*/
@ExcelProperty(value = "登录地点")
private String loginLocation;
/**
* 浏览器类型
*/
@ExcelProperty(value = "浏览器类型")
private String browser;
/**
* 操作系统
*/
@ExcelProperty(value = "操作系统")
private String os;
/**
* 提示消息
*/
@ExcelProperty(value = "提示消息")
private String msg;
/**
* 访问时间
*/
@ExcelProperty(value = "访问时间")
private LocalDateTime loginTime;
}
@@ -0,0 +1,91 @@
package org.dromara.member.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.member.domain.AppMessage;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端消息推送视图对象 app_message
*
* @author pengles
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = AppMessage.class)
public class AppMessageVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 消息ID
*/
@ExcelProperty(value = "消息ID")
private Long messageId;
/**
* 消息标题
*/
@ExcelProperty(value = "消息标题")
private String title;
/**
* 消息内容
*/
@ExcelProperty(value = "消息内容")
private String content;
/**
* 消息类型(system:系统消息 notice:通知 activity:活动)
*/
@ExcelProperty(value = "消息类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "app_message_type")
private String messageType;
/**
* 推送目标(all:全部用户 user:指定用户 role:指定角色)
*/
@ExcelProperty(value = "推送目标", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "app_target_type")
private String targetType;
/**
* 目标ID列表(用户ID或角色ID,逗号分隔)
*/
@ExcelProperty(value = "目标ID列表")
private String targetIds;
/**
* 发送时间
*/
@ExcelProperty(value = "发送时间")
private LocalDateTime sendTime;
/**
* 状态(0待发送 1已发送 2发送失败)
*/
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "app_message_status")
private String status;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private LocalDateTime createTime;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}
@@ -0,0 +1,120 @@
package org.dromara.member.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.member.domain.AppOperLog;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端操作日志视图对象 app_oper_log
*
* @author pengles
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = AppOperLog.class)
public class AppOperLogVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 日志主键
*/
@ExcelProperty(value = "日志主键")
private Long operId;
/**
* 模块标题
*/
@ExcelProperty(value = "模块标题")
private String title;
/**
* 业务类型(0其它 1新增 2修改 3删除)
*/
@ExcelProperty(value = "业务类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_oper_type")
private Integer businessType;
/**
* 方法名称
*/
@ExcelProperty(value = "方法名称")
private String method;
/**
* 请求方式
*/
@ExcelProperty(value = "请求方式")
private String requestMethod;
/**
* 操作人员
*/
@ExcelProperty(value = "操作人员")
private String operName;
/**
* 请求URL
*/
@ExcelProperty(value = "请求URL")
private String operUrl;
/**
* 主机地址
*/
@ExcelProperty(value = "主机地址")
private String operIp;
/**
* 操作地点
*/
@ExcelProperty(value = "操作地点")
private String operLocation;
/**
* 请求参数
*/
@ExcelProperty(value = "请求参数")
private String operParam;
/**
* 返回参数
*/
@ExcelProperty(value = "返回参数")
private String jsonResult;
/**
* 操作状态(0正常 1异常)
*/
@ExcelProperty(value = "操作状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_common_status")
private Integer status;
/**
* 错误消息
*/
@ExcelProperty(value = "错误消息")
private String errorMsg;
/**
* 操作时间
*/
@ExcelProperty(value = "操作时间")
private LocalDateTime operTime;
/**
* 消耗时间
*/
@ExcelProperty(value = "消耗时间")
private Long costTime;
}
@@ -0,0 +1,71 @@
package org.dromara.member.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.member.domain.AppRole;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端角色信息视图对象 app_role
*
* @author pengles
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = AppRole.class)
public class AppRoleVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* App角色ID
*/
@ExcelProperty(value = "App角色ID")
private Long roleId;
/**
* 角色名称
*/
@ExcelProperty(value = "角色名称")
private String roleName;
/**
* 角色权限字符串
*/
@ExcelProperty(value = "角色权限字符串")
private String roleKey;
/**
* 显示顺序
*/
@ExcelProperty(value = "显示顺序")
private Integer roleSort;
/**
* 角色状态(0正常 1停用)
*/
@ExcelProperty(value = "角色状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_normal_disable")
private String status;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private LocalDateTime createTime;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}
@@ -0,0 +1,98 @@
package org.dromara.member.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.member.domain.AppSocialAuth;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端第三方登录授权视图对象 app_social_auth
*
* @author pengles
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = AppSocialAuth.class)
public class AppSocialAuthVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* App用户ID
*/
@ExcelProperty(value = "App用户ID")
private Long userId;
/**
* 用户来源平台(wechat_mp:微信小程序 wechat_open:微信开放平台 qq:QQ weibo:微博)
*/
@ExcelProperty(value = "用户来源平台")
private String platform;
/**
* 表示平台唯一用户标识
*/
@ExcelProperty(value = "平台唯一用户标识")
private String platformUid;
/**
* 微信openid
*/
@ExcelProperty(value = "微信openid")
private String openId;
/**
* 微信unionid
*/
@ExcelProperty(value = "微信unionid")
private String unionId;
/**
* 用户的授权令牌
*/
@ExcelProperty(value = "授权令牌")
private String accessToken;
/**
* 用户的授权令牌的有效期
*/
@ExcelProperty(value = "令牌有效期")
private Integer expireIn;
/**
* 刷新令牌
*/
@ExcelProperty(value = "刷新令牌")
private String refreshToken;
/**
* 授予的权限
*/
@ExcelProperty(value = "授予权限")
private String scope;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private LocalDateTime createTime;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}
@@ -0,0 +1,65 @@
package org.dromara.member.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.member.domain.AppUserMessage;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端用户消息读取记录视图对象 app_user_message
*
* @author pengles
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = AppUserMessage.class)
public class AppUserMessageVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* App用户ID
*/
@ExcelProperty(value = "App用户ID")
private Long userId;
/**
* 消息ID
*/
@ExcelProperty(value = "消息ID")
private Long messageId;
/**
* 是否已读(0未读 1已读)
*/
@ExcelProperty(value = "是否已读", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_yes_no")
private String isRead;
/**
* 读取时间
*/
@ExcelProperty(value = "读取时间")
private LocalDateTime readTime;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private LocalDateTime createTime;
}
@@ -0,0 +1,68 @@
package org.dromara.member.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.member.domain.AppUserRole;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端用户和角色关联视图对象 app_user_role
*
* @author pengles
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = AppUserRole.class)
public class AppUserRoleVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* App用户ID
*/
@ExcelProperty(value = "App用户ID")
private Long userId;
/**
* App角色ID
*/
@ExcelProperty(value = "App角色ID")
private Long roleId;
/**
* 创建者
*/
@ExcelProperty(value = "创建者")
private Long createBy;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private LocalDateTime createTime;
/**
* 更新者
*/
@ExcelProperty(value = "更新者")
private Long updateBy;
/**
* 更新时间
*/
@ExcelProperty(value = "更新时间")
private LocalDateTime updateTime;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}
@@ -0,0 +1,114 @@
package org.dromara.member.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.member.domain.AppUser;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App端用户信息视图对象 app_user
*
* @author pengles
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = AppUser.class)
public class AppUserVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* App用户ID
*/
@ExcelProperty(value = "App用户ID")
private Long userId;
/**
* 用户账号
*/
@ExcelProperty(value = "用户账号")
private String userName;
/**
* 密码
*/
@ExcelProperty(value = "密码")
private String password;
/**
* 用户昵称
*/
@ExcelProperty(value = "用户昵称")
private String nickName;
/**
* 手机号码
*/
@ExcelProperty(value = "手机号码")
private String phone;
/**
* 用户邮箱
*/
@ExcelProperty(value = "用户邮箱")
private String email;
/**
* 用户性别(0男 1女 2未知)
*/
@ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_user_sex")
private String sex;
/**
* 头像地址
*/
@ExcelProperty(value = "头像地址")
private Long avatar;
/**
* 帐号状态(0正常 1停用)
*/
@ExcelProperty(value = "帐号状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_normal_disable")
private String status;
/**
* 最后登录IP
*/
@ExcelProperty(value = "最后登录IP")
private String loginIp;
/**
* 最后登录时间
*/
@ExcelProperty(value = "最后登录时间")
private LocalDateTime loginDate;
/**
* 注册时间
*/
@ExcelProperty(value = "注册时间")
private LocalDateTime registerTime;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private LocalDateTime createTime;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}
@@ -0,0 +1,77 @@
package org.dromara.member.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.member.domain.AppVersion;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* App版本信息视图对象 app_version
*
* @author pengles
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = AppVersion.class)
public class AppVersionVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 版本ID
*/
@ExcelProperty(value = "版本ID")
private Long versionId;
/**
* 版本号(数字)
*/
@ExcelProperty(value = "版本号")
private Integer versionCode;
/**
* 版本名(如1.0.0
*/
@ExcelProperty(value = "版本名")
private String versionName;
/**
* 更新内容
*/
@ExcelProperty(value = "更新内容")
private String updateContent;
/**
* 下载地址
*/
@ExcelProperty(value = "下载地址")
private String downloadUrl;
/**
* 是否强制更新(0否 1是)
*/
@ExcelProperty(value = "是否强制更新", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_yes_no")
private String isForce;
/**
* 平台(android、ios
*/
@ExcelProperty(value = "平台")
private String platform;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private LocalDateTime createTime;
}
@@ -0,0 +1,159 @@
package org.dromara.member.exception;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import java.io.IOException;
/**
* App模块专用异常处理器
* 处理App端特有的业务异常和错误场景
*
* @author pengles
*/
@Slf4j
@Order(1) // 优先级高于全局异常处理器
@RestControllerAdvice(basePackages = "org.dromara.app")
public class AppExceptionHandler {
/**
* App端登录异常处理
*/
@ExceptionHandler(NotLoginException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public R<Void> handleNotLoginException(NotLoginException e) {
log.warn("App端用户未登录: {}", e.getMessage());
return R.fail(401, "请先登录后再操作");
}
/**
* App端权限异常处理
*/
@ExceptionHandler(NotPermissionException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public R<Void> handleNotPermissionException(NotPermissionException e) {
log.warn("App端用户权限不足: {}", e.getMessage());
return R.fail(403, "权限不足,无法访问该资源");
}
/**
* 文件上传大小超限异常
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R<Void> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
log.error("App端文件上传大小超限: {}", e.getMessage());
return R.fail("上传文件大小超出限制,请选择较小的文件");
}
/**
* IO异常处理(主要用于文件操作)
*/
@ExceptionHandler(IOException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public R<Void> handleIOException(IOException e) {
log.error("App端文件操作异常: {}", e.getMessage(), e);
return R.fail("文件操作失败,请稍后重试");
}
/**
* App端业务异常处理
*/
@ExceptionHandler(ServiceException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R<Void> handleServiceException(ServiceException e) {
log.error("App端业务异常: {}", e.getMessage());
Integer code = e.getCode();
if (code != null) {
return R.fail(code, e.getMessage());
}
return R.fail(e.getMessage());
}
/**
* 参数验证异常处理
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldError().getDefaultMessage();
log.warn("App端参数验证失败: {}", message);
return R.fail("参数验证失败: " + message);
}
/**
* 参数绑定异常处理
*/
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R<Void> handleBindException(BindException e) {
String message = e.getBindingResult().getFieldError().getDefaultMessage();
log.warn("App端参数绑定失败: {}", message);
return R.fail("参数绑定失败: " + message);
}
/**
* 约束违反异常处理
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R<Void> handleConstraintViolationException(ConstraintViolationException e) {
String message = e.getMessage();
log.warn("App端约束验证失败: {}", message);
return R.fail("参数验证失败: " + message);
}
/**
* 文件相关异常处理
*/
@ExceptionHandler(FileException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R<Void> handleFileException(FileException e) {
log.error("App端文件异常: {}", e.getMessage());
return R.fail(e.getCode(), e.getMessage());
}
/**
* 设备相关异常处理
*/
@ExceptionHandler(DeviceException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R<Void> handleDeviceException(DeviceException e) {
log.error("App端设备异常: {}", e.getMessage());
return R.fail(e.getCode(), e.getMessage());
}
/**
* 版本相关异常处理
*/
@ExceptionHandler(VersionException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R<Void> handleVersionException(VersionException e) {
log.error("App端版本异常: {}", e.getMessage());
return R.fail(e.getCode(), e.getMessage());
}
/**
* 通用异常处理
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public R<Void> handleException(Exception e) {
log.error("App端系统异常: ", e);
return R.fail("系统异常,请联系管理员");
}
}
@@ -0,0 +1,96 @@
package org.dromara.member.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 设备相关异常
*
* @author pengles
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class DeviceException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private Integer code;
/**
* 错误信息
*/
private String message;
public DeviceException(String message) {
super(message);
this.message = message;
this.code = 400;
}
public DeviceException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
public DeviceException(String message, Throwable cause) {
super(message, cause);
this.message = message;
this.code = 400;
}
/**
* 设备已绑定异常
*/
public static DeviceException deviceAlreadyBound(String deviceInfo) {
return new DeviceException(4001, "设备已绑定: " + deviceInfo);
}
/**
* 设备不存在异常
*/
public static DeviceException deviceNotFound(Long deviceId) {
return new DeviceException(4002, "设备不存在: " + deviceId);
}
/**
* 设备绑定失败异常
*/
public static DeviceException bindFailed(String reason) {
return new DeviceException(4003, "设备绑定失败: " + reason);
}
/**
* 设备解绑失败异常
*/
public static DeviceException unbindFailed(String reason) {
return new DeviceException(4004, "设备解绑失败: " + reason);
}
/**
* 设备状态异常
*/
public static DeviceException invalidStatus(String status) {
return new DeviceException(4005, "设备状态异常: " + status);
}
/**
* 推送令牌无效异常
*/
public static DeviceException invalidPushToken(String token) {
return new DeviceException(4006, "推送令牌无效: " + token);
}
/**
* 设备数量超限异常
*/
public static DeviceException deviceLimitExceeded(int limit) {
return new DeviceException(4007, "设备数量超出限制,最多允许绑定 " + limit + " 台设备");
}
}
@@ -0,0 +1,127 @@
package org.dromara.member.exception;
/**
* 文件相关异常
*
* @author pengles
*/
public class FileException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Integer code;
private String message;
private String detailMessage;
public FileException() {
}
public FileException(String message) {
this.message = message;
}
public FileException(String message, Integer code) {
this.message = message;
this.code = code;
}
public FileException(String message, Throwable e) {
super(message, e);
this.message = message;
}
public FileException(String message, Integer code, Throwable e) {
super(message, e);
this.message = message;
this.code = code;
}
public FileException(String message, Integer code, String detailMessage) {
this.message = message;
this.code = code;
this.detailMessage = detailMessage;
}
public FileException(String message, Integer code, String detailMessage, Throwable e) {
super(message, e);
this.message = message;
this.code = code;
this.detailMessage = detailMessage;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getDetailMessage() {
return detailMessage;
}
public void setDetailMessage(String detailMessage) {
this.detailMessage = detailMessage;
}
// 静态工厂方法
public static FileException fileEmpty() {
return new FileException("上传文件不能为空", 40001);
}
public static FileException fileSizeExceeded(long maxSize) {
return new FileException("文件大小超过限制", 40002, "最大允许大小: " + maxSize + "MB");
}
public static FileException unsupportedFileType(String fileType) {
return new FileException("不支持的文件类型", 40003, "文件类型: " + fileType);
}
public static FileException uploadFailed(String reason) {
return new FileException("文件上传失败", 40004, reason);
}
public static FileException fileNotFound(Long fileId) {
return new FileException("文件不存在", 40005, "文件ID: " + fileId);
}
public static FileException downloadFailed(String reason) {
return new FileException("文件下载失败", 40006, reason);
}
public static FileException deleteFailed(String reason) {
return new FileException("文件删除失败", 40007, reason);
}
public static FileException accessDenied(Long fileId) {
return new FileException("无权限访问文件", 40008, "文件ID: " + fileId);
}
public static FileException storageServiceError(String service) {
return new FileException("存储服务异常", 40009, "服务: " + service);
}
public static FileException md5CalculationFailed() {
return new FileException("文件MD5计算失败", 40010);
}
public static FileException invalidFileName(String fileName) {
return new FileException("无效的文件名", 40011, "文件名: " + fileName);
}
public static FileException fileAlreadyExists(String fileName) {
return new FileException("文件已存在", 40012, "文件名: " + fileName);
}
}
@@ -0,0 +1,110 @@
package org.dromara.member.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 版本相关异常
*
* @author pengles
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class VersionException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private Integer code;
/**
* 错误信息
*/
private String message;
public VersionException(String message) {
super(message);
this.message = message;
this.code = 400;
}
public VersionException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
public VersionException(String message, Throwable cause) {
super(message, cause);
this.message = message;
this.code = 400;
}
/**
* 版本不存在异常
*/
public static VersionException versionNotFound(String version) {
return new VersionException(5001, "版本不存在: " + version);
}
/**
* 版本号重复异常
*/
public static VersionException versionCodeDuplicate(Integer versionCode) {
return new VersionException(5002, "版本号已存在: " + versionCode);
}
/**
* 版本号无效异常
*/
public static VersionException invalidVersionCode(Integer versionCode) {
return new VersionException(5003, "版本号无效: " + versionCode);
}
/**
* 平台不支持异常
*/
public static VersionException unsupportedPlatform(String platform) {
return new VersionException(5004, "不支持的平台: " + platform);
}
/**
* 版本发布失败异常
*/
public static VersionException publishFailed(String reason) {
return new VersionException(5005, "版本发布失败: " + reason);
}
/**
* 版本撤回失败异常
*/
public static VersionException withdrawFailed(String reason) {
return new VersionException(5006, "版本撤回失败: " + reason);
}
/**
* 强制更新版本冲突异常
*/
public static VersionException forceUpdateConflict(String version) {
return new VersionException(5007, "强制更新版本冲突: " + version);
}
/**
* 版本比较异常
*/
public static VersionException versionCompareError(String currentVersion, String targetVersion) {
return new VersionException(5008, "版本比较失败: " + currentVersion + " vs " + targetVersion);
}
/**
* 下载链接无效异常
*/
public static VersionException invalidDownloadUrl(String url) {
return new VersionException(5009, "下载链接无效: " + url);
}
}
@@ -0,0 +1,14 @@
package org.dromara.member.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.member.domain.AppDevice;
import org.dromara.member.domain.vo.AppDeviceVo;
/**
* App端设备信息Mapper接口
*
* @author pengles
*/
public interface AppDeviceMapper extends BaseMapperPlus<AppDevice, AppDeviceVo> {
}
@@ -0,0 +1,14 @@
package org.dromara.member.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.member.domain.AppFile;
import org.dromara.member.domain.vo.AppFileVo;
/**
* App端文件信息Mapper接口
*
* @author pengles
*/
public interface AppFileMapper extends BaseMapperPlus<AppFile, AppFileVo> {
}
@@ -0,0 +1,14 @@
package org.dromara.member.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.member.domain.AppLoginLog;
import org.dromara.member.domain.vo.AppLoginLogVo;
/**
* App端登录日志Mapper接口
*
* @author pengles
*/
public interface AppLoginLogMapper extends BaseMapperPlus<AppLoginLog, AppLoginLogVo> {
}
@@ -0,0 +1,39 @@
package org.dromara.member.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.member.domain.AppMessage;
import org.dromara.member.domain.vo.AppMessageVo;
import java.util.List;
/**
* App端消息推送Mapper接口
*
* @author pengles
*/
public interface AppMessageMapper extends BaseMapperPlus<AppMessage, AppMessageVo> {
/**
* 根据用户ID查询消息列表
*
* @param userId 用户ID
* @return 消息列表
*/
List<AppMessageVo> selectMessagesByUserId(Long userId);
/**
* 根据角色ID查询消息列表
*
* @param roleId 角色ID
* @return 消息列表
*/
List<AppMessageVo> selectMessagesByRoleId(Long roleId);
/**
* 查询全部用户消息列表
*
* @return 消息列表
*/
List<AppMessageVo> selectMessagesForAll();
}
@@ -0,0 +1,14 @@
package org.dromara.member.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.member.domain.AppOperLog;
import org.dromara.member.domain.vo.AppOperLogVo;
/**
* App端操作日志Mapper接口
*
* @author pengles
*/
public interface AppOperLogMapper extends BaseMapperPlus<AppOperLog, AppOperLogVo> {
}
@@ -0,0 +1,64 @@
package org.dromara.member.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.member.domain.AppRole;
import org.dromara.member.domain.vo.AppRoleVo;
import java.util.List;
import java.util.Set;
/**
* App端角色信息Mapper接口
*
* @author pengles
*/
public interface AppRoleMapper extends BaseMapperPlus<AppRole, AppRoleVo> {
/**
* 根据用户ID查询角色
*
* @param userId 用户ID
* @return 角色列表
*/
List<AppRoleVo> selectRolesByUserId(Long userId);
/**
* 根据用户ID获取角色权限
*
* @param userId 用户ID
* @return 权限列表
*/
Set<String> selectRolePermissionByUserId(Long userId);
/**
* 查询所有角色
*
* @return 角色列表
*/
List<AppRoleVo> selectRoleAll();
/**
* 根据用户ID获取角色选择框列表
*
* @param userId 用户ID
* @return 角色ID列表
*/
List<Long> selectRoleListByUserId(Long userId);
/**
* 校验角色名称是否唯一
*
* @param roleName 角色名称
* @return 结果
*/
int checkRoleNameUnique(String roleName);
/**
* 校验角色权限是否唯一
*
* @param roleKey 角色权限
* @return 结果
*/
int checkRoleKeyUnique(String roleKey);
}
@@ -0,0 +1,14 @@
package org.dromara.member.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.member.domain.AppSocialAuth;
import org.dromara.member.domain.vo.AppSocialAuthVo;
/**
* App端第三方登录授权Mapper接口
*
* @author pengles
*/
public interface AppSocialAuthMapper extends BaseMapperPlus<AppSocialAuth, AppSocialAuthVo> {
}
@@ -0,0 +1,62 @@
package org.dromara.member.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.member.domain.AppUser;
import org.dromara.member.domain.vo.AppUserVo;
/**
* App端用户信息Mapper接口
*
* @author pengles
*/
public interface AppUserMapper extends BaseMapperPlus<AppUser, AppUserVo> {
/**
* 根据用户账号查询用户
*
* @param userName 用户账号
* @return 用户信息
*/
AppUserVo selectUserByUserName(String userName);
/**
* 根据手机号查询用户信息
*
* @param phone 手机号
* @return 用户信息
*/
AppUserVo selectUserByPhone(String phone);
/**
* 根据邮箱查询用户信息
*
* @param email 邮箱
* @return 用户信息
*/
AppUserVo selectUserByEmail(String email);
/**
* 校验用户账号是否唯一
*
* @param userName 用户账号
* @return 结果
*/
int checkUserNameUnique(String userName);
/**
* 校验手机号码是否唯一
*
* @param phone 手机号码
* @return 结果
*/
int checkPhoneUnique(String phone);
/**
* 校验email是否唯一
*
* @param email 用户邮箱
* @return 结果
*/
int checkEmailUnique(String email);
}
@@ -0,0 +1,14 @@
package org.dromara.member.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.member.domain.AppUserMessage;
import org.dromara.member.domain.vo.AppUserMessageVo;
/**
* App端用户消息读取记录Mapper接口
*
* @author pengles
*/
public interface AppUserMessageMapper extends BaseMapperPlus<AppUserMessage, AppUserMessageVo> {
}
@@ -0,0 +1,51 @@
package org.dromara.member.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.member.domain.AppUserRole;
import org.dromara.member.domain.vo.AppUserRoleVo;
import java.util.List;
/**
* App端用户和角色关联Mapper接口
*
* @author pengles
*/
@Mapper
public interface AppUserRoleMapper extends BaseMapperPlus<AppUserRole, AppUserRoleVo> {
/**
* 通过用户ID删除用户和角色关联
*
* @param userId 用户ID
* @return 结果
*/
int deleteUserRoleByUserId(Long userId);
/**
* 批量删除用户和角色关联
*
* @param ids 需要删除的数据ID
* @return 结果
*/
int deleteUserRole(Long[] ids);
/**
* 通过角色ID查询角色使用数量
*
* @param roleId 角色ID
* @return 结果
*/
int countUserRoleByRoleId(Long roleId);
/**
* 批量新增用户角色信息
*
* @param userRoleList 用户角色列表
* @return 结果
*/
int batchUserRole(@Param("list") List<AppUserRole> userRoleList);
}
@@ -0,0 +1,16 @@
package org.dromara.member.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.member.domain.AppVersion;
import org.dromara.member.domain.vo.AppVersionVo;
/**
* App版本信息Mapper接口
*
* @author pengles
*/
@Mapper
public interface AppVersionMapper extends BaseMapperPlus<AppVersion, AppVersionVo> {
}
@@ -0,0 +1,98 @@
package org.dromara.member.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.member.domain.bo.AppDeviceBo;
import org.dromara.member.domain.vo.AppDeviceVo;
import java.util.Collection;
import java.util.List;
/**
* App端设备信息Service接口
*
* @author pengles
*/
public interface AppDeviceService {
/**
* 查询App端设备信息
*/
AppDeviceVo queryById(Long deviceId);
/**
* 查询App端设备信息列表
*/
TableDataInfo<AppDeviceVo> queryPageList(AppDeviceBo bo, PageQuery pageQuery);
/**
* 查询App端设备信息列表
*/
List<AppDeviceVo> queryList(AppDeviceBo bo);
/**
* 新增App端设备信息
*/
Boolean insertByBo(AppDeviceBo bo);
/**
* 修改App端设备信息
*/
Boolean updateByBo(AppDeviceBo bo);
/**
* 校验并批量删除App端设备信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 根据用户ID查询设备列表
*/
List<AppDeviceVo> queryByUserId(Long userId);
/**
* 绑定设备
*/
Boolean bindDevice(AppDeviceBo bo);
/**
* 解绑设备
*/
Boolean unbindDevice(Long deviceId, Long userId);
/**
* 更新设备活跃状态
*/
Boolean updateActiveStatus(Long deviceId, String isActive);
/**
* 更新推送令牌
*/
Boolean updatePushToken(Long deviceId, String pushToken);
/**
* 获取用户活跃设备
*/
List<AppDeviceVo> getActiveDevices(Long userId);
/**
* 清理非活跃设备
*/
Integer cleanInactiveDevices(Integer days);
/**
* 根据推送令牌查询设备
*/
AppDeviceVo queryByPushToken(String pushToken);
/**
* 设备心跳更新
*/
Boolean heartbeat(Long deviceId);
/**
* 批量更新设备状态
*/
Boolean batchUpdateStatus(List<Long> deviceIds, String isActive);
}
@@ -0,0 +1,167 @@
package org.dromara.member.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.member.domain.bo.AppFileBo;
import org.dromara.member.domain.vo.AppFileVo;
import org.springframework.web.multipart.MultipartFile;
import java.util.Collection;
import java.util.List;
/**
* App端文件信息Service接口
*
* @author pengles
*/
public interface AppFileService {
/**
* 查询App端文件信息
*/
AppFileVo queryById(Long fileId);
/**
* 查询App端文件信息列表
*/
TableDataInfo<AppFileVo> queryPageList(AppFileBo bo, PageQuery pageQuery);
/**
* 查询App端文件信息列表
*/
List<AppFileVo> queryList(AppFileBo bo);
/**
* 新增App端文件信息
*/
Boolean insertByBo(AppFileBo bo);
/**
* 修改App端文件信息
*/
Boolean updateByBo(AppFileBo bo);
/**
* 校验并批量删除App端文件信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 上传文件
*
* @param file 上传的文件
* @param userId 用户ID
* @param fileCategory 文件分类
* @return 文件信息
*/
AppFileVo uploadFile(MultipartFile file, Long userId, Integer fileCategory);
/**
* 上传头像
*
* @param file 头像文件
* @param userId 用户ID
* @return 文件信息
*/
AppFileVo uploadAvatar(MultipartFile file, Long userId);
/**
* 批量上传文件
*
* @param files 文件数组
* @param userId 用户ID
* @param fileCategory 文件分类
* @return 文件信息列表
*/
List<AppFileVo> batchUploadFiles(MultipartFile[] files, Long userId, Integer fileCategory);
/**
* 根据用户ID查询文件列表
*
* @param userId 用户ID
* @param fileCategory 文件分类(可选)
* @return 文件列表
*/
List<AppFileVo> queryByUserId(Long userId, Integer fileCategory);
/**
* 根据文件分类查询文件列表
*
* @param fileCategory 文件分类
* @return 文件列表
*/
List<AppFileVo> queryByCategory(Integer fileCategory);
/**
* 下载文件
*
* @param fileId 文件ID
* @param userId 用户ID
* @return 文件信息
*/
AppFileVo downloadFile(Long fileId, Long userId);
/**
* 删除用户文件
*
* @param fileId 文件ID
* @param userId 用户ID
* @return 删除结果
*/
Boolean deleteUserFile(Long fileId, Long userId);
/**
* 批量删除用户文件
*
* @param fileIds 文件ID列表
* @param userId 用户ID
* @return 删除结果
*/
Boolean batchDeleteUserFiles(Collection<Long> fileIds, Long userId);
/**
* 更新文件状态
*
* @param fileId 文件ID
* @param status 状态
* @param userId 用户ID
* @return 更新结果
*/
Boolean updateFileStatus(Long fileId, String status, Long userId);
/**
* 获取用户文件统计信息
*
* @param userId 用户ID
* @return 统计信息
*/
AppFileVo getUserFileStatistics(Long userId);
/**
* 清理过期文件
*
* @param days 过期天数
* @return 清理数量
*/
Integer cleanExpiredFiles(Integer days);
/**
* 检查文件是否存在
*
* @param fileMd5 文件MD5
* @param userId 用户ID
* @return 文件信息
*/
AppFileVo checkFileExists(String fileMd5, Long userId);
/**
* 更新文件公开状态
*
* @param fileId 文件ID
* @param isPublic 是否公开
* @param userId 用户ID
* @return 更新结果
*/
Boolean updateFilePublicStatus(Long fileId, String isPublic, Long userId);
}
@@ -0,0 +1,162 @@
package org.dromara.member.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.member.domain.AppMessage;
import org.dromara.member.domain.bo.AppMessageBo;
import org.dromara.member.domain.vo.AppMessageVo;
import java.util.Collection;
import java.util.List;
/**
* App端消息Service接口
*
* @author pengles
*/
public interface AppMessageService extends IService<AppMessage> {
/**
* 查询App端消息
*
* @param messageId 消息ID
* @return App端消息
*/
AppMessageVo queryById(Long messageId);
/**
* 查询App端消息列表
*
* @param bo App端消息
* @param pageQuery 分页查询条件
* @return App端消息集合
*/
TableDataInfo<AppMessageVo> queryPageList(AppMessageBo bo, PageQuery pageQuery);
/**
* 查询App端消息列表
*
* @param bo App端消息
* @return App端消息集合
*/
List<AppMessageVo> queryList(AppMessageBo bo);
/**
* 新增App端消息
*
* @param bo App端消息
* @return 结果
*/
Boolean insertByBo(AppMessageBo bo);
/**
* 修改App端消息
*
* @param bo App端消息
* @return 结果
*/
Boolean updateByBo(AppMessageBo bo);
/**
* 校验并批量删除App端消息
*
* @param ids 主键集合
* @param isValid 是否校验,true-删除前校验,false-不校验
* @return 结果
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 获取用户未读消息数量
*
* @param userId 用户ID
* @return 未读消息数量
*/
Long getUnreadCount(Long userId);
/**
* 标记消息为已读
*
* @param messageId 消息ID
* @param userId 用户ID
* @return 结果
*/
Boolean markAsRead(Long messageId, Long userId);
/**
* 批量标记消息为已读
*
* @param messageIds 消息ID列表
* @param userId 用户ID
* @return 结果
*/
Boolean markAsReadBatch(List<Long> messageIds, Long userId);
/**
* 标记所有消息为已读
*
* @param userId 用户ID
* @return 结果
*/
Boolean markAllAsRead(Long userId);
/**
* 发送系统消息
*
* @param bo 消息信息
* @return 结果
*/
Boolean sendSystemMessage(AppMessageBo bo);
/**
* 发送个人消息
*
* @param bo 消息信息
* @return 结果
*/
Boolean sendPersonalMessage(AppMessageBo bo);
/**
* 推送消息到指定用户
*
* @param bo 消息信息
* @return 结果
*/
Boolean pushMessage(AppMessageBo bo);
/**
* 群发消息
*
* @param bo 消息信息
* @return 结果
*/
Boolean broadcastMessage(AppMessageBo bo);
/**
* 发送推送通知
*
* @param userId 用户ID
* @param title 标题
* @param content 内容
* @return 结果
*/
Boolean sendPushNotification(Long userId, String title, String content);
/**
* 批量发送推送通知
*
* @param userIds 用户ID列表
* @param title 标题
* @param content 内容
* @return 结果
*/
Boolean sendBatchPushNotification(List<Long> userIds, String title, String content);
/**
* 清理过期消息
*
* @return 清理数量
*/
Integer cleanExpiredMessages();
}
@@ -0,0 +1,141 @@
package org.dromara.member.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.member.domain.bo.AppOperLogBo;
import org.dromara.member.domain.vo.AppOperLogVo;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
/**
* App端操作日志Service接口
*
* @author pengles
*/
public interface AppOperLogService {
/**
* 查询App端操作日志
*/
AppOperLogVo queryById(Long operId);
/**
* 查询App端操作日志列表
*/
TableDataInfo<AppOperLogVo> queryPageList(AppOperLogBo bo, PageQuery pageQuery);
/**
* 查询App端操作日志列表
*/
List<AppOperLogVo> queryList(AppOperLogBo bo);
/**
* 新增App端操作日志
*/
Boolean insertByBo(AppOperLogBo bo);
/**
* 修改App端操作日志
*/
Boolean updateByBo(AppOperLogBo bo);
/**
* 校验并批量删除App端操作日志信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 记录操作日志
*
* @param title 模块标题
* @param businessType 业务类型
* @param method 方法名称
* @param requestMethod 请求方式
* @param operName 操作人员
* @param operUrl 请求URL
* @param operIp 主机地址
* @param operLocation 操作地点
* @param operParam 请求参数
* @param jsonResult 返回参数
* @param status 操作状态
* @param errorMsg 错误消息
* @param costTime 消耗时间
*/
void recordOperLog(String title, Integer businessType, String method, String requestMethod,
String operName, String operUrl, String operIp, String operLocation,
String operParam, String jsonResult, Integer status, String errorMsg, Long costTime);
/**
* 根据操作人查询操作日志
*
* @param operName 操作人员
* @return 操作日志列表
*/
List<AppOperLogVo> queryByOperName(String operName);
/**
* 清空操作日志
*/
void cleanOperLog();
/**
* 根据时间范围删除操作日志
*
* @param beginTime 开始时间
* @param endTime 结束时间
*/
void deleteOperLogByTime(LocalDateTime beginTime, LocalDateTime endTime);
/**
* 获取操作统计信息
*
* @param operName 操作人员
* @param beginTime 开始时间
* @param endTime 结束时间
* @return 统计信息
*/
Long getOperLogCount(String operName, LocalDateTime beginTime, LocalDateTime endTime);
/**
* 获取用户行为分析数据
*
* @param operName 操作人员
* @param days 统计天数
* @return 行为分析数据
*/
List<AppOperLogVo> getUserBehaviorAnalysis(String operName, Integer days);
/**
* 获取热门操作统计
*
* @param days 统计天数
* @param limit 返回数量限制
* @return 热门操作列表
*/
List<AppOperLogVo> getPopularOperations(Integer days, Integer limit);
/**
* 获取错误操作统计
*
* @param days 统计天数
* @return 错误操作列表
*/
List<AppOperLogVo> getErrorOperations(Integer days);
/**
* 批量记录操作日志
*
* @param operLogs 操作日志列表
*/
void batchInsertOperLog(List<AppOperLogBo> operLogs);
/**
* 异步记录操作日志
*
* @param bo 操作日志业务对象
*/
void asyncRecordOperLog(AppOperLogBo bo);
}
@@ -0,0 +1,169 @@
package org.dromara.member.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.member.domain.AppRole;
import org.dromara.member.domain.bo.AppRoleBo;
import org.dromara.member.domain.vo.AppRoleVo;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* App端角色信息Service接口
*
* @author pengles
*/
public interface AppRoleService extends IService<AppRole> {
/**
* 查询App端角色信息
*
* @param roleId App角色ID
* @return App端角色信息
*/
AppRoleVo queryById(Long roleId);
/**
* 查询App端角色信息列表
*
* @param bo App端角色信息
* @param pageQuery 分页查询条件
* @return App端角色信息集合
*/
TableDataInfo<AppRoleVo> queryPageList(AppRoleBo bo, PageQuery pageQuery);
/**
* 查询App端角色信息列表
*
* @param bo App端角色信息
* @return App端角色信息集合
*/
List<AppRoleVo> queryList(AppRoleBo bo);
/**
* 新增App端角色信息
*
* @param bo App端角色信息
* @return 结果
*/
Boolean insertByBo(AppRoleBo bo);
/**
* 修改App端角色信息
*
* @param bo App端角色信息
* @return 结果
*/
Boolean updateByBo(AppRoleBo bo);
/**
* 校验并批量删除App端角色信息
*
* @param ids 主键集合
* @param isValid 是否校验,true-删除前校验,false-不校验
* @return 结果
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 根据用户ID查询角色
*
* @param userId 用户ID
* @return 角色列表
*/
List<AppRoleVo> selectRolesByUserId(Long userId);
/**
* 根据用户ID获取角色权限
*
* @param userId 用户ID
* @return 权限列表
*/
Set<String> selectRolePermissionByUserId(Long userId);
/**
* 查询所有角色
*
* @return 角色列表
*/
List<AppRoleVo> selectRoleAll();
/**
* 根据用户ID获取角色选择框列表
*
* @param userId 用户ID
* @return 选中角色ID列表
*/
List<Long> selectRoleListByUserId(Long userId);
/**
* 校验角色名称是否唯一
*
* @param roleName 角色名称
* @return 结果
*/
boolean checkRoleNameUnique(String roleName);
/**
* 校验角色权限是否唯一
*
* @param roleKey 角色权限
* @return 结果
*/
boolean checkRoleKeyUnique(String roleKey);
/**
* 校验角色是否允许操作
*
* @param roleId 角色ID
* @return 结果
*/
boolean checkRoleAllowed(Long roleId);
/**
* 校验角色是否有数据权限
*
* @param roleId 角色ID
* @return 结果
*/
boolean checkRoleDataScope(Long roleId);
/**
* 通过角色ID查询角色使用数量
*
* @param roleId 角色ID
* @return 结果
*/
long countUserRoleByRoleId(Long roleId);
/**
* 取消授权用户角色
*
* @param userId 用户ID
* @param roleId 角色ID
* @return 结果
*/
boolean deleteAuthUser(Long userId, Long roleId);
/**
* 批量取消授权用户角色
*
* @param roleId 角色ID
* @param userIds 需要取消授权的用户数据ID
* @return 结果
*/
boolean deleteAuthUsers(Long roleId, Long[] userIds);
/**
* 批量选择授权用户角色
*
* @param roleId 角色ID
* @param userIds 需要授权的用户数据ID
* @return 结果
*/
boolean insertAuthUsers(Long roleId, Long[] userIds);
}
@@ -0,0 +1,129 @@
package org.dromara.member.service;
import com.baomidou.mybatisplus.extension.service.IService;
import me.zhyd.oauth.model.AuthUser;
import org.dromara.member.domain.AppSocialAuth;
import org.dromara.member.domain.vo.AppSocialAuthVo;
import org.dromara.member.domain.vo.AppUserVo;
import java.util.List;
/**
* App端第三方登录授权Service接口
*
* @author pengles
*/
public interface AppSocialAuthService extends IService<AppSocialAuth> {
/**
* 查询App端第三方登录授权
*
* @param id 主键
* @return App端第三方登录授权
*/
AppSocialAuthVo queryById(Long id);
/**
* 根据用户ID查询社交认证列表
*
* @param userId 用户ID
* @return 社交认证列表
*/
List<AppSocialAuthVo> queryByUserId(Long userId);
/**
* 根据平台和openId查询社交认证
*
* @param platform 平台
* @param openId openId
* @return 社交认证信息
*/
AppSocialAuth queryByPlatformAndOpenId(String platform, String openId);
/**
* 根据平台和unionId查询社交认证
*
* @param platform 平台
* @param unionId unionId
* @return 社交认证信息
*/
AppSocialAuth queryByPlatformAndUnionId(String platform, String unionId);
/**
* 新增App端第三方登录授权
*
* @param appSocialAuth App端第三方登录授权
* @return 结果
*/
Boolean insertAppSocialAuth(AppSocialAuth appSocialAuth);
/**
* 修改App端第三方登录授权
*
* @param appSocialAuth App端第三方登录授权
* @return 结果
*/
Boolean updateAppSocialAuth(AppSocialAuth appSocialAuth);
/**
* 删除App端第三方登录授权
*
* @param id 主键
* @return 结果
*/
Boolean deleteAppSocialAuthById(Long id);
/**
* 根据用户ID删除社交认证
*
* @param userId 用户ID
* @return 结果
*/
Boolean deleteByUserId(Long userId);
/**
* 更新访问令牌
*
* @param id 主键
* @param accessToken 访问令牌
* @param expireIn 过期时间
* @param refreshToken 刷新令牌
* @return 结果
*/
Boolean updateAccessToken(Long id, String accessToken, Integer expireIn, String refreshToken);
/**
* 根据用户ID和平台删除绑定关系
*
* @param userId 用户ID
* @param platform 第三方平台
* @return 是否成功
*/
Boolean deleteByUserIdAndPlatform(Long userId, String platform);
/**
* 根据平台和openId查询绑定的用户信息
*
* @param platform 平台
* @param openId openId
* @return 用户信息
*/
AppUserVo selectUserByPlatformAndOpenId(String platform, String openId);
/**
* 根据平台和unionId查询绑定的用户信息
*
* @param platform 平台
* @param unionId unionId
* @return 用户信息
*/
AppUserVo selectUserByPlatformAndUnionId(String platform, String unionId);
/**
* 绑定第三方用户
*
* @param authUserData 授权响应实体
*/
void socialRegister(AuthUser authUserData);
}
@@ -0,0 +1,155 @@
package org.dromara.member.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.member.domain.AppUser;
import org.dromara.member.domain.bo.AppUserBo;
import org.dromara.member.domain.vo.AppUserVo;
import java.util.Collection;
import java.util.List;
/**
* App端用户信息Service接口
*
* @author pengles
*/
public interface AppUserService extends IService<AppUser> {
/**
* 查询App端用户信息
*
* @param userId App用户ID
* @return App端用户信息
*/
AppUserVo queryById(Long userId);
/**
* 查询App端用户信息列表
*
* @param bo App端用户信息
* @param pageQuery 分页查询条件
* @return App端用户信息集合
*/
TableDataInfo<AppUserVo> queryPageList(AppUserBo bo, PageQuery pageQuery);
/**
* 查询App端用户信息列表
*
* @param bo App端用户信息
* @return App端用户信息集合
*/
List<AppUserVo> queryList(AppUserBo bo);
/**
* 新增App端用户信息
*
* @param bo App端用户信息
* @return 结果
*/
Boolean insertByBo(AppUserBo bo);
/**
* 修改App端用户信息
*
* @param bo App端用户信息
* @return 结果
*/
Boolean updateByBo(AppUserBo bo);
/**
* 校验并批量删除App端用户信息
*
* @param ids 主键集合
* @param isValid 是否校验,true-删除前校验,false-不校验
* @return 结果
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 根据用户账号查询用户
*
* @param userName 用户账号
* @return 用户信息
*/
AppUserVo selectUserByUserName(String userName);
/**
* 根据手机号查询用户信息
*
* @param phone 手机号
* @return 用户信息
*/
AppUserVo selectUserByPhone(String phone);
/**
* 根据邮箱查询用户信息
*
* @param email 邮箱
* @return 用户信息
*/
AppUserVo selectUserByEmail(String email);
/**
* 校验用户账号是否唯一
*
* @param userName 用户账号
* @return 结果
*/
boolean checkUserNameUnique(String userName);
/**
* 校验手机号码是否唯一
*
* @param phone 手机号码
* @return 结果
*/
boolean checkPhoneUnique(String phone);
/**
* 校验email是否唯一
*
* @param email 用户邮箱
* @return 结果
*/
boolean checkEmailUnique(String email);
/**
* 用户注册
*
* @param bo 用户信息
* @return 结果
*/
Boolean register(AppUserBo bo);
/**
* 修改用户密码
*
* @param userId 用户ID
* @param oldPassword 旧密码
* @param newPassword 新密码
* @return 结果
*/
Boolean updatePassword(Long userId, String oldPassword, String newPassword);
/**
* 重置用户密码
*
* @param userId 用户ID
* @param newPassword 新密码
* @return 结果
*/
Boolean resetPassword(Long userId, String newPassword);
/**
* 修改用户状态
*
* @param userId 用户ID
* @param status 状态
* @return 结果
*/
Boolean updateUserStatus(Long userId, String status);
}
@@ -0,0 +1,88 @@
package org.dromara.member.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.member.domain.bo.AppVersionBo;
import org.dromara.member.domain.vo.AppVersionVo;
import java.util.Collection;
import java.util.List;
/**
* App版本信息Service接口
*
* @author pengles
*/
public interface AppVersionService {
/**
* 查询App版本信息
*/
AppVersionVo queryById(Long versionId);
/**
* 查询App版本信息列表
*/
TableDataInfo<AppVersionVo> queryPageList(AppVersionBo bo, PageQuery pageQuery);
/**
* 查询App版本信息列表
*/
List<AppVersionVo> queryList(AppVersionBo bo);
/**
* 新增App版本信息
*/
Boolean insertByBo(AppVersionBo bo);
/**
* 修改App版本信息
*/
Boolean updateByBo(AppVersionBo bo);
/**
* 校验并批量删除App版本信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 检查版本更新
*/
AppVersionVo checkUpdate(String platform, Integer currentVersionCode);
/**
* 获取指定平台的最新版本
*/
AppVersionVo getLatestVersion(String platform);
/**
* 检查版本号是否唯一
*/
Boolean checkVersionCodeUnique(AppVersionBo bo);
/**
* 获取版本历史列表
*/
List<AppVersionVo> getVersionHistory(String platform, Integer limit);
/**
* 检查是否需要强制更新
*/
Boolean checkForceUpdate(String platform, Integer currentVersionCode);
/**
* 获取版本更新信息
*/
AppVersionVo getUpdateInfo(String platform, Integer currentVersionCode);
/**
* 批量发布版本
*/
Boolean batchPublish(List<Long> versionIds);
/**
* 撤回版本
*/
Boolean withdrawVersion(Long versionId);
}
@@ -0,0 +1,333 @@
package org.dromara.member.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.member.domain.AppDevice;
import org.dromara.member.domain.bo.AppDeviceBo;
import org.dromara.member.domain.vo.AppDeviceVo;
import org.dromara.member.exception.DeviceException;
import org.dromara.member.mapper.AppDeviceMapper;
import org.dromara.member.service.AppDeviceService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
/**
* App端设备信息Service业务层处理
*
* @author pengles
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class AppDeviceServiceImpl extends ServiceImpl<AppDeviceMapper, AppDevice> implements AppDeviceService {
private final AppDeviceMapper baseMapper;
/**
* 查询App端设备信息
*/
@Override
public AppDeviceVo queryById(Long deviceId) {
return baseMapper.selectVoById(deviceId);
}
/**
* 查询App端设备信息列表
*/
@Override
public TableDataInfo<AppDeviceVo> queryPageList(AppDeviceBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<AppDevice> lqw = buildQueryWrapper(bo);
Page<AppDeviceVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询App端设备信息列表
*/
@Override
public List<AppDeviceVo> queryList(AppDeviceBo bo) {
LambdaQueryWrapper<AppDevice> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<AppDevice> buildQueryWrapper(AppDeviceBo bo) {
LambdaQueryWrapper<AppDevice> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getUserId() != null, AppDevice::getUserId, bo.getUserId());
lqw.eq(StringUtils.isNotBlank(bo.getDeviceType()), AppDevice::getDeviceType, bo.getDeviceType());
lqw.like(StringUtils.isNotBlank(bo.getDeviceName()), AppDevice::getDeviceName, bo.getDeviceName());
lqw.like(StringUtils.isNotBlank(bo.getDeviceModel()), AppDevice::getDeviceModel, bo.getDeviceModel());
lqw.eq(StringUtils.isNotBlank(bo.getDeviceBrand()), AppDevice::getDeviceBrand, bo.getDeviceBrand());
lqw.eq(StringUtils.isNotBlank(bo.getOsVersion()), AppDevice::getOsVersion, bo.getOsVersion());
lqw.eq(StringUtils.isNotBlank(bo.getAppVersion()), AppDevice::getAppVersion, bo.getAppVersion());
lqw.eq(StringUtils.isNotBlank(bo.getIsActive()), AppDevice::getIsActive, bo.getIsActive());
lqw.orderByDesc(AppDevice::getLastActiveTime);
return lqw;
}
/**
* 新增App端设备信息
*/
@Override
public Boolean insertByBo(AppDeviceBo bo) {
AppDevice add = BeanUtil.toBean(bo, AppDevice.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setDeviceId(add.getDeviceId());
}
return flag;
}
/**
* 修改App端设备信息
*/
@Override
public Boolean updateByBo(AppDeviceBo bo) {
AppDevice update = BeanUtil.toBean(bo, AppDevice.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(AppDevice entity) {
// TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除App端设备信息
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 根据用户ID查询设备列表
*/
@Override
public List<AppDeviceVo> queryByUserId(Long userId) {
LambdaQueryWrapper<AppDevice> lqw = Wrappers.lambdaQuery();
lqw.eq(AppDevice::getUserId, userId);
lqw.orderByDesc(AppDevice::getLastActiveTime);
return baseMapper.selectVoList(lqw);
}
/**
* 绑定设备
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean bindDevice(AppDeviceBo bo) {
// 检查用户设备数量限制
LambdaQueryWrapper<AppDevice> countLqw = Wrappers.lambdaQuery();
countLqw.eq(AppDevice::getUserId, bo.getUserId());
countLqw.eq(AppDevice::getIsActive, "1");
long deviceCount = baseMapper.selectCount(countLqw);
if (deviceCount >= 10) { // 限制每个用户最多绑定10台设备
throw DeviceException.deviceLimitExceeded(10);
}
// 检查是否已存在相同设备
LambdaQueryWrapper<AppDevice> lqw = Wrappers.lambdaQuery();
lqw.eq(AppDevice::getUserId, bo.getUserId());
lqw.eq(AppDevice::getDeviceType, bo.getDeviceType());
lqw.eq(StringUtils.isNotBlank(bo.getPushToken()), AppDevice::getPushToken, bo.getPushToken());
AppDevice existDevice = baseMapper.selectOne(lqw);
if (existDevice != null) {
// 更新现有设备信息
existDevice.setDeviceName(bo.getDeviceName());
existDevice.setDeviceModel(bo.getDeviceModel());
existDevice.setDeviceBrand(bo.getDeviceBrand());
existDevice.setOsVersion(bo.getOsVersion());
existDevice.setAppVersion(bo.getAppVersion());
existDevice.setIsActive("1");
existDevice.setLastActiveTime(LocalDateTime.now());
boolean result = baseMapper.updateById(existDevice) > 0;
if (!result) {
throw DeviceException.bindFailed("设备信息更新失败");
}
return result;
} else {
// 新增设备
bo.setIsActive("1");
bo.setLastActiveTime(LocalDateTime.now());
boolean result = insertByBo(bo);
if (!result) {
throw DeviceException.bindFailed("设备信息保存失败");
}
return result;
}
}
/**
* 解绑设备
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean unbindDevice(Long deviceId, Long userId) {
LambdaQueryWrapper<AppDevice> lqw = Wrappers.lambdaQuery();
lqw.eq(AppDevice::getDeviceId, deviceId);
lqw.eq(AppDevice::getUserId, userId);
AppDevice device = baseMapper.selectOne(lqw);
if (device == null) {
throw DeviceException.deviceNotFound(deviceId);
}
boolean result = baseMapper.deleteById(deviceId) > 0;
if (!result) {
throw DeviceException.unbindFailed("设备删除失败");
}
return result;
}
/**
* 更新设备活跃状态
*/
@Override
public Boolean updateActiveStatus(Long deviceId, String isActive) {
if (!"0".equals(isActive) && !"1".equals(isActive)) {
throw DeviceException.invalidStatus(isActive);
}
AppDevice existDevice = baseMapper.selectById(deviceId);
if (existDevice == null) {
throw DeviceException.deviceNotFound(deviceId);
}
AppDevice device = new AppDevice();
device.setDeviceId(deviceId);
device.setIsActive(isActive);
if ("1".equals(isActive)) {
device.setLastActiveTime(LocalDateTime.now());
}
return baseMapper.updateById(device) > 0;
}
/**
* 更新推送令牌
*/
@Override
public Boolean updatePushToken(Long deviceId, String pushToken) {
if (StringUtils.isBlank(pushToken)) {
throw DeviceException.invalidPushToken(pushToken);
}
AppDevice existDevice = baseMapper.selectById(deviceId);
if (existDevice == null) {
throw DeviceException.deviceNotFound(deviceId);
}
AppDevice device = new AppDevice();
device.setDeviceId(deviceId);
device.setPushToken(pushToken);
device.setLastActiveTime(LocalDateTime.now());
return baseMapper.updateById(device) > 0;
}
/**
* 获取用户活跃设备
*/
@Override
public List<AppDeviceVo> getActiveDevices(Long userId) {
LambdaQueryWrapper<AppDevice> lqw = Wrappers.lambdaQuery();
lqw.eq(AppDevice::getUserId, userId);
lqw.eq(AppDevice::getIsActive, "1");
lqw.orderByDesc(AppDevice::getLastActiveTime);
return baseMapper.selectVoList(lqw);
}
/**
* 清理非活跃设备
*/
@Override
public Integer cleanInactiveDevices(Integer days) {
if (days == null || days <= 0) {
days = 30; // 默认30天
}
LambdaQueryWrapper<AppDevice> lqw = Wrappers.lambdaQuery();
lqw.lt(AppDevice::getLastActiveTime, LocalDateTime.now().minusDays(days));
lqw.or();
lqw.eq(AppDevice::getIsActive, "0");
List<AppDevice> inactiveDevices = baseMapper.selectList(lqw);
if (!inactiveDevices.isEmpty()) {
List<Long> ids = inactiveDevices.stream().map(AppDevice::getDeviceId).toList();
baseMapper.deleteBatchIds(ids);
log.info("清理非活跃设备 {} 个", ids.size());
return ids.size();
}
return 0;
}
/**
* 根据推送令牌查询设备
*/
@Override
public AppDeviceVo queryByPushToken(String pushToken) {
if (StringUtils.isBlank(pushToken)) {
return null;
}
LambdaQueryWrapper<AppDevice> lqw = Wrappers.lambdaQuery();
lqw.eq(AppDevice::getPushToken, pushToken);
lqw.eq(AppDevice::getIsActive, "1");
return baseMapper.selectVoOne(lqw);
}
/**
* 设备心跳更新
*/
@Override
public Boolean heartbeat(Long deviceId) {
AppDevice device = new AppDevice();
device.setDeviceId(deviceId);
device.setIsActive("1");
device.setLastActiveTime(LocalDateTime.now());
return baseMapper.updateById(device) > 0;
}
/**
* 批量更新设备状态
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean batchUpdateStatus(List<Long> deviceIds, String isActive) {
if (ObjectUtil.isEmpty(deviceIds)) {
return false;
}
AppDevice device = new AppDevice();
device.setIsActive(isActive);
if ("1".equals(isActive)) {
device.setLastActiveTime(LocalDateTime.now());
}
LambdaQueryWrapper<AppDevice> lqw = Wrappers.lambdaQuery();
lqw.in(AppDevice::getDeviceId, deviceIds);
return baseMapper.update(device, lqw) > 0;
}
}
@@ -0,0 +1,420 @@
package org.dromara.member.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.oss.core.OssClient;
import org.dromara.common.oss.entity.UploadResult;
import org.dromara.common.oss.factory.OssFactory;
import org.dromara.member.domain.AppFile;
import org.dromara.member.domain.bo.AppFileBo;
import org.dromara.member.domain.vo.AppFileVo;
import org.dromara.member.exception.FileException;
import org.dromara.member.mapper.AppFileMapper;
import org.dromara.member.service.AppFileService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* App端文件信息Service业务层处理
*
* @author pengles
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class AppFileServiceImpl implements AppFileService {
private final AppFileMapper baseMapper;
/**
* 查询App端文件信息
*/
@Override
public AppFileVo queryById(Long fileId) {
return baseMapper.selectVoById(fileId);
}
/**
* 查询App端文件信息列表
*/
@Override
public TableDataInfo<AppFileVo> queryPageList(AppFileBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<AppFile> lqw = buildQueryWrapper(bo);
Page<AppFileVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询App端文件信息列表
*/
@Override
public List<AppFileVo> queryList(AppFileBo bo) {
LambdaQueryWrapper<AppFile> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<AppFile> buildQueryWrapper(AppFileBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<AppFile> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getUserId() != null, AppFile::getUserId, bo.getUserId());
lqw.like(StringUtils.isNotBlank(bo.getFileName()), AppFile::getFileName, bo.getFileName());
lqw.like(StringUtils.isNotBlank(bo.getOriginalName()), AppFile::getOriginalName, bo.getOriginalName());
lqw.eq(StringUtils.isNotBlank(bo.getFileType()), AppFile::getFileType, bo.getFileType());
lqw.eq(StringUtils.isNotBlank(bo.getFileSuffix()), AppFile::getFileSuffix, bo.getFileSuffix());
lqw.eq(bo.getFileCategory() != null, AppFile::getFileCategory, bo.getFileCategory());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), AppFile::getStatus, bo.getStatus());
lqw.eq(StringUtils.isNotBlank(bo.getIsPublic()), AppFile::getIsPublic, bo.getIsPublic());
lqw.between(params.get("beginTime") != null && params.get("endTime") != null,
AppFile::getCreateTime, params.get("beginTime"), params.get("endTime"));
lqw.orderByDesc(AppFile::getCreateTime);
return lqw;
}
/**
* 新增App端文件信息
*/
@Override
public Boolean insertByBo(AppFileBo bo) {
AppFile add = BeanUtil.toBean(bo, AppFile.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setFileId(add.getFileId());
}
return flag;
}
/**
* 修改App端文件信息
*/
@Override
public Boolean updateByBo(AppFileBo bo) {
AppFile update = BeanUtil.toBean(bo, AppFile.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(AppFile entity) {
// 可以添加一些业务校验逻辑
}
/**
* 批量删除App端文件信息
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// 可以添加删除前的校验逻辑
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 上传文件
*/
@Override
@Transactional(rollbackFor = Exception.class)
public AppFileVo uploadFile(MultipartFile file, Long userId, Integer fileCategory) {
if (file.isEmpty()) {
throw FileException.fileEmpty();
}
// 检查文件大小(限制为100MB
if (file.getSize() > 100 * 1024 * 1024) {
throw FileException.fileSizeExceeded(100);
}
try {
// 计算文件MD5
String fileMd5 = DigestUtil.md5Hex(file.getInputStream());
// 检查文件是否已存在
AppFileVo existFile = checkFileExists(fileMd5, userId);
if (existFile != null) {
return existFile;
}
// 上传到OSS
OssClient ossClient = OssFactory.instance();
UploadResult uploadResult = ossClient.uploadSuffix(file.getBytes(), getFileSuffix(file.getOriginalFilename()), file.getContentType());
// 保存文件信息
AppFile appFile = new AppFile();
appFile.setUserId(userId);
appFile.setFileName(uploadResult.getFilename());
appFile.setOriginalName(file.getOriginalFilename());
appFile.setFilePath(uploadResult.getFilename());
appFile.setFileUrl(uploadResult.getUrl());
appFile.setFileSize(file.getSize());
appFile.setFileType(file.getContentType());
appFile.setFileSuffix(getFileSuffix(file.getOriginalFilename()));
appFile.setFileCategory(fileCategory != null ? fileCategory : 6); // 默认为其他
appFile.setStorageService(ossClient.getConfigKey());
appFile.setStatus("0"); // 正常
appFile.setDownloadCount(0L);
appFile.setIsPublic("0"); // 默认私有
appFile.setFileMd5(fileMd5);
baseMapper.insert(appFile);
return MapstructUtils.convert(appFile, AppFileVo.class);
} catch (IOException e) {
log.error("文件上传失败", e);
throw FileException.uploadFailed(e.getMessage());
}
}
/**
* 上传头像
*/
@Override
public AppFileVo uploadAvatar(MultipartFile file, Long userId) {
// 检查文件类型
String contentType = file.getContentType();
if (!isImageFile(contentType)) {
throw FileException.unsupportedFileType(contentType);
}
// 检查文件大小(限制为5MB
if (file.getSize() > 5 * 1024 * 1024) {
throw FileException.fileSizeExceeded(5);
}
return uploadFile(file, userId, 1); // 头像分类
}
/**
* 批量上传文件
*/
@Override
@Transactional(rollbackFor = Exception.class)
public List<AppFileVo> batchUploadFiles(MultipartFile[] files, Long userId, Integer fileCategory) {
List<AppFileVo> result = new ArrayList<>();
for (MultipartFile file : files) {
try {
AppFileVo fileVo = uploadFile(file, userId, fileCategory);
result.add(fileVo);
} catch (Exception e) {
log.error("批量上传文件失败: {}", file.getOriginalFilename(), e);
// 继续处理其他文件
}
}
return result;
}
/**
* 根据用户ID查询文件列表
*/
@Override
public List<AppFileVo> queryByUserId(Long userId, Integer fileCategory) {
LambdaQueryWrapper<AppFile> lqw = Wrappers.lambdaQuery();
lqw.eq(AppFile::getUserId, userId);
lqw.eq(fileCategory != null, AppFile::getFileCategory, fileCategory);
lqw.eq(AppFile::getStatus, "0"); // 正常状态
lqw.orderByDesc(AppFile::getCreateTime);
return baseMapper.selectVoList(lqw);
}
/**
* 根据文件分类查询文件列表
*/
@Override
public List<AppFileVo> queryByCategory(Integer fileCategory) {
LambdaQueryWrapper<AppFile> lqw = Wrappers.lambdaQuery();
lqw.eq(AppFile::getFileCategory, fileCategory);
lqw.eq(AppFile::getStatus, "0"); // 正常状态
lqw.eq(AppFile::getIsPublic, "1"); // 公开文件
lqw.orderByDesc(AppFile::getCreateTime);
return baseMapper.selectVoList(lqw);
}
/**
* 下载文件
*/
@Override
@Transactional(rollbackFor = Exception.class)
public AppFileVo downloadFile(Long fileId, Long userId) {
AppFile appFile = baseMapper.selectById(fileId);
if (appFile == null) {
throw FileException.fileNotFound(fileId);
}
// 检查权限
if (!appFile.getUserId().equals(userId) && !"1".equals(appFile.getIsPublic())) {
throw FileException.accessDenied(fileId);
}
// 更新下载次数
appFile.setDownloadCount(appFile.getDownloadCount() + 1);
baseMapper.updateById(appFile);
return MapstructUtils.convert(appFile, AppFileVo.class);
}
/**
* 删除用户文件
*/
@Override
public Boolean deleteUserFile(Long fileId, Long userId) {
AppFile appFile = baseMapper.selectById(fileId);
if (appFile == null) {
throw FileException.fileNotFound(fileId);
}
if (!appFile.getUserId().equals(userId)) {
throw FileException.accessDenied(fileId);
}
return baseMapper.deleteById(fileId) > 0;
}
/**
* 批量删除用户文件
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean batchDeleteUserFiles(Collection<Long> fileIds, Long userId) {
for (Long fileId : fileIds) {
deleteUserFile(fileId, userId);
}
return true;
}
/**
* 更新文件状态
*/
@Override
public Boolean updateFileStatus(Long fileId, String status, Long userId) {
AppFile appFile = baseMapper.selectById(fileId);
if (appFile == null) {
throw new ServiceException("文件不存在");
}
if (!appFile.getUserId().equals(userId)) {
throw new ServiceException("无权限修改该文件");
}
appFile.setStatus(status);
return baseMapper.updateById(appFile) > 0;
}
/**
* 获取用户文件统计信息
*/
@Override
public AppFileVo getUserFileStatistics(Long userId) {
LambdaQueryWrapper<AppFile> lqw = Wrappers.lambdaQuery();
lqw.eq(AppFile::getUserId, userId);
lqw.eq(AppFile::getStatus, "0");
List<AppFile> files = baseMapper.selectList(lqw);
AppFileVo statistics = new AppFileVo();
statistics.setUserId(userId);
if (!files.isEmpty()) {
long totalSize = files.stream().mapToLong(AppFile::getFileSize).sum();
long totalDownloads = files.stream().mapToLong(AppFile::getDownloadCount).sum();
statistics.setFileSize(totalSize);
statistics.setDownloadCount(totalDownloads);
}
return statistics;
}
/**
* 清理过期文件
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Integer cleanExpiredFiles(Integer days) {
LocalDateTime expiredTime = LocalDateTime.now().minusDays(days);
LambdaQueryWrapper<AppFile> lqw = Wrappers.lambdaQuery();
lqw.lt(AppFile::getCreateTime, expiredTime);
lqw.eq(AppFile::getStatus, "1"); // 已删除状态
List<AppFile> expiredFiles = baseMapper.selectList(lqw);
if (!expiredFiles.isEmpty()) {
List<Long> fileIds = expiredFiles.stream().map(AppFile::getFileId).collect(Collectors.toList());
baseMapper.deleteBatchIds(fileIds);
}
return expiredFiles.size();
}
/**
* 检查文件是否存在
*/
@Override
public AppFileVo checkFileExists(String fileMd5, Long userId) {
LambdaQueryWrapper<AppFile> lqw = Wrappers.lambdaQuery();
lqw.eq(AppFile::getFileMd5, fileMd5);
lqw.eq(AppFile::getUserId, userId);
lqw.eq(AppFile::getStatus, "0");
AppFile existFile = baseMapper.selectOne(lqw);
return existFile != null ? MapstructUtils.convert(existFile, AppFileVo.class) : null;
}
/**
* 更新文件公开状态
*/
@Override
public Boolean updateFilePublicStatus(Long fileId, String isPublic, Long userId) {
AppFile appFile = baseMapper.selectById(fileId);
if (appFile == null) {
throw new ServiceException("文件不存在");
}
if (!appFile.getUserId().equals(userId)) {
throw new ServiceException("无权限修改该文件");
}
appFile.setIsPublic(isPublic);
return baseMapper.updateById(appFile) > 0;
}
/**
* 获取文件后缀
*/
private String getFileSuffix(String fileName) {
if (StringUtils.isBlank(fileName)) {
return "";
}
int lastDotIndex = fileName.lastIndexOf(".");
return lastDotIndex > 0 ? fileName.substring(lastDotIndex) : "";
}
/**
* 检查是否为图片文件
*/
private boolean isImageFile(String contentType) {
return StringUtils.isNotBlank(contentType) &&
(contentType.startsWith("image/") ||
contentType.equals("application/octet-stream"));
}
}
@@ -0,0 +1,310 @@
package org.dromara.member.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.member.domain.AppMessage;
import org.dromara.member.domain.bo.AppMessageBo;
import org.dromara.member.domain.vo.AppMessageVo;
import org.dromara.member.mapper.AppMessageMapper;
import org.dromara.member.service.AppMessageService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
/**
* App端消息Service业务层处理
*
* @author pengles
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class AppMessageServiceImpl extends ServiceImpl<AppMessageMapper, AppMessage> implements AppMessageService {
private final AppMessageMapper baseMapper;
/**
* 查询App端消息
*/
@Override
public AppMessageVo queryById(Long messageId) {
return baseMapper.selectVoById(messageId);
}
/**
* 查询App端消息列表
*/
@Override
public TableDataInfo<AppMessageVo> queryPageList(AppMessageBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<AppMessage> lqw = buildQueryWrapper(bo);
Page<AppMessageVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询App端消息列表
*/
@Override
public List<AppMessageVo> queryList(AppMessageBo bo) {
LambdaQueryWrapper<AppMessage> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<AppMessage> buildQueryWrapper(AppMessageBo bo) {
LambdaQueryWrapper<AppMessage> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(bo.getTitle()), AppMessage::getTitle, bo.getTitle());
lqw.like(StringUtils.isNotBlank(bo.getContent()), AppMessage::getContent, bo.getContent());
lqw.eq(StringUtils.isNotBlank(bo.getMessageType()), AppMessage::getMessageType, bo.getMessageType());
lqw.eq(StringUtils.isNotBlank(bo.getTargetType()), AppMessage::getTargetType, bo.getTargetType());
lqw.like(StringUtils.isNotBlank(bo.getTargetIds()), AppMessage::getTargetIds, bo.getTargetIds());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), AppMessage::getStatus, bo.getStatus());
// 如果指定了接收者ID,查询该用户相关的消息
if (ObjectUtil.isNotNull(bo.getReceiverId())) {
lqw.and(wrapper -> wrapper.eq(AppMessage::getTargetType, "all")
.or()
.and(subWrapper -> subWrapper.eq(AppMessage::getTargetType, "user")
.like(AppMessage::getTargetIds, bo.getReceiverId().toString())));
}
lqw.orderByDesc(AppMessage::getCreateTime);
return lqw;
}
/**
* 新增App端消息
*/
@Override
public Boolean insertByBo(AppMessageBo bo) {
AppMessage add = BeanUtil.toBean(bo, AppMessage.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setMessageId(add.getMessageId());
}
return flag;
}
/**
* 修改App端消息
*/
@Override
public Boolean updateByBo(AppMessageBo bo) {
AppMessage update = BeanUtil.toBean(bo, AppMessage.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(AppMessage entity) {
// TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除App端消息
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 获取用户未读消息数量
*/
@Override
public Long getUnreadCount(Long userId) {
LambdaQueryWrapper<AppMessage> lqw = Wrappers.lambdaQuery();
lqw.and(wrapper -> wrapper.eq(AppMessage::getTargetType, "user")
.like(AppMessage::getTargetIds, userId.toString())
.or()
.eq(AppMessage::getTargetType, "all"));
lqw.eq(AppMessage::getStatus, "1"); // 已发送
return baseMapper.selectCount(lqw);
}
/**
* 标记消息为已读
*/
@Override
public Boolean markAsRead(Long messageId, Long userId) {
// 由于当前表结构不支持已读状态,这里只是示例实现
// 实际项目中可能需要单独的消息阅读记录表
log.info("用户 {} 标记消息 {} 为已读", userId, messageId);
return true;
}
/**
* 批量标记消息为已读
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean markAsReadBatch(List<Long> messageIds, Long userId) {
// 由于当前表结构不支持已读状态,这里只是示例实现
log.info("用户 {} 批量标记消息为已读,消息数量: {}", userId, messageIds.size());
return true;
}
/**
* 标记所有消息为已读
*/
@Override
public Boolean markAllAsRead(Long userId) {
// 由于当前表结构不支持已读状态,这里只是示例实现
log.info("用户 {} 标记所有消息为已读", userId);
return true;
}
/**
* 发送系统消息
*/
@Override
public Boolean sendSystemMessage(AppMessageBo bo) {
bo.setMessageType("system"); // 系统消息
bo.setTargetType("all"); // 全部用户
bo.setStatus("0"); // 待发送
bo.setSendTime(LocalDateTime.now());
return insertByBo(bo);
}
/**
* 发送个人消息
*/
@Override
public Boolean sendPersonalMessage(AppMessageBo bo) {
bo.setMessageType("notice"); // 通知消息
bo.setTargetType("user"); // 指定用户
bo.setStatus("0"); // 待发送
bo.setSendTime(LocalDateTime.now());
return insertByBo(bo);
}
/**
* 推送消息到指定用户
*/
@Override
public Boolean pushMessage(AppMessageBo bo) {
bo.setTargetType("user");
bo.setStatus("0"); // 待发送
bo.setSendTime(LocalDateTime.now());
// 先保存消息
Boolean result = insertByBo(bo);
if (result) {
// 发送推送通知
String[] userIds = bo.getTargetIds().split(",");
for (String userId : userIds) {
sendPushNotification(Long.valueOf(userId), bo.getTitle(), bo.getContent());
}
// 更新发送状态
AppMessage update = new AppMessage();
update.setMessageId(bo.getMessageId());
update.setStatus("1"); // 已发送
baseMapper.updateById(update);
}
return result;
}
/**
* 群发消息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean broadcastMessage(AppMessageBo bo) {
bo.setMessageType("activity"); // 活动消息
bo.setTargetType("all"); // 全部用户
bo.setStatus("0"); // 待发送
bo.setSendTime(LocalDateTime.now());
Boolean result = insertByBo(bo);
if (result) {
// 发送批量推送通知
sendBatchPushNotification(null, bo.getTitle(), bo.getContent());
// 更新发送状态
AppMessage update = new AppMessage();
update.setMessageId(bo.getMessageId());
update.setStatus("1"); // 已发送
baseMapper.updateById(update);
}
return result;
}
/**
* 发送推送通知
*/
@Override
public Boolean sendPushNotification(Long userId, String title, String content) {
try {
// TODO: 集成第三方推送服务(极光推送、友盟推送等)
// 这里只是示例代码,实际需要根据具体的推送服务进行实现
log.info("发送推送通知 - 用户ID: {}, 标题: {}, 内容: {}", userId, title, content);
// 模拟推送成功
return true;
} catch (Exception e) {
log.error("发送推送通知失败", e);
return false;
}
}
/**
* 批量发送推送通知
*/
@Override
public Boolean sendBatchPushNotification(List<Long> userIds, String title, String content) {
try {
// TODO: 集成第三方推送服务的批量推送接口
if (userIds != null) {
log.info("批量发送推送通知 - 用户数量: {}, 标题: {}, 内容: {}", userIds.size(), title, content);
} else {
log.info("全员推送通知 - 标题: {}, 内容: {}", title, content);
}
// 模拟推送成功
return true;
} catch (Exception e) {
log.error("批量发送推送通知失败", e);
return false;
}
}
/**
* 清理过期消息
*/
@Override
public Integer cleanExpiredMessages() {
// 清理30天前的已发送消息
LambdaQueryWrapper<AppMessage> lqw = Wrappers.lambdaQuery();
lqw.lt(AppMessage::getCreateTime, LocalDateTime.now().minusDays(30));
lqw.eq(AppMessage::getStatus, "1"); // 已发送
List<AppMessage> expiredMessages = baseMapper.selectList(lqw);
if (!expiredMessages.isEmpty()) {
List<Long> ids = expiredMessages.stream().map(AppMessage::getMessageId).toList();
baseMapper.deleteBatchIds(ids);
log.info("清理过期消息 {} 条", ids.size());
return ids.size();
}
return 0;
}
}
@@ -0,0 +1,296 @@
package org.dromara.member.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.member.domain.AppOperLog;
import org.dromara.member.domain.bo.AppOperLogBo;
import org.dromara.member.domain.vo.AppOperLogVo;
import org.dromara.member.mapper.AppOperLogMapper;
import org.dromara.member.service.AppOperLogService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* App端操作日志Service业务层处理
*
* @author pengles
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class AppOperLogServiceImpl extends ServiceImpl<AppOperLogMapper, AppOperLog> implements AppOperLogService {
private final AppOperLogMapper baseMapper;
/**
* 查询App端操作日志
*/
@Override
public AppOperLogVo queryById(Long operId) {
return baseMapper.selectVoById(operId);
}
/**
* 查询App端操作日志列表
*/
@Override
public TableDataInfo<AppOperLogVo> queryPageList(AppOperLogBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<AppOperLog> lqw = buildQueryWrapper(bo);
Page<AppOperLogVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询App端操作日志列表
*/
@Override
public List<AppOperLogVo> queryList(AppOperLogBo bo) {
LambdaQueryWrapper<AppOperLog> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<AppOperLog> buildQueryWrapper(AppOperLogBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<AppOperLog> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(bo.getTitle()), AppOperLog::getTitle, bo.getTitle());
lqw.eq(bo.getBusinessType() != null, AppOperLog::getBusinessType, bo.getBusinessType());
lqw.like(StringUtils.isNotBlank(bo.getMethod()), AppOperLog::getMethod, bo.getMethod());
lqw.eq(StringUtils.isNotBlank(bo.getRequestMethod()), AppOperLog::getRequestMethod, bo.getRequestMethod());
lqw.like(StringUtils.isNotBlank(bo.getOperName()), AppOperLog::getOperName, bo.getOperName());
lqw.like(StringUtils.isNotBlank(bo.getOperIp()), AppOperLog::getOperIp, bo.getOperIp());
lqw.eq(bo.getStatus() != null, AppOperLog::getStatus, bo.getStatus());
lqw.between(params.get("beginTime") != null && params.get("endTime") != null,
AppOperLog::getOperTime, params.get("beginTime"), params.get("endTime"));
lqw.orderByDesc(AppOperLog::getOperTime);
return lqw;
}
/**
* 新增App端操作日志
*/
@Override
public Boolean insertByBo(AppOperLogBo bo) {
AppOperLog add = MapstructUtils.convert(bo, AppOperLog.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setOperId(add.getOperId());
}
return flag;
}
/**
* 修改App端操作日志
*/
@Override
public Boolean updateByBo(AppOperLogBo bo) {
AppOperLog update = MapstructUtils.convert(bo, AppOperLog.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(AppOperLog entity) {
// TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除App端操作日志
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 记录操作日志
*/
@Override
public void recordOperLog(String title, Integer businessType, String method, String requestMethod,
String operName, String operUrl, String operIp, String operLocation,
String operParam, String jsonResult, Integer status, String errorMsg, Long costTime) {
AppOperLog operLog = new AppOperLog();
operLog.setTitle(title);
operLog.setBusinessType(businessType);
operLog.setMethod(method);
operLog.setRequestMethod(requestMethod);
operLog.setOperName(operName);
operLog.setOperUrl(operUrl);
operLog.setOperIp(operIp);
operLog.setOperLocation(operLocation);
operLog.setOperParam(operParam);
operLog.setJsonResult(jsonResult);
operLog.setStatus(status);
operLog.setErrorMsg(errorMsg);
operLog.setOperTime(LocalDateTime.now());
operLog.setCostTime(costTime);
try {
baseMapper.insert(operLog);
} catch (Exception e) {
log.error("记录操作日志失败: {}", e.getMessage(), e);
}
}
/**
* 根据操作人查询操作日志
*/
@Override
public List<AppOperLogVo> queryByOperName(String operName) {
LambdaQueryWrapper<AppOperLog> lqw = Wrappers.lambdaQuery();
lqw.eq(StringUtils.isNotBlank(operName), AppOperLog::getOperName, operName);
lqw.orderByDesc(AppOperLog::getOperTime);
return baseMapper.selectVoList(lqw);
}
/**
* 清空操作日志
*/
@Override
public void cleanOperLog() {
baseMapper.delete(Wrappers.lambdaQuery());
}
/**
* 根据时间范围删除操作日志
*/
@Override
public void deleteOperLogByTime(LocalDateTime beginTime, LocalDateTime endTime) {
LambdaQueryWrapper<AppOperLog> lqw = Wrappers.lambdaQuery();
if (beginTime != null) {
lqw.ge(AppOperLog::getOperTime, beginTime);
}
if (endTime != null) {
lqw.le(AppOperLog::getOperTime, endTime);
}
baseMapper.delete(lqw);
}
/**
* 获取操作统计信息
*/
@Override
public Long getOperLogCount(String operName, LocalDateTime beginTime, LocalDateTime endTime) {
LambdaQueryWrapper<AppOperLog> lqw = Wrappers.lambdaQuery();
lqw.eq(StringUtils.isNotBlank(operName), AppOperLog::getOperName, operName);
if (beginTime != null) {
lqw.ge(AppOperLog::getOperTime, beginTime);
}
if (endTime != null) {
lqw.le(AppOperLog::getOperTime, endTime);
}
return baseMapper.selectCount(lqw);
}
/**
* 获取用户行为分析数据
*/
@Override
public List<AppOperLogVo> getUserBehaviorAnalysis(String operName, Integer days) {
LambdaQueryWrapper<AppOperLog> lqw = Wrappers.lambdaQuery();
lqw.eq(StringUtils.isNotBlank(operName), AppOperLog::getOperName, operName);
if (days != null && days > 0) {
LocalDateTime startTime = LocalDateTime.now().minusDays(days);
lqw.ge(AppOperLog::getOperTime, startTime);
}
lqw.orderByDesc(AppOperLog::getOperTime);
return baseMapper.selectVoList(lqw);
}
/**
* 获取热门操作统计
*/
@Override
public List<AppOperLogVo> getPopularOperations(Integer days, Integer limit) {
LambdaQueryWrapper<AppOperLog> lqw = Wrappers.lambdaQuery();
if (days != null && days > 0) {
LocalDateTime startTime = LocalDateTime.now().minusDays(days);
lqw.ge(AppOperLog::getOperTime, startTime);
}
lqw.eq(AppOperLog::getStatus, 0); // 只统计成功的操作
lqw.orderByDesc(AppOperLog::getOperTime);
List<AppOperLogVo> allLogs = baseMapper.selectVoList(lqw);
// 按操作标题分组统计
Map<String, Long> operationCount = allLogs.stream()
.collect(Collectors.groupingBy(AppOperLogVo::getTitle, Collectors.counting()));
// 返回前N个热门操作
return allLogs.stream()
.collect(Collectors.groupingBy(AppOperLogVo::getTitle))
.entrySet().stream()
.sorted((e1, e2) -> Long.compare(e2.getValue().size(), e1.getValue().size()))
.limit(limit != null ? limit : 10)
.flatMap(entry -> entry.getValue().stream().limit(1))
.collect(Collectors.toList());
}
/**
* 获取错误操作统计
*/
@Override
public List<AppOperLogVo> getErrorOperations(Integer days) {
LambdaQueryWrapper<AppOperLog> lqw = Wrappers.lambdaQuery();
if (days != null && days > 0) {
LocalDateTime startTime = LocalDateTime.now().minusDays(days);
lqw.ge(AppOperLog::getOperTime, startTime);
}
lqw.eq(AppOperLog::getStatus, 1); // 只查询异常操作
lqw.orderByDesc(AppOperLog::getOperTime);
return baseMapper.selectVoList(lqw);
}
/**
* 批量记录操作日志
*/
@Override
public void batchInsertOperLog(List<AppOperLogBo> operLogs) {
if (operLogs == null || operLogs.isEmpty()) {
return;
}
List<AppOperLog> logs = operLogs.stream()
.map(bo -> MapstructUtils.convert(bo, AppOperLog.class))
.collect(Collectors.toList());
try {
this.saveBatch(logs);
} catch (Exception e) {
log.error("批量记录操作日志失败: {}", e.getMessage(), e);
}
}
/**
* 异步记录操作日志
*/
@Async
@Override
public void asyncRecordOperLog(AppOperLogBo bo) {
try {
insertByBo(bo);
} catch (Exception e) {
log.error("异步记录操作日志失败: {}", e.getMessage(), e);
}
}
}
@@ -0,0 +1,237 @@
package org.dromara.member.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.member.domain.AppRole;
import org.dromara.member.domain.AppUserRole;
import org.dromara.member.domain.bo.AppRoleBo;
import org.dromara.member.domain.vo.AppRoleVo;
import org.dromara.member.mapper.AppRoleMapper;
import org.dromara.member.mapper.AppUserRoleMapper;
import org.dromara.member.service.AppRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* App端角色信息Service业务层处理
*
* @author pengles
*/
@Service
public class AppRoleServiceImpl extends ServiceImpl<AppRoleMapper, AppRole> implements AppRoleService {
@Autowired
private AppUserRoleMapper userRoleMapper;
/**
* 查询App端角色信息
*/
@Override
public AppRoleVo queryById(Long roleId) {
return baseMapper.selectVoById(roleId);
}
/**
* 查询App端角色信息列表
*/
@Override
public TableDataInfo<AppRoleVo> queryPageList(AppRoleBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<AppRole> lqw = buildQueryWrapper(bo);
Page<AppRoleVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询App端角色信息列表
*/
@Override
public List<AppRoleVo> queryList(AppRoleBo bo) {
LambdaQueryWrapper<AppRole> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<AppRole> buildQueryWrapper(AppRoleBo bo) {
LambdaQueryWrapper<AppRole> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(bo.getRoleName()), AppRole::getRoleName, bo.getRoleName());
lqw.eq(StringUtils.isNotBlank(bo.getRoleKey()), AppRole::getRoleKey, bo.getRoleKey());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), AppRole::getStatus, bo.getStatus());
lqw.orderByAsc(AppRole::getRoleSort);
return lqw;
}
/**
* 新增App端角色信息
*/
@Override
public Boolean insertByBo(AppRoleBo bo) {
AppRole add = BeanUtil.toBean(bo, AppRole.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setRoleId(add.getRoleId());
}
return flag;
}
/**
* 修改App端角色信息
*/
@Override
public Boolean updateByBo(AppRoleBo bo) {
AppRole update = BeanUtil.toBean(bo, AppRole.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(AppRole entity) {
// TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除App端角色信息
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// 校验角色是否存在用户关联
for (Long id : ids) {
long count = countUserRoleByRoleId(id);
if (count > 0) {
AppRoleVo role = queryById(id);
throw new RuntimeException(String.format("角色%s已分配用户,不能删除", role.getRoleName()));
}
}
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 根据用户ID查询角色
*/
@Override
public List<AppRoleVo> selectRolesByUserId(Long userId) {
return baseMapper.selectRolesByUserId(userId);
}
/**
* 根据用户ID获取角色权限
*/
@Override
public Set<String> selectRolePermissionByUserId(Long userId) {
Set<String> perms = baseMapper.selectRolePermissionByUserId(userId);
return perms;
}
/**
* 查询所有角色
*/
@Override
public List<AppRoleVo> selectRoleAll() {
return baseMapper.selectRoleAll();
}
/**
* 根据用户ID获取角色选择框列表
*/
@Override
public List<Long> selectRoleListByUserId(Long userId) {
return baseMapper.selectRoleListByUserId(userId);
}
/**
* 校验角色名称是否唯一
*/
@Override
public boolean checkRoleNameUnique(String roleName) {
return baseMapper.checkRoleNameUnique(roleName) == 0;
}
/**
* 校验角色权限是否唯一
*/
@Override
public boolean checkRoleKeyUnique(String roleKey) {
return baseMapper.checkRoleKeyUnique(roleKey) == 0;
}
/**
* 校验角色是否允许操作
*/
@Override
public boolean checkRoleAllowed(Long roleId) {
return roleId != null && roleId != 1L;
}
/**
* 校验角色是否有数据权限
*/
@Override
public boolean checkRoleDataScope(Long roleId) {
return roleId != null && roleId != 1L;
}
/**
* 通过角色ID查询角色使用数量
*/
@Override
public long countUserRoleByRoleId(Long roleId) {
return userRoleMapper.countUserRoleByRoleId(roleId);
}
/**
* 取消授权用户角色
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteAuthUser(Long userId, Long roleId) {
LambdaQueryWrapper<AppUserRole> wrapper = Wrappers.lambdaQuery();
wrapper.eq(AppUserRole::getUserId, userId)
.eq(AppUserRole::getRoleId, roleId);
return userRoleMapper.delete(wrapper) > 0;
}
/**
* 批量取消授权用户角色
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteAuthUsers(Long roleId, Long[] userIds) {
LambdaQueryWrapper<AppUserRole> wrapper = Wrappers.lambdaQuery();
wrapper.eq(AppUserRole::getRoleId, roleId)
.in(AppUserRole::getUserId, Arrays.asList(userIds));
return userRoleMapper.delete(wrapper) > 0;
}
/**
* 批量选择授权用户角色
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean insertAuthUsers(Long roleId, Long[] userIds) {
// 新增用户与角色管理
List<AppUserRole> list = Arrays.stream(userIds).map(userId -> {
AppUserRole ur = new AppUserRole();
ur.setUserId(userId);
ur.setRoleId(roleId);
return ur;
}).collect(Collectors.toList());
return userRoleMapper.batchUserRole(list) > 0;
}
}
@@ -0,0 +1,273 @@
package org.dromara.member.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.member.domain.AppSocialAuth;
import org.dromara.member.domain.vo.AppSocialAuthVo;
import org.dromara.member.domain.vo.AppUserVo;
import org.dromara.member.mapper.AppSocialAuthMapper;
import org.dromara.member.service.AppSocialAuthService;
import org.dromara.member.service.AppUserService;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
/**
* App端第三方登录授权Service业务层处理
*
* @author pengles
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class AppSocialAuthServiceImpl extends ServiceImpl<AppSocialAuthMapper, AppSocialAuth> implements AppSocialAuthService {
private final AppSocialAuthMapper baseMapper;
private final AppUserService appUserService;
/**
* 查询App端第三方登录授权
*/
@Override
public AppSocialAuthVo queryById(Long id) {
return baseMapper.selectVoById(id);
}
/**
* 根据用户ID查询社交认证列表
*/
@Override
public List<AppSocialAuthVo> queryByUserId(Long userId) {
LambdaQueryWrapper<AppSocialAuth> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AppSocialAuth::getUserId, userId)
.eq(AppSocialAuth::getDelFlag, "0")
.orderByDesc(AppSocialAuth::getCreateTime);
return baseMapper.selectVoList(wrapper);
}
/**
* 根据平台和openId查询社交认证
*/
@Override
public AppSocialAuth queryByPlatformAndOpenId(String platform, String openId) {
if (StringUtils.isBlank(platform) || StringUtils.isBlank(openId)) {
return null;
}
LambdaQueryWrapper<AppSocialAuth> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AppSocialAuth::getPlatform, platform)
.eq(AppSocialAuth::getOpenId, openId)
.eq(AppSocialAuth::getDelFlag, "0")
.last("LIMIT 1");
return baseMapper.selectOne(wrapper);
}
/**
* 根据平台和unionId查询社交认证
*/
@Override
public AppSocialAuth queryByPlatformAndUnionId(String platform, String unionId) {
if (StringUtils.isBlank(platform) || StringUtils.isBlank(unionId)) {
return null;
}
LambdaQueryWrapper<AppSocialAuth> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AppSocialAuth::getPlatform, platform)
.eq(AppSocialAuth::getUnionId, unionId)
.eq(AppSocialAuth::getDelFlag, "0")
.last("LIMIT 1");
return baseMapper.selectOne(wrapper);
}
/**
* 新增App端第三方登录授权
*/
@Override
public Boolean insertAppSocialAuth(AppSocialAuth appSocialAuth) {
appSocialAuth.setDelFlag("0");
appSocialAuth.setCreateTime(new Date());
return baseMapper.insert(appSocialAuth) > 0;
}
/**
* 修改App端第三方登录授权
*/
@Override
public Boolean updateAppSocialAuth(AppSocialAuth appSocialAuth) {
appSocialAuth.setUpdateTime(new Date());
return baseMapper.updateById(appSocialAuth) > 0;
}
/**
* 删除App端第三方登录授权
*/
@Override
public Boolean deleteAppSocialAuthById(Long id) {
AppSocialAuth appSocialAuth = new AppSocialAuth();
appSocialAuth.setId(id);
appSocialAuth.setDelFlag("2");
appSocialAuth.setUpdateTime(new Date());
return baseMapper.updateById(appSocialAuth) > 0;
}
/**
* 根据用户ID删除社交认证
*/
@Override
public Boolean deleteByUserId(Long userId) {
LambdaQueryWrapper<AppSocialAuth> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AppSocialAuth::getUserId, userId);
List<AppSocialAuth> list = baseMapper.selectList(wrapper);
if (list.isEmpty()) {
return true;
}
AppSocialAuth updateEntity = new AppSocialAuth();
updateEntity.setDelFlag("2");
updateEntity.setUpdateTime(new Date());
return baseMapper.update(updateEntity, wrapper) > 0;
}
/**
* 更新访问令牌
*/
@Override
public Boolean updateAccessToken(Long id, String accessToken, Integer expireIn, String refreshToken) {
AppSocialAuth appSocialAuth = new AppSocialAuth();
appSocialAuth.setId(id);
appSocialAuth.setAccessToken(accessToken);
appSocialAuth.setExpireIn(expireIn);
appSocialAuth.setRefreshToken(refreshToken);
appSocialAuth.setUpdateTime(new Date());
return baseMapper.updateById(appSocialAuth) > 0;
}
@Override
public Boolean deleteByUserIdAndPlatform(Long userId, String platform) {
LambdaQueryWrapper<AppSocialAuth> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AppSocialAuth::getUserId, userId)
.eq(AppSocialAuth::getPlatform, platform)
.eq(AppSocialAuth::getDelFlag, "0");
return baseMapper.delete(wrapper) > 0;
}
@Override
public AppUserVo selectUserByPlatformAndOpenId(String platform, String openId) {
if (StringUtils.isBlank(platform) || StringUtils.isBlank(openId)) {
return null;
}
// 根据平台和openId查询社交认证记录
AppSocialAuth socialAuth = queryByPlatformAndOpenId(platform, openId);
if (socialAuth == null || socialAuth.getUserId() == null) {
return null;
}
// 根据userId查询用户信息
return appUserService.queryById(socialAuth.getUserId());
}
@Override
public AppUserVo selectUserByPlatformAndUnionId(String platform, String unionId) {
if (StringUtils.isBlank(platform) || StringUtils.isBlank(unionId)) {
return null;
}
AppSocialAuth socialAuth = queryByPlatformAndUnionId(platform, unionId);
if (socialAuth == null || socialAuth.getUserId() == null) {
return null;
}
return appUserService.queryById(socialAuth.getUserId());
}
@Override
public void socialRegister(me.zhyd.oauth.model.AuthUser authUserData) {
if (authUserData == null) {
throw new org.dromara.common.core.exception.ServiceException("第三方用户信息不能为空");
}
String platform = authUserData.getSource();
String openId = authUserData.getUuid();
Long userId = org.dromara.common.satoken.utils.LoginHelper.getUserId();
if (StringUtils.isBlank(platform) || StringUtils.isBlank(openId)) {
throw new org.dromara.common.core.exception.ServiceException("第三方平台信息不完整");
}
log.info("开始绑定第三方账号,用户ID: {}, 平台: {}, openId: {}", userId, platform, openId);
// 检查是否已经绑定过该第三方账号
AppSocialAuth existingSocialAuth = queryByPlatformAndOpenId(platform, openId);
if (existingSocialAuth != null) {
if (!userId.equals(existingSocialAuth.getUserId())) {
log.warn("第三方账号已被其他用户绑定,平台: {}, openId: {}, 绑定用户: {}", platform, openId, existingSocialAuth.getUserId());
throw new org.dromara.common.core.exception.ServiceException("此第三方账号已经被其他用户绑定!");
} else {
log.info("第三方账号已绑定当前用户,无需重复绑定,平台: {}, openId: {}", platform, openId);
return;
}
}
// 检查当前用户是否已经绑定过该平台的账号
List<AppSocialAuthVo> userSocialAuths = queryByUserId(userId);
boolean alreadyBound = userSocialAuths.stream()
.anyMatch(auth -> platform.equals(auth.getPlatform()));
if (alreadyBound) {
// 如果已经绑定过该平台,则更新绑定信息
log.info("用户已绑定该平台账号,更新绑定信息,用户ID: {}, 平台: {}", userId, platform);
AppSocialAuth updateAuth = userSocialAuths.stream()
.filter(auth -> platform.equals(auth.getPlatform()))
.map(vo -> {
AppSocialAuth auth = new AppSocialAuth();
auth.setId(vo.getId());
auth.setUserId(userId);
auth.setPlatform(platform);
auth.setPlatformUid(authUserData.getUuid());
auth.setOpenId(openId);
auth.setUnionId(authUserData.getToken() != null ? authUserData.getToken().getUnionId() : null);
auth.setAccessToken(authUserData.getToken() != null ? authUserData.getToken().getAccessToken() : null);
auth.setExpireIn(authUserData.getToken() != null ? authUserData.getToken().getExpireIn() : null);
auth.setRefreshToken(authUserData.getToken() != null ? authUserData.getToken().getRefreshToken() : null);
auth.setScope(authUserData.getToken() != null ? authUserData.getToken().getScope() : null);
return auth;
})
.findFirst()
.orElse(null);
if (updateAuth != null) {
boolean success = updateAppSocialAuth(updateAuth);
if (success) {
log.info("更新第三方账号绑定成功,用户ID: {}, 平台: {}", userId, platform);
} else {
log.error("更新第三方账号绑定失败,用户ID: {}, 平台: {}", userId, platform);
throw new org.dromara.common.core.exception.ServiceException("更新绑定信息失败");
}
}
} else {
// 新增绑定记录
log.info("新增第三方账号绑定,用户ID: {}, 平台: {}", userId, platform);
AppSocialAuth newAuth = new AppSocialAuth();
newAuth.setUserId(userId);
newAuth.setPlatform(platform);
newAuth.setPlatformUid(authUserData.getUuid());
newAuth.setOpenId(openId);
newAuth.setUnionId(authUserData.getToken() != null ? authUserData.getToken().getUnionId() : null);
newAuth.setAccessToken(authUserData.getToken() != null ? authUserData.getToken().getAccessToken() : null);
newAuth.setExpireIn(authUserData.getToken() != null ? authUserData.getToken().getExpireIn() : null);
newAuth.setRefreshToken(authUserData.getToken() != null ? authUserData.getToken().getRefreshToken() : null);
newAuth.setScope(authUserData.getToken() != null ? authUserData.getToken().getScope() : null);
boolean success = insertAppSocialAuth(newAuth);
if (success) {
log.info("新增第三方账号绑定成功,用户ID: {}, 平台: {}", userId, platform);
} else {
log.error("新增第三方账号绑定失败,用户ID: {}, 平台: {}", userId, platform);
throw new org.dromara.common.core.exception.ServiceException("绑定第三方账号失败");
}
}
}
}
@@ -0,0 +1,239 @@
package org.dromara.member.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.member.domain.AppUser;
import org.dromara.member.domain.AppUserRole;
import org.dromara.member.domain.bo.AppUserBo;
import org.dromara.member.domain.vo.AppUserVo;
import org.dromara.member.mapper.AppUserMapper;
import org.dromara.member.mapper.AppUserRoleMapper;
import org.dromara.member.service.AppUserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
/**
* App端用户信息Service业务层处理
*
* @author pengles
*/
@Service
@RequiredArgsConstructor
public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implements AppUserService {
private final AppUserRoleMapper appUserRoleMapper;
/**
* 查询App端用户信息
*/
@Override
public AppUserVo queryById(Long userId) {
return baseMapper.selectVoById(userId);
}
/**
* 查询App端用户信息列表
*/
@Override
public TableDataInfo<AppUserVo> queryPageList(AppUserBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<AppUser> lqw = buildQueryWrapper(bo);
Page<AppUserVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询App端用户信息列表
*/
@Override
public List<AppUserVo> queryList(AppUserBo bo) {
LambdaQueryWrapper<AppUser> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<AppUser> buildQueryWrapper(AppUserBo bo) {
LambdaQueryWrapper<AppUser> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(bo.getUserName()), AppUser::getUserName, bo.getUserName());
lqw.like(StringUtils.isNotBlank(bo.getNickName()), AppUser::getNickName, bo.getNickName());
lqw.eq(StringUtils.isNotBlank(bo.getPhone()), AppUser::getPhone, bo.getPhone());
lqw.eq(StringUtils.isNotBlank(bo.getEmail()), AppUser::getEmail, bo.getEmail());
lqw.eq(StringUtils.isNotBlank(bo.getSex()), AppUser::getSex, bo.getSex());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), AppUser::getStatus, bo.getStatus());
return lqw;
}
/**
* 新增App端用户信息
*/
@Override
public Boolean insertByBo(AppUserBo bo) {
AppUser add = BeanUtil.toBean(bo, AppUser.class);
validEntityBeforeSave(add);
// 加密密码
if (StringUtils.isNotBlank(add.getPassword())) {
add.setPassword(BCrypt.hashpw(add.getPassword()));
}
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setUserId(add.getUserId());
}
return flag;
}
/**
* 修改App端用户信息
*/
@Override
public Boolean updateByBo(AppUserBo bo) {
AppUser update = BeanUtil.toBean(bo, AppUser.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(AppUser entity) {
// TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除App端用户信息
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 根据用户账号查询用户信息
*/
@Override
public AppUserVo selectUserByUserName(String userName) {
return baseMapper.selectUserByUserName(userName);
}
/**
* 根据手机号查询用户信息
*/
@Override
public AppUserVo selectUserByPhone(String phone) {
return baseMapper.selectUserByPhone(phone);
}
/**
* 根据邮箱查询用户信息
*/
@Override
public AppUserVo selectUserByEmail(String email) {
return baseMapper.selectUserByEmail(email);
}
/**
* 校验用户账号是否唯一
*/
@Override
public boolean checkUserNameUnique(String userName) {
return baseMapper.checkUserNameUnique(userName) == 0;
}
/**
* 校验手机号码是否唯一
*/
@Override
public boolean checkPhoneUnique(String phone) {
return baseMapper.checkPhoneUnique(phone) == 0;
}
/**
* 校验email是否唯一
*/
@Override
public boolean checkEmailUnique(String email) {
return baseMapper.checkEmailUnique(email) == 0;
}
/**
* 用户注册
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean register(AppUserBo bo) {
// 校验账号唯一性
if (!checkUserNameUnique(bo.getUserName())) {
throw new RuntimeException("用户账号已存在");
}
// 校验手机号唯一性
if (StringUtils.isNotBlank(bo.getPhone()) && !checkPhoneUnique(bo.getPhone())) {
throw new RuntimeException("手机号已存在");
}
// 校验邮箱唯一性
if (StringUtils.isNotBlank(bo.getEmail()) && !checkEmailUnique(bo.getEmail())) {
throw new RuntimeException("邮箱已存在");
}
boolean result = insertByBo(bo);
if (result) {
// 分配默认角色
AppUserRole userRole = new AppUserRole();
userRole.setUserId(bo.getUserId());
userRole.setRoleId(1L); // 默认角色ID为1 (App普通用户)
appUserRoleMapper.insert(userRole);
}
return result;
}
/**
* 修改用户密码
*/
@Override
public Boolean updatePassword(Long userId, String oldPassword, String newPassword) {
AppUser user = baseMapper.selectById(userId);
if (ObjectUtil.isNull(user)) {
throw new RuntimeException("用户不存在");
}
if (!BCrypt.checkpw(oldPassword, user.getPassword())) {
throw new RuntimeException("旧密码错误");
}
user.setPassword(BCrypt.hashpw(newPassword));
return baseMapper.updateById(user) > 0;
}
/**
* 重置用户密码
*/
@Override
public Boolean resetPassword(Long userId, String newPassword) {
AppUser user = new AppUser();
user.setUserId(userId);
user.setPassword(BCrypt.hashpw(newPassword));
return baseMapper.updateById(user) > 0;
}
/**
* 更新用户状态
*/
@Override
public Boolean updateUserStatus(Long userId, String status) {
return baseMapper.update(null,
new LambdaUpdateWrapper<AppUser>()
.set(AppUser::getStatus, status)
.eq(AppUser::getUserId, userId)) > 0;
}
}
@@ -0,0 +1,297 @@
package org.dromara.member.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.member.domain.AppVersion;
import org.dromara.member.domain.bo.AppVersionBo;
import org.dromara.member.domain.vo.AppVersionVo;
import org.dromara.member.exception.VersionException;
import org.dromara.member.mapper.AppVersionMapper;
import org.dromara.member.service.AppVersionService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
/**
* App版本信息Service业务层处理
*
* @author pengles
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class AppVersionServiceImpl extends ServiceImpl<AppVersionMapper, AppVersion> implements AppVersionService {
private final AppVersionMapper baseMapper;
/**
* 查询App版本信息
*/
@Override
public AppVersionVo queryById(Long versionId) {
return baseMapper.selectVoById(versionId);
}
/**
* 查询App版本信息列表
*/
@Override
public TableDataInfo<AppVersionVo> queryPageList(AppVersionBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<AppVersion> lqw = buildQueryWrapper(bo);
Page<AppVersionVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询App版本信息列表
*/
@Override
public List<AppVersionVo> queryList(AppVersionBo bo) {
LambdaQueryWrapper<AppVersion> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<AppVersion> buildQueryWrapper(AppVersionBo bo) {
LambdaQueryWrapper<AppVersion> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getVersionCode() != null, AppVersion::getVersionCode, bo.getVersionCode());
lqw.like(StringUtils.isNotBlank(bo.getVersionName()), AppVersion::getVersionName, bo.getVersionName());
lqw.eq(StringUtils.isNotBlank(bo.getPlatform()), AppVersion::getPlatform, bo.getPlatform());
lqw.eq(StringUtils.isNotBlank(bo.getIsForce()), AppVersion::getIsForce, bo.getIsForce());
lqw.orderByDesc(AppVersion::getVersionCode);
return lqw;
}
/**
* 新增App版本信息
*/
@Override
public Boolean insertByBo(AppVersionBo bo) {
AppVersion add = BeanUtil.toBean(bo, AppVersion.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setVersionId(add.getVersionId());
}
return flag;
}
/**
* 修改App版本信息
*/
@Override
public Boolean updateByBo(AppVersionBo bo) {
AppVersion update = BeanUtil.toBean(bo, AppVersion.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(AppVersion entity) {
// 校验版本号唯一性
if (entity.getVersionCode() != null && StringUtils.isNotBlank(entity.getPlatform())) {
LambdaQueryWrapper<AppVersion> lqw = Wrappers.lambdaQuery();
lqw.eq(AppVersion::getVersionCode, entity.getVersionCode());
lqw.eq(AppVersion::getPlatform, entity.getPlatform());
if (entity.getVersionId() != null) {
lqw.ne(AppVersion::getVersionId, entity.getVersionId());
}
long count = baseMapper.selectCount(lqw);
if (count > 0) {
throw new RuntimeException("该平台下版本号已存在");
}
}
}
/**
* 批量删除App版本信息
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 检查版本更新
*/
@Override
public AppVersionVo checkUpdate(String platform, Integer currentVersionCode) {
if (StringUtils.isBlank(platform)) {
throw VersionException.unsupportedPlatform(platform);
}
if (currentVersionCode == null || currentVersionCode <= 0) {
throw VersionException.invalidVersionCode(currentVersionCode);
}
LambdaQueryWrapper<AppVersion> lqw = Wrappers.lambdaQuery();
lqw.eq(AppVersion::getPlatform, platform);
lqw.gt(AppVersion::getVersionCode, currentVersionCode);
lqw.orderByDesc(AppVersion::getVersionCode);
lqw.last("LIMIT 1");
return baseMapper.selectVoOne(lqw);
}
/**
* 获取指定平台的最新版本
*/
@Override
public AppVersionVo getLatestVersion(String platform) {
if (StringUtils.isBlank(platform)) {
throw VersionException.unsupportedPlatform(platform);
}
LambdaQueryWrapper<AppVersion> lqw = Wrappers.lambdaQuery();
lqw.eq(AppVersion::getPlatform, platform);
lqw.orderByDesc(AppVersion::getVersionCode);
lqw.last("LIMIT 1");
AppVersionVo result = baseMapper.selectVoOne(lqw);
if (result == null) {
throw VersionException.versionNotFound("平台 " + platform + " 暂无可用版本");
}
return result;
}
/**
* 检查版本号是否唯一
*/
@Override
public Boolean checkVersionCodeUnique(AppVersionBo bo) {
if (bo.getVersionCode() == null || bo.getVersionCode() <= 0) {
throw VersionException.invalidVersionCode(bo.getVersionCode());
}
if (StringUtils.isBlank(bo.getPlatform())) {
throw VersionException.unsupportedPlatform(bo.getPlatform());
}
LambdaQueryWrapper<AppVersion> lqw = Wrappers.lambdaQuery();
lqw.eq(AppVersion::getVersionCode, bo.getVersionCode());
lqw.eq(AppVersion::getPlatform, bo.getPlatform());
if (bo.getVersionId() != null) {
lqw.ne(AppVersion::getVersionId, bo.getVersionId());
}
boolean isUnique = baseMapper.selectCount(lqw) == 0;
if (!isUnique) {
throw VersionException.versionCodeDuplicate(bo.getVersionCode());
}
return true;
}
/**
* 获取版本历史列表
*/
@Override
public List<AppVersionVo> getVersionHistory(String platform, Integer limit) {
if (StringUtils.isBlank(platform)) {
return List.of();
}
if (limit == null || limit <= 0) {
limit = 10; // 默认返回10个版本
}
LambdaQueryWrapper<AppVersion> lqw = Wrappers.lambdaQuery();
lqw.eq(AppVersion::getPlatform, platform);
lqw.orderByDesc(AppVersion::getVersionCode);
lqw.last("LIMIT " + limit);
return baseMapper.selectVoList(lqw);
}
/**
* 检查是否需要强制更新
*/
@Override
public Boolean checkForceUpdate(String platform, Integer currentVersionCode) {
if (StringUtils.isBlank(platform)) {
throw VersionException.unsupportedPlatform(platform);
}
if (currentVersionCode == null || currentVersionCode <= 0) {
throw VersionException.invalidVersionCode(currentVersionCode);
}
LambdaQueryWrapper<AppVersion> lqw = Wrappers.lambdaQuery();
lqw.eq(AppVersion::getPlatform, platform);
lqw.gt(AppVersion::getVersionCode, currentVersionCode);
lqw.eq(AppVersion::getIsForce, "1");
return baseMapper.selectCount(lqw) > 0;
}
/**
* 获取版本更新信息
*/
@Override
public AppVersionVo getUpdateInfo(String platform, Integer currentVersionCode) {
AppVersionVo latestVersion = checkUpdate(platform, currentVersionCode);
if (latestVersion == null) {
return null;
}
// 检查是否有强制更新版本
Boolean forceUpdate = checkForceUpdate(platform, currentVersionCode);
if (forceUpdate) {
// 如果有强制更新,返回最新的强制更新版本
LambdaQueryWrapper<AppVersion> lqw = Wrappers.lambdaQuery();
lqw.eq(AppVersion::getPlatform, platform);
lqw.gt(AppVersion::getVersionCode, currentVersionCode);
lqw.eq(AppVersion::getIsForce, "1");
lqw.orderByDesc(AppVersion::getVersionCode);
lqw.last("LIMIT 1");
AppVersionVo forceVersion = baseMapper.selectVoOne(lqw);
return forceVersion != null ? forceVersion : latestVersion;
}
return latestVersion;
}
/**
* 批量发布版本
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean batchPublish(List<Long> versionIds) {
if (ObjectUtil.isEmpty(versionIds)) {
return false;
}
// 这里可以添加发布逻辑,比如更新状态等
log.info("批量发布版本: {}", versionIds);
return true;
}
/**
* 撤回版本
*/
@Override
public Boolean withdrawVersion(Long versionId) {
if (versionId == null) {
return false;
}
// 这里可以添加撤回逻辑,比如更新状态等
log.info("撤回版本: {}", versionId);
return true;
}
}
@@ -0,0 +1,117 @@
package org.dromara.member.utils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
* JWT工具类
*
* @author pengles
*/
public class JwtUtil {
private final String secret;
public JwtUtil(String secret) {
this.secret = secret;
}
public String generateToken(String subject, long ttlSeconds, String jti) {
long now = Instant.now().getEpochSecond();
long exp = now + ttlSeconds;
Map<String, Object> header = new LinkedHashMap<>();
header.put("alg", "HS256");
header.put("typ", "JWT");
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("iss", "ruoyi-app");
payload.put("sub", subject);
payload.put("iat", now);
payload.put("exp", exp);
payload.put("jti", jti == null ? UUID.randomUUID().toString() : jti);
String h = base64Url(Json.write(header));
String p = base64Url(Json.write(payload));
String signing = h + "." + p;
String sig = base64Url(hmacSha256(signing, secret));
return signing + "." + sig;
}
public boolean verify(String token) {
try {
String[] parts = token.split("\\.");
if (parts.length != 3) return false;
String signing = parts[0] + "." + parts[1];
String calc = base64Url(hmacSha256(signing, secret));
return slowEquals(calc, parts[2]);
} catch (Exception e) {
return false;
}
}
public Map<String, Object> parsePayload(String token) {
String[] parts = token.split("\\.");
if (parts.length != 3) return Map.of();
String json = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8);
return Json.read(json);
}
private static boolean slowEquals(String a, String b) {
if (a == null || b == null) return false;
if (a.length() != b.length()) return false;
int r = 0;
for (int i = 0; i < a.length(); i++) r |= a.charAt(i) ^ b.charAt(i);
return r == 0;
}
private static byte[] hmacSha256(String data, String key) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static String base64Url(String s) {
return Base64.getUrlEncoder().withoutPadding().encodeToString(s.getBytes(StandardCharsets.UTF_8));
}
private static String base64Url(byte[] b) {
return Base64.getUrlEncoder().withoutPadding().encodeToString(b);
}
static class Json {
public static String write(Map<String, Object> map) {
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Object> e : map.entrySet()) {
if (!first) sb.append(",");
sb.append("\"").append(e.getKey()).append("\":");
Object v = e.getValue();
if (v instanceof Number || v instanceof Boolean) {
sb.append(v.toString());
} else {
sb.append("\"").append(String.valueOf(v).replace("\"", "\\\"")).append("\"");
}
first = false;
}
sb.append("}");
return sb.toString();
}
@SuppressWarnings("unchecked")
public static Map<String, Object> read(String json) {
try {
return org.dromara.common.json.utils.JsonUtils.parseObject(json, Map.class);
} catch (Exception e) {
return Map.of();
}
}
}
}
@@ -0,0 +1,162 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.member.mapper.AppDeviceMapper">
<resultMap type="org.dromara.member.domain.AppDevice" id="AppDeviceResult">
<id property="deviceId" column="device_id"/>
<result property="userId" column="user_id"/>
<result property="deviceType" column="device_type"/>
<result property="deviceName" column="device_name"/>
<result property="deviceModel" column="device_model"/>
<result property="deviceBrand" column="device_brand"/>
<result property="osVersion" column="os_version"/>
<result property="appVersion" column="app_version"/>
<result property="pushToken" column="push_token"/>
<result property="isActive" column="is_active"/>
<result property="lastActiveTime" column="last_active_time"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<resultMap type="org.dromara.member.domain.vo.AppDeviceVo" id="AppDeviceVoResult">
<id property="deviceId" column="device_id"/>
<result property="userId" column="user_id"/>
<result property="deviceType" column="device_type"/>
<result property="deviceName" column="device_name"/>
<result property="deviceModel" column="device_model"/>
<result property="deviceBrand" column="device_brand"/>
<result property="osVersion" column="os_version"/>
<result property="appVersion" column="app_version"/>
<result property="pushToken" column="push_token"/>
<result property="isActive" column="is_active"/>
<result property="lastActiveTime" column="last_active_time"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="selectAppDeviceVo">
select device_id,
user_id,
device_type,
device_name,
device_model,
device_brand,
os_version,
app_version,
push_token,
is_active,
last_active_time,
create_time,
update_time
from app_device
</sql>
<!-- 查询设备列表 -->
<select id="selectAppDeviceList" parameterType="AppDevice" resultMap="AppDeviceVoResult">
<include refid="selectAppDeviceVo"/>
<where>
<if test="userId != null">and user_id = #{userId}</if>
<if test="deviceType != null and deviceType != ''">and device_type = #{deviceType}</if>
<if test="deviceName != null and deviceName != ''">and device_name like concat('%', #{deviceName}, '%')</if>
<if test="isActive != null">and is_active = #{isActive}</if>
</where>
order by create_time desc
</select>
<!-- 根据设备ID查询设备信息 -->
<select id="selectAppDeviceByDeviceId" parameterType="Long" resultMap="AppDeviceVoResult">
<include refid="selectAppDeviceVo"/>
where device_id = #{deviceId}
</select>
<!-- 根据用户ID查询设备列表 -->
<select id="selectAppDeviceByUserId" parameterType="Long" resultMap="AppDeviceVoResult">
<include refid="selectAppDeviceVo"/>
where user_id = #{userId} and is_active = '1'
order by last_active_time desc
</select>
<!-- 根据推送令牌查询设备 -->
<select id="selectAppDeviceByPushToken" parameterType="String" resultMap="AppDeviceResult">
<include refid="selectAppDeviceVo"/>
where push_token = #{pushToken} and is_active = '1'
</select>
<!-- 新增设备信息 -->
<insert id="insertAppDevice" parameterType="org.dromara.member.domain.AppDevice" useGeneratedKeys="true"
keyProperty="deviceId">
insert into app_device
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userId != null">user_id,</if>
<if test="deviceType != null and deviceType != ''">device_type,</if>
<if test="deviceName != null and deviceName != ''">device_name,</if>
<if test="deviceModel != null and deviceModel != ''">device_model,</if>
<if test="deviceBrand != null and deviceBrand != ''">device_brand,</if>
<if test="osVersion != null and osVersion != ''">os_version,</if>
<if test="appVersion != null and appVersion != ''">app_version,</if>
<if test="pushToken != null and pushToken != ''">push_token,</if>
<if test="isActive != null">is_active,</if>
<if test="lastActiveTime != null">last_active_time,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userId != null">#{userId},</if>
<if test="deviceType != null and deviceType != ''">#{deviceType},</if>
<if test="deviceName != null and deviceName != ''">#{deviceName},</if>
<if test="deviceModel != null and deviceModel != ''">#{deviceModel},</if>
<if test="deviceBrand != null and deviceBrand != ''">#{deviceBrand},</if>
<if test="osVersion != null and osVersion != ''">#{osVersion},</if>
<if test="appVersion != null and appVersion != ''">#{appVersion},</if>
<if test="pushToken != null and pushToken != ''">#{pushToken},</if>
<if test="isActive != null">#{isActive},</if>
<if test="lastActiveTime != null">#{lastActiveTime},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
</trim>
</insert>
<!-- 修改设备信息 -->
<update id="updateAppDevice" parameterType="org.dromara.member.domain.AppDevice">
update app_device
<trim prefix="SET" suffixOverrides=",">
<if test="deviceType != null and deviceType != ''">device_type = #{deviceType},</if>
<if test="deviceName != null and deviceName != ''">device_name = #{deviceName},</if>
<if test="deviceModel != null and deviceModel != ''">device_model = #{deviceModel},</if>
<if test="deviceBrand != null and deviceBrand != ''">device_brand = #{deviceBrand},</if>
<if test="osVersion != null and osVersion != ''">os_version = #{osVersion},</if>
<if test="appVersion != null and appVersion != ''">app_version = #{appVersion},</if>
<if test="pushToken != null and pushToken != ''">push_token = #{pushToken},</if>
<if test="isActive != null">is_active = #{isActive},</if>
<if test="lastActiveTime != null">last_active_time = #{lastActiveTime},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</trim>
where device_id = #{deviceId}
</update>
<!-- 删除设备信息 -->
<delete id="deleteAppDeviceByDeviceId" parameterType="Long">
delete
from app_device
where device_id = #{deviceId}
</delete>
<!-- 批量删除设备信息 -->
<delete id="deleteAppDeviceByDeviceIds" parameterType="String">
delete from app_device where device_id in
<foreach item="deviceId" collection="array" open="(" separator="," close=")">
#{deviceId}
</foreach>
</delete>
<!-- 更新设备活跃状态 -->
<update id="updateDeviceActiveStatus">
update app_device
set is_active = #{isActive},
last_active_time = #{lastActiveTime}
where device_id = #{deviceId}
</update>
</mapper>
@@ -0,0 +1,166 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.member.mapper.AppFileMapper">
<resultMap type="org.dromara.member.domain.AppFile" id="AppFileResult">
<result property="fileId" column="file_id"/>
<result property="userId" column="user_id"/>
<result property="fileName" column="file_name"/>
<result property="originalName" column="original_name"/>
<result property="filePath" column="file_path"/>
<result property="fileUrl" column="file_url"/>
<result property="fileSize" column="file_size"/>
<result property="fileType" column="file_type"/>
<result property="fileSuffix" column="file_suffix"/>
<result property="fileCategory" column="file_category"/>
<result property="storageService" column="storage_service"/>
<result property="status" column="status"/>
<result property="downloadCount" column="download_count"/>
<result property="isPublic" column="is_public"/>
<result property="fileMd5" column="file_md5"/>
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="selectAppFileVo">
select file_id,
user_id,
file_name,
original_name,
file_path,
file_url,
file_size,
file_type,
file_suffix,
file_category,
storage_service,
status,
download_count,
is_public,
file_md5,
remark,
create_by,
create_time,
update_by,
update_time
from app_file
</sql>
<select id="selectAppFileList" parameterType="org.dromara.member.domain.AppFile" resultMap="AppFileResult">
<include refid="selectAppFileVo"/>
<where>
<if test="userId != null ">and user_id = #{userId}</if>
<if test="fileName != null and fileName != ''">and file_name like concat('%', #{fileName}, '%')</if>
<if test="originalName != null and originalName != ''">and original_name like concat('%', #{originalName},
'%')
</if>
<if test="filePath != null and filePath != ''">and file_path = #{filePath}</if>
<if test="fileUrl != null and fileUrl != ''">and file_url = #{fileUrl}</if>
<if test="fileSize != null ">and file_size = #{fileSize}</if>
<if test="fileType != null and fileType != ''">and file_type = #{fileType}</if>
<if test="fileSuffix != null and fileSuffix != ''">and file_suffix = #{fileSuffix}</if>
<if test="fileCategory != null ">and file_category = #{fileCategory}</if>
<if test="storageService != null and storageService != ''">and storage_service = #{storageService}</if>
<if test="status != null and status != ''">and status = #{status}</if>
<if test="downloadCount != null ">and download_count = #{downloadCount}</if>
<if test="isPublic != null and isPublic != ''">and is_public = #{isPublic}</if>
<if test="fileMd5 != null and fileMd5 != ''">and file_md5 = #{fileMd5}</if>
</where>
order by create_time desc
</select>
<select id="selectAppFileByFileId" parameterType="Long" resultMap="AppFileResult">
<include refid="selectAppFileVo"/>
where file_id = #{fileId}
</select>
<insert id="insertAppFile" parameterType="org.dromara.member.domain.AppFile" useGeneratedKeys="true"
keyProperty="fileId">
insert into app_file
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userId != null">user_id,</if>
<if test="fileName != null and fileName != ''">file_name,</if>
<if test="originalName != null and originalName != ''">original_name,</if>
<if test="filePath != null and filePath != ''">file_path,</if>
<if test="fileUrl != null and fileUrl != ''">file_url,</if>
<if test="fileSize != null">file_size,</if>
<if test="fileType != null and fileType != ''">file_type,</if>
<if test="fileSuffix != null and fileSuffix != ''">file_suffix,</if>
<if test="fileCategory != null">file_category,</if>
<if test="storageService != null and storageService != ''">storage_service,</if>
<if test="status != null and status != ''">status,</if>
<if test="downloadCount != null">download_count,</if>
<if test="isPublic != null and isPublic != ''">is_public,</if>
<if test="fileMd5 != null and fileMd5 != ''">file_md5,</if>
<if test="remark != null">remark,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userId != null">#{userId},</if>
<if test="fileName != null and fileName != ''">#{fileName},</if>
<if test="originalName != null and originalName != ''">#{originalName},</if>
<if test="filePath != null and filePath != ''">#{filePath},</if>
<if test="fileUrl != null and fileUrl != ''">#{fileUrl},</if>
<if test="fileSize != null">#{fileSize},</if>
<if test="fileType != null and fileType != ''">#{fileType},</if>
<if test="fileSuffix != null and fileSuffix != ''">#{fileSuffix},</if>
<if test="fileCategory != null">#{fileCategory},</if>
<if test="storageService != null and storageService != ''">#{storageService},</if>
<if test="status != null and status != ''">#{status},</if>
<if test="downloadCount != null">#{downloadCount},</if>
<if test="isPublic != null and isPublic != ''">#{isPublic},</if>
<if test="fileMd5 != null and fileMd5 != ''">#{fileMd5},</if>
<if test="remark != null">#{remark},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
</trim>
</insert>
<update id="updateAppFile" parameterType="org.dromara.member.domain.AppFile">
update app_file
<trim prefix="SET" suffixOverrides=",">
<if test="userId != null">user_id = #{userId},</if>
<if test="fileName != null and fileName != ''">file_name = #{fileName},</if>
<if test="originalName != null and originalName != ''">original_name = #{originalName},</if>
<if test="filePath != null and filePath != ''">file_path = #{filePath},</if>
<if test="fileUrl != null and fileUrl != ''">file_url = #{fileUrl},</if>
<if test="fileSize != null">file_size = #{fileSize},</if>
<if test="fileType != null and fileType != ''">file_type = #{fileType},</if>
<if test="fileSuffix != null and fileSuffix != ''">file_suffix = #{fileSuffix},</if>
<if test="fileCategory != null">file_category = #{fileCategory},</if>
<if test="storageService != null and storageService != ''">storage_service = #{storageService},</if>
<if test="status != null and status != ''">status = #{status},</if>
<if test="downloadCount != null">download_count = #{downloadCount},</if>
<if test="isPublic != null and isPublic != ''">is_public = #{isPublic},</if>
<if test="fileMd5 != null and fileMd5 != ''">file_md5 = #{fileMd5},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</trim>
where file_id = #{fileId}
</update>
<delete id="deleteAppFileByFileId" parameterType="Long">
delete
from app_file
where file_id = #{fileId}
</delete>
<delete id="deleteAppFileByFileIds" parameterType="String">
delete from app_file where file_id in
<foreach item="fileId" collection="array" open="(" separator="," close=")">
#{fileId}
</foreach>
</delete>
</mapper>
@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.member.mapper.AppLoginLogMapper">
<resultMap type="org.dromara.member.domain.AppLoginLog" id="AppLoginLogResult">
<id property="infoId" column="info_id"/>
<result property="userName" column="user_name"/>
<result property="status" column="status"/>
<result property="ipaddr" column="ipaddr"/>
<result property="loginLocation" column="login_location"/>
<result property="browser" column="browser"/>
<result property="os" column="os"/>
<result property="msg" column="msg"/>
<result property="loginTime" column="login_time"/>
</resultMap>
<resultMap type="org.dromara.member.domain.vo.AppLoginLogVo" id="AppLoginLogVoResult">
<id property="infoId" column="info_id"/>
<result property="userName" column="user_name"/>
<result property="status" column="status"/>
<result property="ipaddr" column="ipaddr"/>
<result property="loginLocation" column="login_location"/>
<result property="browser" column="browser"/>
<result property="os" column="os"/>
<result property="msg" column="msg"/>
<result property="loginTime" column="login_time"/>
</resultMap>
<sql id="selectAppLoginLogVo">
select info_id,
user_name,
status,
ipaddr,
login_location,
browser,
os,
msg,
login_time
from app_login_log
</sql>
<!-- 查询登录日志列表 -->
<select id="selectAppLoginLogList" parameterType="AppLoginLog" resultMap="AppLoginLogVoResult">
<include refid="selectAppLoginLogVo"/>
<where>
<if test="userName != null and userName != ''">and user_name like concat('%', #{userName}, '%')</if>
<if test="status != null and status != ''">and status = #{status}</if>
<if test="ipaddr != null and ipaddr != ''">and ipaddr like concat('%', #{ipaddr}, '%')</if>
<if test="loginLocation != null and loginLocation != ''">and login_location like concat('%',
#{loginLocation}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''">and login_time &gt;= #{params.beginTime}</if>
<if test="params.endTime != null and params.endTime != ''">and login_time &lt;= #{params.endTime}</if>
</where>
order by login_time desc
</select>
<!-- 根据ID查询登录日志 -->
<select id="selectAppLoginLogByInfoId" parameterType="Long" resultMap="AppLoginLogVoResult">
<include refid="selectAppLoginLogVo"/>
where info_id = #{infoId}
</select>
<!-- 根据用户名查询登录日志 -->
<select id="selectAppLoginLogByUserName" parameterType="String" resultMap="AppLoginLogVoResult">
<include refid="selectAppLoginLogVo"/>
where user_name = #{userName}
order by login_time desc
</select>
<!-- 新增登录日志 -->
<insert id="insertAppLoginLog" parameterType="org.dromara.member.domain.AppLoginLog" useGeneratedKeys="true"
keyProperty="infoId">
insert into app_login_log
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userName != null and userName != ''">user_name,</if>
<if test="status != null and status != ''">status,</if>
<if test="ipaddr != null and ipaddr != ''">ipaddr,</if>
<if test="loginLocation != null and loginLocation != ''">login_location,</if>
<if test="browser != null and browser != ''">browser,</if>
<if test="os != null and os != ''">os,</if>
<if test="msg != null and msg != ''">msg,</if>
<if test="loginTime != null">login_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userName != null and userName != ''">#{userName},</if>
<if test="status != null and status != ''">#{status},</if>
<if test="ipaddr != null and ipaddr != ''">#{ipaddr},</if>
<if test="loginLocation != null and loginLocation != ''">#{loginLocation},</if>
<if test="browser != null and browser != ''">#{browser},</if>
<if test="os != null and os != ''">#{os},</if>
<if test="msg != null and msg != ''">#{msg},</if>
<if test="loginTime != null">#{loginTime},</if>
</trim>
</insert>
<!-- 删除登录日志 -->
<delete id="deleteAppLoginLogByInfoId" parameterType="Long">
delete
from app_login_log
where info_id = #{infoId}
</delete>
<!-- 批量删除登录日志 -->
<delete id="deleteAppLoginLogByInfoIds" parameterType="String">
delete from app_login_log where info_id in
<foreach item="infoId" collection="array" open="(" separator="," close=")">
#{infoId}
</foreach>
</delete>
<!-- 清空登录日志 -->
<delete id="cleanAppLoginLog">
truncate table app_login_log
</delete>
<!-- 根据时间范围删除登录日志 -->
<delete id="deleteAppLoginLogByTime">
delete from app_login_log
<where>
<if test="beginTime != null and beginTime != ''">and login_time &gt;= #{beginTime}</if>
<if test="endTime != null and endTime != ''">and login_time &lt;= #{endTime}</if>
</where>
</delete>
</mapper>
@@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.member.mapper.AppMessageMapper">
<resultMap type="org.dromara.member.domain.AppMessage" id="AppMessageResult">
<result property="messageId" column="message_id"/>
<result property="title" column="title"/>
<result property="content" column="content"/>
<result property="messageType" column="message_type"/>
<result property="pushTarget" column="push_target"/>
<result property="targetIds" column="target_ids"/>
<result property="sendTime" column="send_time"/>
<result property="status" column="status"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
</resultMap>
<resultMap type="org.dromara.member.domain.vo.AppMessageVo" id="AppMessageVoResult">
<result property="messageId" column="message_id"/>
<result property="title" column="title"/>
<result property="content" column="content"/>
<result property="messageType" column="message_type"/>
<result property="pushTarget" column="push_target"/>
<result property="targetIds" column="target_ids"/>
<result property="sendTime" column="send_time"/>
<result property="status" column="status"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectAppMessageVo">
select message_id,
title,
content,
message_type,
push_target,
target_ids,
send_time,
status,
create_by,
create_time,
update_by,
update_time,
remark
from app_message
</sql>
<select id="selectMessagesByUserId" parameterType="Long" resultMap="AppMessageResult">
<include refid="selectAppMessageVo"/>
where (push_target = 'user' and FIND_IN_SET(#{userId}, target_ids))
or push_target = 'all'
order by send_time desc
</select>
<select id="selectMessagesByRoleId" parameterType="Long" resultMap="AppMessageResult">
<include refid="selectAppMessageVo"/>
where push_target = 'role' and FIND_IN_SET(#{roleId}, target_ids)
order by send_time desc
</select>
<select id="selectMessagesForAll" resultMap="AppMessageResult">
<include refid="selectAppMessageVo"/>
where push_target = 'all'
order by send_time desc
</select>
<select id="selectAppMessageList" parameterType="AppMessage" resultMap="AppMessageVoResult">
<include refid="selectAppMessageVo"/>
<where>
<if test="title != null and title != ''">and title like concat('%', #{title}, '%')</if>
<if test="messageType != null and messageType != ''">and message_type = #{messageType}</if>
<if test="pushTarget != null and pushTarget != ''">and push_target = #{pushTarget}</if>
<if test="status != null and status != ''">and status = #{status}</if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
and date_format(send_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
</if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
and date_format(send_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
</if>
</where>
order by send_time desc
</select>
<select id="selectAppMessageByMessageId" parameterType="Long" resultMap="AppMessageVoResult">
<include refid="selectAppMessageVo"/>
where message_id = #{messageId}
</select>
<select id="selectAppMessageByStatus" parameterType="String" resultMap="AppMessageVoResult">
<include refid="selectAppMessageVo"/>
where status = #{status}
order by send_time desc
</select>
<insert id="insertAppMessage" parameterType="AppMessage" useGeneratedKeys="true" keyProperty="messageId">
insert into app_message
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="title != null and title != ''">title,</if>
<if test="content != null">content,</if>
<if test="messageType != null and messageType != ''">message_type,</if>
<if test="pushTarget != null and pushTarget != ''">push_target,</if>
<if test="targetIds != null">target_ids,</if>
<if test="sendTime != null">send_time,</if>
<if test="status != null">status,</if>
<if test="createBy != null">create_by,</if>
<if test="remark != null">remark,</if>
create_time
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="title != null and title != ''">#{title},</if>
<if test="content != null">#{content},</if>
<if test="messageType != null and messageType != ''">#{messageType},</if>
<if test="pushTarget != null and pushTarget != ''">#{pushTarget},</if>
<if test="targetIds != null">#{targetIds},</if>
<if test="sendTime != null">#{sendTime},</if>
<if test="status != null">#{status},</if>
<if test="createBy != null">#{createBy},</if>
<if test="remark != null">#{remark},</if>
sysdate()
</trim>
</insert>
<update id="updateAppMessage" parameterType="AppMessage">
update app_message
<trim prefix="SET" suffixOverrides=",">
<if test="title != null and title != ''">title = #{title},</if>
<if test="content != null">content = #{content},</if>
<if test="messageType != null and messageType != ''">message_type = #{messageType},</if>
<if test="pushTarget != null and pushTarget != ''">push_target = #{pushTarget},</if>
<if test="targetIds != null">target_ids = #{targetIds},</if>
<if test="sendTime != null">send_time = #{sendTime},</if>
<if test="status != null">status = #{status},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="remark != null">remark = #{remark},</if>
update_time = sysdate()
</trim>
where message_id = #{messageId}
</update>
<update id="updateMessageStatus" parameterType="AppMessage">
update app_message
set status = #{status},
update_by = #{updateBy},
update_time = sysdate()
where message_id = #{messageId}
</update>
<delete id="deleteAppMessageByMessageId" parameterType="Long">
delete
from app_message
where message_id = #{messageId}
</delete>
<delete id="deleteAppMessageByMessageIds" parameterType="String">
delete from app_message where message_id in
<foreach item="messageId" collection="array" open="(" separator="," close=")">
#{messageId}
</foreach>
</delete>
<delete id="deleteAppMessageByStatus" parameterType="String">
delete
from app_message
where status = #{status}
</delete>
</mapper>
@@ -0,0 +1,159 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.member.mapper.AppOperLogMapper">
<resultMap type="org.dromara.member.domain.AppOperLog" id="AppOperLogResult">
<id property="operId" column="oper_id"/>
<result property="title" column="title"/>
<result property="businessType" column="business_type"/>
<result property="method" column="method"/>
<result property="requestMethod" column="request_method"/>
<result property="operName" column="oper_name"/>
<result property="operUrl" column="oper_url"/>
<result property="operIp" column="oper_ip"/>
<result property="operLocation" column="oper_location"/>
<result property="operParam" column="oper_param"/>
<result property="jsonResult" column="json_result"/>
<result property="status" column="status"/>
<result property="errorMsg" column="error_msg"/>
<result property="operTime" column="oper_time"/>
<result property="costTime" column="cost_time"/>
</resultMap>
<resultMap type="org.dromara.member.domain.vo.AppOperLogVo" id="AppOperLogVoResult">
<id property="operId" column="oper_id"/>
<result property="title" column="title"/>
<result property="businessType" column="business_type"/>
<result property="method" column="method"/>
<result property="requestMethod" column="request_method"/>
<result property="operName" column="oper_name"/>
<result property="operUrl" column="oper_url"/>
<result property="operIp" column="oper_ip"/>
<result property="operLocation" column="oper_location"/>
<result property="operParam" column="oper_param"/>
<result property="jsonResult" column="json_result"/>
<result property="status" column="status"/>
<result property="errorMsg" column="error_msg"/>
<result property="operTime" column="oper_time"/>
<result property="costTime" column="cost_time"/>
</resultMap>
<sql id="selectAppOperLogVo">
select oper_id,
title,
business_type,
method,
request_method,
oper_name,
oper_url,
oper_ip,
oper_location,
oper_param,
json_result,
status,
error_msg,
oper_time,
cost_time
from app_oper_log
</sql>
<!-- 查询操作日志列表 -->
<select id="selectAppOperLogList" parameterType="AppOperLog" resultMap="AppOperLogVoResult">
<include refid="selectAppOperLogVo"/>
<where>
<if test="title != null and title != ''">and title like concat('%', #{title}, '%')</if>
<if test="businessType != null">and business_type = #{businessType}</if>
<if test="method != null and method != ''">and method like concat('%', #{method}, '%')</if>
<if test="requestMethod != null and requestMethod != ''">and request_method = #{requestMethod}</if>
<if test="operName != null and operName != ''">and oper_name like concat('%', #{operName}, '%')</if>
<if test="operIp != null and operIp != ''">and oper_ip like concat('%', #{operIp}, '%')</if>
<if test="status != null">and status = #{status}</if>
<if test="params.beginTime != null and params.beginTime != ''">and oper_time &gt;= #{params.beginTime}</if>
<if test="params.endTime != null and params.endTime != ''">and oper_time &lt;= #{params.endTime}</if>
</where>
order by oper_time desc
</select>
<!-- 根据ID查询操作日志 -->
<select id="selectAppOperLogByOperId" parameterType="Long" resultMap="AppOperLogVoResult">
<include refid="selectAppOperLogVo"/>
where oper_id = #{operId}
</select>
<!-- 根据操作人查询操作日志 -->
<select id="selectAppOperLogByOperName" parameterType="String" resultMap="AppOperLogVoResult">
<include refid="selectAppOperLogVo"/>
where oper_name = #{operName}
order by oper_time desc
</select>
<!-- 新增操作日志 -->
<insert id="insertAppOperLog" parameterType="org.dromara.member.domain.AppOperLog" useGeneratedKeys="true"
keyProperty="operId">
insert into app_oper_log
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="title != null and title != ''">title,</if>
<if test="businessType != null">business_type,</if>
<if test="method != null and method != ''">method,</if>
<if test="requestMethod != null and requestMethod != ''">request_method,</if>
<if test="operName != null and operName != ''">oper_name,</if>
<if test="operUrl != null and operUrl != ''">oper_url,</if>
<if test="operIp != null and operIp != ''">oper_ip,</if>
<if test="operLocation != null and operLocation != ''">oper_location,</if>
<if test="operParam != null and operParam != ''">oper_param,</if>
<if test="jsonResult != null and jsonResult != ''">json_result,</if>
<if test="status != null">status,</if>
<if test="errorMsg != null and errorMsg != ''">error_msg,</if>
<if test="operTime != null">oper_time,</if>
<if test="costTime != null">cost_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="title != null and title != ''">#{title},</if>
<if test="businessType != null">#{businessType},</if>
<if test="method != null and method != ''">#{method},</if>
<if test="requestMethod != null and requestMethod != ''">#{requestMethod},</if>
<if test="operName != null and operName != ''">#{operName},</if>
<if test="operUrl != null and operUrl != ''">#{operUrl},</if>
<if test="operIp != null and operIp != ''">#{operIp},</if>
<if test="operLocation != null and operLocation != ''">#{operLocation},</if>
<if test="operParam != null and operParam != ''">#{operParam},</if>
<if test="jsonResult != null and jsonResult != ''">#{jsonResult},</if>
<if test="status != null">#{status},</if>
<if test="errorMsg != null and errorMsg != ''">#{errorMsg},</if>
<if test="operTime != null">#{operTime},</if>
<if test="costTime != null">#{costTime},</if>
</trim>
</insert>
<!-- 删除操作日志 -->
<delete id="deleteAppOperLogByOperId" parameterType="Long">
delete
from app_oper_log
where oper_id = #{operId}
</delete>
<!-- 批量删除操作日志 -->
<delete id="deleteAppOperLogByOperIds" parameterType="String">
delete from app_oper_log where oper_id in
<foreach item="operId" collection="array" open="(" separator="," close=")">
#{operId}
</foreach>
</delete>
<!-- 清空操作日志 -->
<delete id="cleanAppOperLog">
truncate table app_oper_log
</delete>
<!-- 根据时间范围删除操作日志 -->
<delete id="deleteAppOperLogByTime">
delete from app_oper_log
<where>
<if test="beginTime != null and beginTime != ''">and oper_time &gt;= #{beginTime}</if>
<if test="endTime != null and endTime != ''">and oper_time &lt;= #{endTime}</if>
</where>
</delete>
</mapper>
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.member.mapper.AppRoleMapper">
<resultMap type="org.dromara.member.domain.AppRole" id="AppRoleResult">
<id property="roleId" column="role_id"/>
<result property="roleName" column="role_name"/>
<result property="roleKey" column="role_key"/>
<result property="roleSort" column="role_sort"/>
<result property="status" column="status"/>
<result property="delFlag" column="del_flag"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
</resultMap>
<resultMap type="org.dromara.member.domain.vo.AppRoleVo" id="AppRoleVoResult">
<id property="roleId" column="role_id"/>
<result property="roleName" column="role_name"/>
<result property="roleKey" column="role_key"/>
<result property="roleSort" column="role_sort"/>
<result property="status" column="status"/>
<result property="createTime" column="create_time"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectAppRoleVo">
select distinct r.role_id,
r.role_name,
r.role_key,
r.role_sort,
r.status,
r.del_flag,
r.create_time,
r.remark
from app_role r
left join app_user_role ur on ur.role_id = r.role_id
left join app_user u on u.user_id = ur.user_id
</sql>
<select id="selectRolesByUserId" parameterType="Long" resultMap="AppRoleVoResult">
<include refid="selectAppRoleVo"/>
WHERE r.del_flag = '0' and ur.user_id = #{userId}
</select>
<select id="selectRolePermissionByUserId" parameterType="Long" resultType="String">
select distinct r.role_key
from app_role r
left join app_user_role ur on ur.role_id = r.role_id
WHERE r.del_flag = '0'
and ur.user_id = #{userId}
</select>
<select id="selectRoleAll" resultMap="AppRoleVoResult">
<include refid="selectAppRoleVo"/>
where r.del_flag = '0'
</select>
<select id="selectRoleListByUserId" parameterType="Long" resultType="Long">
select r.role_id
from app_role r
left join app_user_role ur on ur.role_id = r.role_id
left join app_user u on u.user_id = ur.user_id
where u.user_id = #{userId}
</select>
<select id="checkRoleNameUnique" parameterType="String" resultType="int">
select count(1)
from app_role r
where r.role_name = #{roleName}
and r.del_flag = '0'
</select>
<select id="checkRoleKeyUnique" parameterType="String" resultType="int">
select count(1)
from app_role r
where r.role_key = #{roleKey}
and r.del_flag = '0'
</select>
</mapper>
@@ -0,0 +1,186 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.member.mapper.AppSocialAuthMapper">
<resultMap type="org.dromara.member.domain.AppSocialAuth" id="AppSocialAuthResult">
<id property="id" column="id"/>
<result property="userId" column="user_id"/>
<result property="platform" column="platform"/>
<result property="platformUid" column="platform_uid"/>
<result property="openId" column="open_id"/>
<result property="unionId" column="union_id"/>
<result property="accessToken" column="access_token"/>
<result property="expireIn" column="expire_in"/>
<result property="refreshToken" column="refresh_token"/>
<result property="scope" column="scope"/>
<result property="delFlag" column="del_flag"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<resultMap type="org.dromara.member.domain.vo.AppSocialAuthVo" id="AppSocialAuthVoResult">
<id property="id" column="id"/>
<result property="userId" column="user_id"/>
<result property="platform" column="platform"/>
<result property="platformUid" column="platform_uid"/>
<result property="openId" column="open_id"/>
<result property="unionId" column="union_id"/>
<result property="accessToken" column="access_token"/>
<result property="expireIn" column="expire_in"/>
<result property="refreshToken" column="refresh_token"/>
<result property="scope" column="scope"/>
<result property="createTime" column="create_time"/>
</resultMap>
<sql id="selectAppSocialAuthVo">
select id,
user_id,
platform,
platform_uid,
open_id,
union_id,
access_token,
expire_in,
refresh_token,
scope,
del_flag,
create_by,
create_time,
update_by,
update_time
from app_social_auth
</sql>
<!-- 查询社交认证列表 -->
<select id="selectAppSocialAuthList" parameterType="AppSocialAuth" resultMap="AppSocialAuthVoResult">
<include refid="selectAppSocialAuthVo"/>
<where>
<if test="userId != null">and user_id = #{userId}</if>
<if test="platform != null and platform != ''">and platform = #{platform}</if>
<if test="platformUid != null and platformUid != ''">and platform_uid = #{platformUid}</if>
<if test="openId != null and openId != ''">and open_id = #{openId}</if>
<if test="unionId != null and unionId != ''">and union_id = #{unionId}</if>
and del_flag = '0'
</where>
order by create_time desc
</select>
<!-- 根据ID查询社交认证 -->
<select id="selectAppSocialAuthById" parameterType="Long" resultMap="AppSocialAuthVoResult">
<include refid="selectAppSocialAuthVo"/>
where id = #{id} and del_flag = '0'
</select>
<!-- 根据用户ID查询社交认证列表 -->
<select id="selectAppSocialAuthByUserId" parameterType="Long" resultMap="AppSocialAuthVoResult">
<include refid="selectAppSocialAuthVo"/>
where user_id = #{userId} and del_flag = '0'
order by create_time desc
</select>
<!-- 根据平台和openId查询社交认证 -->
<select id="selectAppSocialAuthByPlatformAndOpenId" resultMap="AppSocialAuthResult">
<include refid="selectAppSocialAuthVo"/>
where platform = #{platform} and open_id = #{openId} and del_flag = '0'
</select>
<!-- 根据平台和unionId查询社交认证 -->
<select id="selectAppSocialAuthByPlatformAndUnionId" resultMap="AppSocialAuthResult">
<include refid="selectAppSocialAuthVo"/>
where platform = #{platform} and union_id = #{unionId} and del_flag = '0'
</select>
<!-- 新增社交认证 -->
<insert id="insertAppSocialAuth" parameterType="org.dromara.member.domain.AppSocialAuth" useGeneratedKeys="true"
keyProperty="id">
insert into app_social_auth
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userId != null">user_id,</if>
<if test="platform != null and platform != ''">platform,</if>
<if test="platformUid != null and platformUid != ''">platform_uid,</if>
<if test="openId != null and openId != ''">open_id,</if>
<if test="unionId != null and unionId != ''">union_id,</if>
<if test="accessToken != null and accessToken != ''">access_token,</if>
<if test="expireIn != null">expire_in,</if>
<if test="refreshToken != null and refreshToken != ''">refresh_token,</if>
<if test="scope != null and scope != ''">scope,</if>
<if test="delFlag != null and delFlag != ''">del_flag,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null and updateBy != ''">update_by,</if>
<if test="updateTime != null">update_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userId != null">#{userId},</if>
<if test="platform != null and platform != ''">#{platform},</if>
<if test="platformUid != null and platformUid != ''">#{platformUid},</if>
<if test="openId != null and openId != ''">#{openId},</if>
<if test="unionId != null and unionId != ''">#{unionId},</if>
<if test="accessToken != null and accessToken != ''">#{accessToken},</if>
<if test="expireIn != null">#{expireIn},</if>
<if test="refreshToken != null and refreshToken != ''">#{refreshToken},</if>
<if test="scope != null and scope != ''">#{scope},</if>
<if test="delFlag != null and delFlag != ''">#{delFlag},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null and updateBy != ''">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
</trim>
</insert>
<!-- 修改社交认证 -->
<update id="updateAppSocialAuth" parameterType="org.dromara.member.domain.AppSocialAuth">
update app_social_auth
<trim prefix="SET" suffixOverrides=",">
<if test="userId != null">user_id = #{userId},</if>
<if test="platform != null and platform != ''">platform = #{platform},</if>
<if test="platformUid != null and platformUid != ''">platform_uid = #{platformUid},</if>
<if test="openId != null and openId != ''">open_id = #{openId},</if>
<if test="unionId != null and unionId != ''">union_id = #{unionId},</if>
<if test="accessToken != null and accessToken != ''">access_token = #{accessToken},</if>
<if test="expireIn != null">expire_in = #{expireIn},</if>
<if test="refreshToken != null and refreshToken != ''">refresh_token = #{refreshToken},</if>
<if test="scope != null and scope != ''">scope = #{scope},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</trim>
where id = #{id}
</update>
<!-- 删除社交认证 -->
<update id="deleteAppSocialAuthById" parameterType="Long">
update app_social_auth
set del_flag = '2'
where id = #{id}
</update>
<!-- 批量删除社交认证 -->
<update id="deleteAppSocialAuthByIds" parameterType="String">
update app_social_auth set del_flag = '2' where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<!-- 根据用户ID删除社交认证 -->
<update id="deleteAppSocialAuthByUserId" parameterType="Long">
update app_social_auth
set del_flag = '2'
where user_id = #{userId}
</update>
<!-- 更新访问令牌 -->
<update id="updateAccessToken">
update app_social_auth
set access_token = #{accessToken},
expire_in = #{expireIn},
refresh_token = #{refreshToken},
update_time = #{updateTime}
where id = #{id}
</update>
</mapper>
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.member.mapper.AppUserMapper">
<resultMap type="org.dromara.member.domain.AppUser" id="AppUserResult">
<id property="userId" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="nickName" column="nick_name"/>
<result property="phone" column="phone"/>
<result property="email" column="email"/>
<result property="sex" column="sex"/>
<result property="avatar" column="avatar"/>
<result property="password" column="password"/>
<result property="status" column="status"/>
<result property="delFlag" column="del_flag"/>
<result property="loginIp" column="login_ip"/>
<result property="loginDate" column="login_date"/>
<result property="registerTime" column="register_time"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
</resultMap>
<resultMap type="org.dromara.member.domain.vo.AppUserVo" id="AppUserResultVo">
<id property="userId" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="nickName" column="nick_name"/>
<result property="phone" column="phone"/>
<result property="email" column="email"/>
<result property="sex" column="sex"/>
<result property="avatar" column="avatar"/>
<result property="status" column="status"/>
<result property="loginIp" column="login_ip"/>
<result property="loginDate" column="login_date"/>
<result property="registerTime" column="register_time"/>
<result property="createTime" column="create_time"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectAppUserVo">
select user_id,
user_name,
nick_name,
phone,
email,
sex,
avatar,
password,
status,
del_flag,
login_ip,
login_date,
register_time,
create_by,
create_time,
update_by,
update_time,
remark
from app_user
</sql>
<select id="selectUserByUserName" parameterType="String" resultMap="AppUserResultVo">
<include refid="selectAppUserVo"/>
where user_name = #{userName} and del_flag = '0'
</select>
<select id="selectUserByPhone" parameterType="String" resultMap="AppUserResultVo">
<include refid="selectAppUserVo"/>
where phone = #{phone} and del_flag = '0'
</select>
<select id="selectUserByEmail" parameterType="String" resultMap="AppUserResultVo">
<include refid="selectAppUserVo"/>
where email = #{email} and del_flag = '0'
</select>
<select id="checkUserNameUnique" parameterType="String" resultType="int">
select count(*)
from app_user
where user_name = #{userName}
and del_flag = '0'
</select>
<select id="checkPhoneUnique" parameterType="String" resultType="int">
select count(*)
from app_user
where phone = #{phone}
and del_flag = '0'
</select>
<select id="checkEmailUnique" parameterType="String" resultType="int">
select count(*)
from app_user
where email = #{email}
and del_flag = '0'
</select>
</mapper>
@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.member.mapper.AppUserMessageMapper">
<resultMap type="org.dromara.member.domain.AppUserMessage" id="AppUserMessageResult">
<id property="id" column="id"/>
<result property="userId" column="user_id"/>
<result property="messageId" column="message_id"/>
<result property="isRead" column="is_read"/>
<result property="readTime" column="read_time"/>
<result property="createTime" column="create_time"/>
</resultMap>
<resultMap type="org.dromara.member.domain.vo.AppUserMessageVo" id="AppUserMessageVoResult">
<id property="id" column="id"/>
<result property="userId" column="user_id"/>
<result property="messageId" column="message_id"/>
<result property="isRead" column="is_read"/>
<result property="readTime" column="read_time"/>
<result property="createTime" column="create_time"/>
</resultMap>
<sql id="selectAppUserMessageVo">
select user_message_id, user_id, message_id, is_read, read_time, create_time
from app_user_message
</sql>
<select id="selectAppUserMessageList" parameterType="AppUserMessage" resultMap="AppUserMessageVoResult">
<include refid="selectAppUserMessageVo"/>
<where>
<if test="userId != null">and user_id = #{userId}</if>
<if test="messageId != null">and message_id = #{messageId}</if>
<if test="isRead != null">and is_read = #{isRead}</if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
and date_format(create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
</if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
and date_format(create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
</if>
</where>
order by create_time desc
</select>
<select id="selectAppUserMessageByUserMessageId" parameterType="Long" resultMap="AppUserMessageVoResult">
<include refid="selectAppUserMessageVo"/>
where user_message_id = #{userMessageId}
</select>
<select id="selectAppUserMessageByUserId" parameterType="Long" resultMap="AppUserMessageVoResult">
<include refid="selectAppUserMessageVo"/>
where user_id = #{userId}
order by create_time desc
</select>
<select id="selectUnreadMessagesByUserId" parameterType="Long" resultMap="AppUserMessageVoResult">
<include refid="selectAppUserMessageVo"/>
where user_id = #{userId} and is_read = 0
order by create_time desc
</select>
<select id="countUnreadMessagesByUserId" parameterType="Long" resultType="int">
select count(1)
from app_user_message
where user_id = #{userId}
and is_read = 0
</select>
<select id="selectAppUserMessageByUserIdAndMessageId" resultMap="AppUserMessageVoResult">
<include refid="selectAppUserMessageVo"/>
where user_id = #{userId} and message_id = #{messageId}
</select>
<insert id="insertAppUserMessage" parameterType="AppUserMessage" useGeneratedKeys="true"
keyProperty="userMessageId">
insert into app_user_message
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userId != null">user_id,</if>
<if test="messageId != null">message_id,</if>
<if test="isRead != null">is_read,</if>
<if test="readTime != null">read_time,</if>
create_time
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userId != null">#{userId},</if>
<if test="messageId != null">#{messageId},</if>
<if test="isRead != null">#{isRead},</if>
<if test="readTime != null">#{readTime},</if>
sysdate()
</trim>
</insert>
<insert id="batchInsertAppUserMessage" parameterType="java.util.List">
insert into app_user_message(user_id, message_id, is_read, create_time)
values
<foreach collection="list" item="item" separator=",">
(#{item.userId}, #{item.messageId}, #{item.isRead}, sysdate())
</foreach>
</insert>
<update id="updateAppUserMessage" parameterType="AppUserMessage">
update app_user_message
<trim prefix="SET" suffixOverrides=",">
<if test="userId != null">user_id = #{userId},</if>
<if test="messageId != null">message_id = #{messageId},</if>
<if test="isRead != null">is_read = #{isRead},</if>
<if test="readTime != null">read_time = #{readTime},</if>
</trim>
where user_message_id = #{userMessageId}
</update>
<update id="markAsRead" parameterType="Long">
update app_user_message
set is_read = 1,
read_time = sysdate()
where user_message_id = #{userMessageId}
</update>
<update id="markAllAsReadByUserId" parameterType="Long">
update app_user_message
set is_read = 1,
read_time = sysdate()
where user_id = #{userId}
and is_read = 0
</update>
<delete id="deleteAppUserMessageByUserMessageId" parameterType="Long">
delete
from app_user_message
where user_message_id = #{userMessageId}
</delete>
<delete id="deleteAppUserMessageByUserMessageIds" parameterType="String">
delete from app_user_message where user_message_id in
<foreach item="userMessageId" collection="array" open="(" separator="," close=")">
#{userMessageId}
</foreach>
</delete>
<delete id="deleteAppUserMessageByUserId" parameterType="Long">
delete
from app_user_message
where user_id = #{userId}
</delete>
<delete id="deleteAppUserMessageByMessageId" parameterType="Long">
delete
from app_user_message
where message_id = #{messageId}
</delete>
</mapper>
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.member.mapper.AppUserRoleMapper">
<resultMap type="org.dromara.member.domain.AppUserRole" id="AppUserRoleResult">
<result property="userId" column="user_id"/>
<result property="roleId" column="role_id"/>
</resultMap>
<!-- 通过用户ID删除用户和角色关联 -->
<delete id="deleteUserRoleByUserId" parameterType="Long">
delete
from app_user_role
where user_id = #{userId}
</delete>
<!-- 批量删除用户和角色关联 -->
<delete id="deleteUserRole" parameterType="Long">
delete from app_user_role where user_id in
<foreach collection="array" item="userId" open="(" separator="," close=")">
#{userId}
</foreach>
</delete>
<!-- 通过角色ID查询角色使用数量 -->
<select id="countUserRoleByRoleId" parameterType="Long" resultType="int">
select count(1)
from app_user_role
where role_id = #{roleId}
</select>
<!-- 批量新增用户角色信息 -->
<insert id="batchUserRole">
insert into app_user_role(user_id, role_id) values
<foreach item="item" index="index" collection="list" separator=",">
(#{item.userId}, #{item.roleId})
</foreach>
</insert>
</mapper>
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.member.mapper.AppVersionMapper">
<resultMap type="org.dromara.member.domain.AppVersion" id="AppVersionResult">
<id property="versionId" column="version_id"/>
<result property="versionCode" column="version_code"/>
<result property="versionName" column="version_name"/>
<result property="updateContent" column="update_content"/>
<result property="downloadUrl" column="download_url"/>
<result property="isForce" column="is_force"/>
<result property="platform" column="platform"/>
<result property="createTime" column="create_time"/>
</resultMap>
<resultMap type="org.dromara.member.domain.vo.AppVersionVo" id="AppVersionVoResult">
<id property="versionId" column="version_id"/>
<result property="versionCode" column="version_code"/>
<result property="versionName" column="version_name"/>
<result property="updateContent" column="update_content"/>
<result property="downloadUrl" column="download_url"/>
<result property="isForce" column="is_force"/>
<result property="platform" column="platform"/>
<result property="createTime" column="create_time"/>
</resultMap>
<sql id="selectAppVersionVo">
select version_id,
version_code,
version_name,
update_content,
download_url,
is_force,
platform,
create_time
from app_version
</sql>
<select id="selectAppVersionList" parameterType="AppVersion" resultMap="AppVersionVoResult">
<include refid="selectAppVersionVo"/>
<where>
<if test="versionName != null and versionName != ''">and version_name like concat('%', #{versionName},
'%')
</if>
<if test="versionCode != null and versionCode != ''">and version_code = #{versionCode}</if>
<if test="platform != null and platform != ''">and platform = #{platform}</if>
<if test="isForce != null">and is_force = #{isForce}</if>
</where>
order by create_time desc
</select>
<select id="selectAppVersionByVersionId" parameterType="Long" resultMap="AppVersionVoResult">
<include refid="selectAppVersionVo"/>
where version_id = #{versionId}
</select>
<select id="selectLatestVersionByPlatform" parameterType="String" resultMap="AppVersionVoResult">
<include refid="selectAppVersionVo"/>
where platform = #{platform}
order by version_code desc
limit 1
</select>
<select id="checkVersionCodeUnique" parameterType="AppVersion" resultType="int">
select count(1) from app_version
where version_code = #{versionCode} and platform = #{platform}
<if test="versionId != null and versionId != 0">and version_id != #{versionId}</if>
</select>
<insert id="insertAppVersion" parameterType="AppVersion" useGeneratedKeys="true" keyProperty="versionId">
insert into app_version
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="versionCode != null and versionCode != ''">version_code,</if>
<if test="versionName != null and versionName != ''">version_name,</if>
<if test="updateContent != null">update_content,</if>
<if test="downloadUrl != null">download_url,</if>
<if test="isForce != null">is_force,</if>
<if test="platform != null and platform != ''">platform,</if>
create_time
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="versionCode != null and versionCode != ''">#{versionCode},</if>
<if test="versionName != null and versionName != ''">#{versionName},</if>
<if test="updateContent != null">#{updateContent},</if>
<if test="downloadUrl != null">#{downloadUrl},</if>
<if test="isForce != null">#{isForce},</if>
<if test="platform != null and platform != ''">#{platform},</if>
sysdate()
</trim>
</insert>
<update id="updateAppVersion" parameterType="AppVersion">
update app_version
<trim prefix="SET" suffixOverrides=",">
<if test="versionCode != null and versionCode != ''">version_code = #{versionCode},</if>
<if test="versionName != null and versionName != ''">version_name = #{versionName},</if>
<if test="updateContent != null">update_content = #{updateContent},</if>
<if test="downloadUrl != null">download_url = #{downloadUrl},</if>
<if test="isForce != null">is_force = #{isForce},</if>
<if test="platform != null and platform != ''">platform = #{platform},</if>
</trim>
where version_id = #{versionId}
</update>
<delete id="deleteAppVersionByVersionId" parameterType="Long">
delete
from app_version
where version_id = #{versionId}
</delete>
<delete id="deleteAppVersionByVersionIds" parameterType="String">
delete from app_version where version_id in
<foreach item="versionId" collection="array" open="(" separator="," close=")">
#{versionId}
</foreach>
</delete>
</mapper>