Merge local friend_back v2 including worktree changes
This commit is contained in:
commit
9811d336e0
156 files changed
+6580
-186
No files matched your search
@@ -0,0 +1,283 @@
|
||||
# 清库操作文档:清空全部用户数据(含 IM)
|
||||
|
||||
> 适用环境:荷塘月色业务后端 `friend_back` + IM 系统 `chat-im`
|
||||
> 目标:清除**所有会员用户**相关数据(资料、会话、钱包/分销用户侧数据、IM 好友与聊天等)
|
||||
> **不是**「注销单个用户」;也不是清空系统配置/管理员账号。
|
||||
|
||||
---
|
||||
|
||||
## 1. 先搞清楚有两套系统
|
||||
|
||||
| 系统 | 代码目录 | MySQL 库 | Redis | 文件 |
|
||||
|------|----------|----------|-------|------|
|
||||
| 业务 API | `friend_back` | **`chat_v1`** | **db 7** | `infra_file` + 对应对象存储(七牛/MinIO 等) |
|
||||
| IM | `chat-im`(`im-platform` / `im-server`) | **`im_platform`** | **db 10** | MinIO bucket **`im-platform`** |
|
||||
|
||||
- 会员 ID 对齐:`member_user.id` == `im_user.id`
|
||||
- `friend_back` 只通过 HTTP 调 IM(`chat.base-url`),**没有「删除用户」的第三方 API**,IM 必须在 `im_platform` 库直接清
|
||||
- 特殊 IM 账号(**不要删**):
|
||||
- `-100` 系统消息通知号
|
||||
- `-200` 客服号
|
||||
|
||||
生产配置参考:
|
||||
|
||||
- 业务:`friend_back/yudao-server/src/main/resources/application-prod.yaml`(`chat_v1`、Redis db7、`chat.base-url`)
|
||||
- IM:`chat-im/im-platform/src/main/resources/application-prod.yml`(`im_platform`、Redis db10)
|
||||
|
||||
---
|
||||
|
||||
## 2. 操作前检查清单
|
||||
|
||||
1. **确认环境**(测试 / 预发 / 生产),禁止在未备份的生产库直接执行。
|
||||
2. **全量备份**
|
||||
```bash
|
||||
mysqldump -h... -u... -p chat_v1 > chat_v1_backup_$(date +%Y%m%d).sql
|
||||
mysqldump -h... -u... -p im_platform > im_platform_backup_$(date +%Y%m%d).sql
|
||||
# Redis:按运维规范导出 RDB/AOF(业务 db7、IM db10)
|
||||
# 对象存储:按需快照或确认可丢弃用户上传文件
|
||||
```
|
||||
3. **停写流量**(强烈建议)
|
||||
- 停或摘流:`yudao-server`、`im-platform`、`im-server`
|
||||
- 避免删一半又有新注册/新消息写入
|
||||
4. **合规确认**
|
||||
- 支付订单、退款、提现流水是否允许物理删除(默认建议**保留或脱敏归档**,见第 5 节)
|
||||
5. **现有后台没有「一键清会员」接口**,只能运维 SQL + Redis + 存储清理。
|
||||
|
||||
---
|
||||
|
||||
## 3. 建议操作顺序(总览)
|
||||
|
||||
```text
|
||||
① 停服务
|
||||
② 备份 MySQL / Redis / 文件
|
||||
③ 清业务库 chat_v1(用户相关表)
|
||||
④ 清 IM 库 im_platform(保留 -100/-200)
|
||||
⑤ 清 Redis db7、db10
|
||||
⑥ 清用户上传文件(可选)
|
||||
⑦ 启服务并验收
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 业务库 `chat_v1`:清用户数据
|
||||
|
||||
> 执行前先 `USE chat_v1;`
|
||||
> 下面按「先子表、后主表」;若表不存在可跳过。
|
||||
> 全量重置测试环境可用 `TRUNCATE`;生产更建议 `DELETE` + 条件(如按 `tenant_id`)。
|
||||
|
||||
### 4.1 会员扩展与行为(必清)
|
||||
|
||||
```sql
|
||||
-- 相册 / 位置 / 动态相关
|
||||
TRUNCATE TABLE member_photo;
|
||||
TRUNCATE TABLE member_location;
|
||||
TRUNCATE TABLE member_recent;
|
||||
TRUNCATE TABLE member_operation;
|
||||
TRUNCATE TABLE member_view_record;
|
||||
TRUNCATE TABLE member_recent_tag;
|
||||
TRUNCATE TABLE member_recent_cfg;
|
||||
|
||||
-- 认证 / 反馈 / 开通记录 / 积分经验签到
|
||||
TRUNCATE TABLE member_com_auth;
|
||||
TRUNCATE TABLE member_feedback;
|
||||
TRUNCATE TABLE member_open_type_user;
|
||||
TRUNCATE TABLE member_point_record;
|
||||
TRUNCATE TABLE member_level_record;
|
||||
TRUNCATE TABLE member_experience_record;
|
||||
TRUNCATE TABLE member_sign_in_record;
|
||||
TRUNCATE TABLE member_address;
|
||||
```
|
||||
|
||||
### 4.2 登录态 / 社交绑定(会员侧)
|
||||
|
||||
```sql
|
||||
-- user_type = 1 表示会员(Member)
|
||||
DELETE FROM system_oauth2_access_token WHERE user_type = 1;
|
||||
DELETE FROM system_oauth2_refresh_token WHERE user_type = 1;
|
||||
DELETE FROM system_oauth2_approve WHERE user_type = 1;
|
||||
DELETE FROM system_oauth2_code WHERE user_type = 1;
|
||||
|
||||
DELETE FROM system_social_user_bind WHERE user_type = 1;
|
||||
-- 可选:清理已无绑定的 social_user 孤儿数据(按你们库实际字段调整)
|
||||
|
||||
DELETE FROM system_login_log WHERE user_type = 1;
|
||||
DELETE FROM system_notify_message WHERE user_type = 1;
|
||||
-- 短信验证码可按需清空
|
||||
TRUNCATE TABLE system_sms_code;
|
||||
```
|
||||
|
||||
### 4.3 分销用户数据(必清用户侧)
|
||||
|
||||
```sql
|
||||
TRUNCATE TABLE trade_brokerage_record;
|
||||
TRUNCATE TABLE trade_brokerage_withdraw;
|
||||
TRUNCATE TABLE trade_brokerage_code;
|
||||
TRUNCATE TABLE trade_brokerage_user;
|
||||
```
|
||||
|
||||
### 4.4 钱包用户数据(必清用户侧)
|
||||
|
||||
```sql
|
||||
TRUNCATE TABLE pay_wallet_transaction;
|
||||
TRUNCATE TABLE pay_wallet_recharge;
|
||||
TRUNCATE TABLE pay_wallet_user_transition; -- 若存在
|
||||
TRUNCATE TABLE pay_wallet_tx_auth; -- 若存在
|
||||
DELETE FROM pay_wallet WHERE user_type = 1;
|
||||
```
|
||||
|
||||
### 4.5 商城用户行为(若启用 mall)
|
||||
|
||||
```sql
|
||||
TRUNCATE TABLE trade_cart;
|
||||
TRUNCATE TABLE trade_order_log;
|
||||
TRUNCATE TABLE trade_order_item;
|
||||
TRUNCATE TABLE trade_after_sale_log;
|
||||
TRUNCATE TABLE trade_after_sale;
|
||||
TRUNCATE TABLE trade_order;
|
||||
|
||||
TRUNCATE TABLE product_browse_history;
|
||||
TRUNCATE TABLE product_favorite;
|
||||
TRUNCATE TABLE product_comment;
|
||||
-- 优惠券领取记录等带 user_id 的 promotion_* 表按实际启用情况清理
|
||||
```
|
||||
|
||||
### 4.6 最后删会员主表
|
||||
|
||||
```sql
|
||||
TRUNCATE TABLE member_user;
|
||||
-- 或:DELETE FROM member_user;
|
||||
```
|
||||
|
||||
### 4.7 业务库「不要清」的配置表(示例)
|
||||
|
||||
保留系统能继续跑的配置:
|
||||
|
||||
- `member_config`、`member_level`、`member_tag`、`member_group`
|
||||
- `member_sign_in_config`、`member_open_type`(会员套餐)
|
||||
- `pay_app`、`pay_channel`、`pay_wallet_recharge_package`
|
||||
- `trade_config`
|
||||
- `system_oauth2_client`、`system_social_client`、短信模板/渠道
|
||||
- `system_users` 等**后台管理员**账号(除非连运营账号一起重置)
|
||||
|
||||
---
|
||||
|
||||
## 5. 支付流水是否删除(单独决策)
|
||||
|
||||
| 策略 | 处理 |
|
||||
|------|------|
|
||||
| **推荐(测试可删、生产慎删)** | 保留 `pay_order` / `pay_order_extension` / `pay_refund` / `pay_transfer` / `pay_notify_*`,仅清用户侧钱包与会员 |
|
||||
| **测试环境彻底重置** | 可一并 `TRUNCATE` 上述支付流水表,并确认与渠道对账无冲突 |
|
||||
|
||||
若生产必须销毁支付痕迹,需法务/财务确认后另出工单,不建议与「清用户」混为一谈。
|
||||
|
||||
---
|
||||
|
||||
## 6. IM 库 `im_platform`:清 IM 数据
|
||||
|
||||
> 执行前先 `USE im_platform;`
|
||||
> 建表参考:`chat-im/db/im-platform.sql`
|
||||
|
||||
```sql
|
||||
-- 消息与关系
|
||||
TRUNCATE TABLE im_private_message;
|
||||
TRUNCATE TABLE im_group_message;
|
||||
TRUNCATE TABLE im_friend;
|
||||
TRUNCATE TABLE im_group_member;
|
||||
TRUNCATE TABLE im_group;
|
||||
TRUNCATE TABLE im_file_info;
|
||||
|
||||
-- 用户:保留系统号
|
||||
DELETE FROM im_user WHERE id NOT IN (-100, -200);
|
||||
|
||||
-- 敏感词库一般保留
|
||||
-- TRUNCATE TABLE im_sensitive_word; -- 仅当要重置词库时
|
||||
```
|
||||
|
||||
**说明:**
|
||||
|
||||
- 不要依赖 `friend_back` 的 ChatApi 删用户(接口不支持)
|
||||
- 清完后会员重新登录会走同步逻辑(`syncChatUser`)在 IM 侧重建用户
|
||||
|
||||
---
|
||||
|
||||
## 7. Redis
|
||||
|
||||
**务必指定 DB,禁止误 FLUSH 其它库。**
|
||||
|
||||
```bash
|
||||
# 业务 Redis(application-prod: database 7)
|
||||
redis-cli -n 7 FLUSHDB
|
||||
|
||||
# IM Redis(im-platform/im-server: database 10)
|
||||
redis-cli -n 10 FLUSHDB
|
||||
```
|
||||
|
||||
若不能整库清空,至少清理:
|
||||
|
||||
- 业务:`oauth2_access_token:*`、会员相关 lock/cache、`social_auth_state:*` 等
|
||||
- IM:`im:user:*`、`im:cache:*`、`im:message:*`、`im:readed:*` 等
|
||||
|
||||
---
|
||||
|
||||
## 8. 文件存储(可选)
|
||||
|
||||
| 来源 | 处理建议 |
|
||||
|------|----------|
|
||||
| 业务头像/相册/认证图 | 根据 `infra_file` + `member_user.avatar` / `member_photo` 对应对象存储清理;测试环境可按配置整桶策略清理 |
|
||||
| IM 图片视频 | MinIO bucket `im-platform`;清 `im_file_info` 后按 path 删对象,或测试环境整桶清理 |
|
||||
|
||||
生产环境删除对象存储前务必确认无合规留存要求。
|
||||
|
||||
---
|
||||
|
||||
## 9. 启动与验收
|
||||
|
||||
1. 启动 `im-platform` → `im-server` → `yudao-server`
|
||||
2. 检查:
|
||||
```sql
|
||||
SELECT COUNT(*) FROM chat_v1.member_user; -- 期望 0
|
||||
SELECT COUNT(*) FROM im_platform.im_user
|
||||
WHERE id NOT IN (-100, -200); -- 期望 0
|
||||
SELECT id, user_name FROM im_platform.im_user; -- 应还能看到 -100/-200
|
||||
```
|
||||
3. App 侧:
|
||||
- 旧 token 应失效,需重新注册/登录
|
||||
- 登录后 IM 可正常建链、发消息
|
||||
- 后台管理账号仍可登录(若未清 `system_users`)
|
||||
4. 配置仍在:会员套餐、支付渠道、分销开关、敏感词等
|
||||
|
||||
---
|
||||
|
||||
## 10. 回滚
|
||||
|
||||
仅能依赖操作前备份:
|
||||
|
||||
```bash
|
||||
mysql -h... -u... -p chat_v1 < chat_v1_backup_YYYYMMDD.sql
|
||||
mysql -h... -u... -p im_platform < im_platform_backup_YYYYMMDD.sql
|
||||
# 恢复 Redis / 文件快照后重启服务
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 常见问题
|
||||
|
||||
**Q:只清 `member_user` 行不行?**
|
||||
不行。登录 token、IM 消息、钱包、分销、相册等都会残留,且可能脏数据关联异常。
|
||||
|
||||
**Q:清完业务库,IM 会不会自动空?**
|
||||
不会。必须单独清 `im_platform`(和 Redis db10)。
|
||||
|
||||
**Q:有没有管理后台按钮?**
|
||||
没有会员「注销/全量删除」接口;管理员删除只作用于后台 `system_users`。
|
||||
|
||||
**Q:测试环境最快怎么干?**
|
||||
停服务 → 备份 → 按第 4~7 节 TRUNCATE/DELETE + `FLUSHDB` → 启服务。
|
||||
|
||||
---
|
||||
|
||||
## 12. 变更记录
|
||||
|
||||
| 日期 | 说明 |
|
||||
|------|------|
|
||||
| 2026-08-12 | 初版:基于当前 `friend_back` + `chat-im` 配置与表结构整理 |
|
||||
@@ -16,17 +16,8 @@
|
||||
<module>yudao-module-system</module>
|
||||
<module>yudao-module-infra</module>
|
||||
<module>yudao-module-member</module>
|
||||
<!-- <module>yudao-module-bpm</module>-->
|
||||
<!-- <module>yudao-module-report</module>-->
|
||||
<!-- <module>yudao-module-mp</module>-->
|
||||
<module>yudao-module-pay</module>
|
||||
<module>yudao-module-mall</module>
|
||||
<!-- <module>yudao-module-crm</module>-->
|
||||
<!-- <module>yudao-module-erp</module>-->
|
||||
<!-- <module>yudao-module-iot</module>-->
|
||||
<!-- AI 大模型的开启,请参考 https://doc.iocoder.cn/ai/build/ 文档,对 JDK 版本要要求! -->
|
||||
<!-- <module>yudao-module-ai</module>-->
|
||||
<!-- <module>yudao-module-iot</module>-->
|
||||
</modules>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC1K3mUuyaWg6eE
|
||||
Ud2Hw0lrUvvpT/JBZf5LZU/LJeCnv/1M1xs6XKBDSIPT5Jd3zbzCtWR4ejrsQu1y
|
||||
wsX/tJ15DWbKS/ESzAqEAbtIKNqhyr1LAydKt7uIeeu2c0pFEfxKjN2zruxEk94/
|
||||
Pyjxb9mvpcp4S5K29ncTqfYcPtSDjnmojIUwMmdjue1vcARacZ2FewX02qLbbK6o
|
||||
mstA4ykFa7+lMWGkI+vYQJ0x26Ubj3Un6ydH9Rglm7tGjrk7gVvd3gRUL0QcofZx
|
||||
AMsCRVZx4WWJi+KGu61i8GSD1rKC3kVpBmJSt+J7SJXLkW9kuQ7ee/5+b2U9Mo3J
|
||||
p09vbqQPAgMBAAECggEAHQndYH4I5touWRKoH/EozPeNoi1qxpm7A0m3Wvf0xN6M
|
||||
kqrt1dJv66jtXGLI9m53IXxioL7yv9jQZ3MZum0wSm1f+u/mMZ2b1+HRK3VE6ebt
|
||||
pV7ZhuvhNrRYGDPthRmnZymiBYF1pF5RIrEUdnJ3oWRBCjwOxZ3Go1O8O3XEqVtB
|
||||
UKPpBgoUQqzFzyeck3HKNCZH62eIdTUQnyp7cU6wyj/9NwOerF7RbRRRFuayPzdP
|
||||
uHJVcye7/qX/4z8GlNLklP1QOXBg4Qj8x48doR8OaFwSrhoDjcjVQ+mdmoTPGcgL
|
||||
DtZSnTiWtY3Z8czovKIFLdtF6Rg0L8P+2ovBnlwMDQKBgQDaFxlnfVXu30SJJSwX
|
||||
6FgIdkRg/VjyuqAVo7T30qmRWENiyhfJ0MhMiiy57tkT0MwQOk3M/+Cp0ah4RTJS
|
||||
OanEr6kttTd3FzjVlbjNmJML6PjLw0UCGxBbdKLVqiqiGfz86MTDn5c+NluIMJ8o
|
||||
ZqQT6algUuDbJ9K5hfsNk8bnuwKBgQDUqW/byjSl7Ph+SHOfji2THHvLRdKHQL+W
|
||||
ijbOc7FiIsZuQZ5YyqIq7Rbed7dq38Nb9zo9hk4vd5FgcFvI/hPZUy3TngPYJfwv
|
||||
O+mvAdZGBCBY5V5waywhD+BRGrT7FVJ89iCQANPzCx4ficsKNiC7QTzUiCM3jbMD
|
||||
1aZIUDo9vQKBgQCiAtjnbU9iFVAHhaq80+hZ2OmxF2N1Zbg4exWbqE45X9UnVTPY
|
||||
w5WYbmkNcual9s0xHO7g5BDUdqBtQBJCzP1V1JycDOv4NzMMT54qdTZ/eYEhdZ0V
|
||||
FF/RYNL6YOAs7Ztnkc9h93NWTvEuOdhyHdWzyTf/C3Jou6uqEGLQ0j/HswKBgHC2
|
||||
SRoVxue2fFH6NoX3hkI1U4t+O0ICWEkCgl/Tw80+nysqzjFDXMYhmQBaNTWI2XMx
|
||||
yoV3XJMd7UhO5ObXe0+xHVXOpzz1jP6/43AlKhs77qUPFInC08XGM17pJ8j4GRsw
|
||||
HDwWpD9pwhJ3Js0HuTtmLUZ7zSaRjLLYluQ22VeFAoGBAKz9Yd7/FfiaJ3RCG585
|
||||
E4WJg+BoO7uBw1xuTjKabt6gxTD3TcSAmnQEZrKSlmzxoF7ypdOUymJU46TzN5KD
|
||||
1MBkLADN+mr2Xk1TtawORFZk0KfZsXuVDwtSMXrmq5nj1Ul8noq8Azz8qTxxcuW4
|
||||
pdry0NF+Wfjny8ATyFDKY08w
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC1K3mUuyaWg6eEUd2Hw0lrUvvpT/JBZf5LZU/LJeCnv/1M1xs6XKBDSIPT5Jd3zbzCtWR4ejrsQu1ywsX/tJ15DWbKS/ESzAqEAbtIKNqhyr1LAydKt7uIeeu2c0pFEfxKjN2zruxEk94/Pyjxb9mvpcp4S5K29ncTqfYcPtSDjnmojIUwMmdjue1vcARacZ2FewX02qLbbK6omstA4ykFa7+lMWGkI+vYQJ0x26Ubj3Un6ydH9Rglm7tGjrk7gVvd3gRUL0QcofZxAMsCRVZx4WWJi+KGu61i8GSD1rKC3kVpBmJSt+J7SJXLkW9kuQ7ee/5+b2U9Mo3Jp09vbqQPAgMBAAECggEAHQndYH4I5touWRKoH/EozPeNoi1qxpm7A0m3Wvf0xN6Mkqrt1dJv66jtXGLI9m53IXxioL7yv9jQZ3MZum0wSm1f+u/mMZ2b1+HRK3VE6ebtpV7ZhuvhNrRYGDPthRmnZymiBYF1pF5RIrEUdnJ3oWRBCjwOxZ3Go1O8O3XEqVtBUKPpBgoUQqzFzyeck3HKNCZH62eIdTUQnyp7cU6wyj/9NwOerF7RbRRRFuayPzdPuHJVcye7/qX/4z8GlNLklP1QOXBg4Qj8x48doR8OaFwSrhoDjcjVQ+mdmoTPGcgLDtZSnTiWtY3Z8czovKIFLdtF6Rg0L8P+2ovBnlwMDQKBgQDaFxlnfVXu30SJJSwX6FgIdkRg/VjyuqAVo7T30qmRWENiyhfJ0MhMiiy57tkT0MwQOk3M/+Cp0ah4RTJSOanEr6kttTd3FzjVlbjNmJML6PjLw0UCGxBbdKLVqiqiGfz86MTDn5c+NluIMJ8oZqQT6algUuDbJ9K5hfsNk8bnuwKBgQDUqW/byjSl7Ph+SHOfji2THHvLRdKHQL+WijbOc7FiIsZuQZ5YyqIq7Rbed7dq38Nb9zo9hk4vd5FgcFvI/hPZUy3TngPYJfwvO+mvAdZGBCBY5V5waywhD+BRGrT7FVJ89iCQANPzCx4ficsKNiC7QTzUiCM3jbMD1aZIUDo9vQKBgQCiAtjnbU9iFVAHhaq80+hZ2OmxF2N1Zbg4exWbqE45X9UnVTPYw5WYbmkNcual9s0xHO7g5BDUdqBtQBJCzP1V1JycDOv4NzMMT54qdTZ/eYEhdZ0VFF/RYNL6YOAs7Ztnkc9h93NWTvEuOdhyHdWzyTf/C3Jou6uqEGLQ0j/HswKBgHC2SRoVxue2fFH6NoX3hkI1U4t+O0ICWEkCgl/Tw80+nysqzjFDXMYhmQBaNTWI2XMxyoV3XJMd7UhO5ObXe0+xHVXOpzz1jP6/43AlKhs77qUPFInC08XGM17pJ8j4GRswHDwWpD9pwhJ3Js0HuTtmLUZ7zSaRjLLYluQ22VeFAoGBAKz9Yd7/FfiaJ3RCG585E4WJg+BoO7uBw1xuTjKabt6gxTD3TcSAmnQEZrKSlmzxoF7ypdOUymJU46TzN5KD1MBkLADN+mr2Xk1TtawORFZk0KfZsXuVDwtSMXrmq5nj1Ul8noq8Azz8qTxxcuW4pdry0NF+Wfjny8ATyFDKY08w
|
||||
@@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtSt5lLsmloOnhFHdh8NJ
|
||||
a1L76U/yQWX+S2VPyyXgp7/9TNcbOlygQ0iD0+SXd828wrVkeHo67ELtcsLF/7Sd
|
||||
eQ1mykvxEswKhAG7SCjaocq9SwMnSre7iHnrtnNKRRH8Sozds67sRJPePz8o8W/Z
|
||||
r6XKeEuStvZ3E6n2HD7Ug455qIyFMDJnY7ntb3AEWnGdhXsF9Nqi22yuqJrLQOMp
|
||||
BWu/pTFhpCPr2ECdMdulG491J+snR/UYJZu7Ro65O4Fb3d4EVC9EHKH2cQDLAkVW
|
||||
ceFliYvihrutYvBkg9aygt5FaQZiUrfie0iVy5FvZLkO3nv+fm9lPTKNyadPb26k
|
||||
DwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtSt5lLsmloOnhFHdh8NJa1L76U/yQWX+S2VPyyXgp7/9TNcbOlygQ0iD0+SXd828wrVkeHo67ELtcsLF/7SdeQ1mykvxEswKhAG7SCjaocq9SwMnSre7iHnrtnNKRRH8Sozds67sRJPePz8o8W/Zr6XKeEuStvZ3E6n2HD7Ug455qIyFMDJnY7ntb3AEWnGdhXsF9Nqi22yuqJrLQOMpBWu/pTFhpCPr2ECdMdulG491J+snR/UYJZu7Ro65O4Fb3d4EVC9EHKH2cQDLAkVWceFliYvihrutYvBkg9aygt5FaQZiUrfie0iVy5FvZLkO3nv+fm9lPTKNyadPb26kDwIDAQAB
|
||||
@@ -0,0 +1,64 @@
|
||||
-- 航空账号(管理员开通:手机号+密码+有效期+设备唯一绑定)
|
||||
CREATE TABLE IF NOT EXISTS `member_airline_account` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||
`mobile` varchar(11) NOT NULL COMMENT '手机号(同一手机号可在不同渠道各有一个账号)',
|
||||
`channel` varchar(8) NOT NULL DEFAULT 'CZ' COMMENT '品牌渠道(CZ南航/MU东航/HU海航)',
|
||||
`password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码(BCrypt)',
|
||||
`start_time` datetime NOT NULL COMMENT '生效开始时间',
|
||||
`end_time` datetime NOT NULL COMMENT '生效结束时间',
|
||||
`device_id` varchar(128) NULL DEFAULT NULL COMMENT '手机唯一识别号(首次登录绑定;重装/重启应由客户端保持不变)',
|
||||
`status` tinyint NOT NULL DEFAULT 0 COMMENT '状态(0开启 1关闭)',
|
||||
`remark` varchar(500) NULL DEFAULT NULL COMMENT '备注',
|
||||
`user_id` bigint NULL DEFAULT NULL COMMENT '关联会员用户编号 member_user.id(已废弃,登录不再创建会员用户)',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
`creator` varchar(64) NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` varchar(64) NULL DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_mobile_channel` (`mobile`, `channel`, `deleted`, `tenant_id`) USING BTREE,
|
||||
UNIQUE KEY `uk_channel_device` (`channel`, `device_id`, `deleted`, `tenant_id`) USING BTREE,
|
||||
KEY `idx_end_time` (`end_time`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='航空账号';
|
||||
|
||||
-- 菜单:航空账号管理(挂在会员中心 2262 下)
|
||||
-- 若 id 冲突,请在管理后台「菜单管理」手动新建,或调整 id
|
||||
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
|
||||
VALUES (5800, '航空账号', '', 2, 15, 2262, 'airline-account', 'ep:iphone', 'member/airlineAccount/index', 'MemberAirlineAccount', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0')
|
||||
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `component` = VALUES(`component`);
|
||||
|
||||
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
|
||||
VALUES
|
||||
(5801, '航空账号查询', 'member:airline-account:query', 3, 1, 5800, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
|
||||
(5802, '航空账号创建', 'member:airline-account:create', 3, 2, 5800, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
|
||||
(5803, '航空账号更新', 'member:airline-account:update', 3, 3, 5800, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
|
||||
(5804, '航空账号删除', 'member:airline-account:delete', 3, 4, 5800, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
|
||||
(5805, '航空账号解绑设备', 'member:airline-account:unbind', 3, 5, 5800, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0')
|
||||
ON DUPLICATE KEY UPDATE `permission` = VALUES(`permission`);
|
||||
|
||||
-- 测试账号(App 登录)
|
||||
-- 手机号:13800138000 密码:123456 有效期约一年 未绑定设备
|
||||
-- 密码哈希为 BCrypt(123456)
|
||||
INSERT INTO `member_airline_account`
|
||||
(`mobile`, `channel`, `password`, `start_time`, `end_time`, `device_id`, `status`, `remark`, `user_id`, `tenant_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
|
||||
SELECT '13800138000',
|
||||
'CZ',
|
||||
'$2a$10$PplOV8yXcQo9xc32V8vwTuIMbMKgNtjXf3lu6l18LUTOy9YGDdAY6',
|
||||
NOW() - INTERVAL 1 DAY,
|
||||
NOW() + INTERVAL 365 DAY,
|
||||
NULL,
|
||||
0,
|
||||
'本地联调测试账号',
|
||||
NULL,
|
||||
1,
|
||||
'1',
|
||||
NOW(),
|
||||
'1',
|
||||
NOW(),
|
||||
b'0'
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `member_airline_account`
|
||||
WHERE `mobile` = '13800138000' AND `channel` = 'CZ' AND `deleted` = b'0' AND `tenant_id` = 1
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
-- 航空账号增加品牌渠道(channel):CZ 南航 / MU 东航 / HU 海航
|
||||
-- 目的:三个 App 使用同一手机号分别登录,互不干扰。
|
||||
-- 唯一键调整:
|
||||
-- uk_mobile (mobile, deleted, tenant_id) -> 废弃
|
||||
-- uk_device_id (device_id, deleted, tenant_id) -> 废弃
|
||||
-- uk_mobile_channel (mobile, channel, deleted, tenant_id) -> 同一手机号每渠道一个账号
|
||||
-- uk_channel_device (channel, device_id, deleted, tenant_id) -> 同一设备每渠道绑定一个账号
|
||||
|
||||
ALTER TABLE `member_airline_account`
|
||||
ADD COLUMN `channel` varchar(8) NOT NULL DEFAULT 'CZ' COMMENT '品牌渠道(CZ南航/MU东航/HU海航)' AFTER `mobile`;
|
||||
|
||||
-- 存量数据归属渠道默认回填为 CZ(南航),如实际属于东航/海航请手工执行:
|
||||
-- UPDATE `member_airline_account` SET `channel` = 'MU' WHERE ...;
|
||||
-- UPDATE `member_airline_account` SET `channel` = 'HU' WHERE ...;
|
||||
|
||||
ALTER TABLE `member_airline_account`
|
||||
DROP INDEX `uk_mobile`,
|
||||
DROP INDEX `uk_device_id`,
|
||||
ADD UNIQUE KEY `uk_mobile_channel` (`mobile`, `channel`, `deleted`, `tenant_id`),
|
||||
ADD UNIQUE KEY `uk_channel_device` (`channel`, `device_id`, `deleted`, `tenant_id`);
|
||||
@@ -0,0 +1,64 @@
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 航空 App 数据云同步:新增航线 / 我的(积点钱包)
|
||||
-- 数据归属:member_airline_account.id(登录账号),一账号一套数据
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
-- 用户自建的飞行任务(航线)
|
||||
CREATE TABLE IF NOT EXISTS `member_airline_flight` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||
`account_id` bigint NOT NULL COMMENT '航空账号编号 member_airline_account.id',
|
||||
`flight_no` varchar(32) NULL DEFAULT '' COMMENT '航班号',
|
||||
`title` varchar(64) NULL DEFAULT '' COMMENT '任务/航班名称',
|
||||
`begin_city` varchar(64) NULL DEFAULT '' COMMENT '出发城市',
|
||||
`begin_airport` varchar(128) NULL DEFAULT '' COMMENT '出发机场',
|
||||
`end_city` varchar(64) NULL DEFAULT '' COMMENT '到达城市',
|
||||
`end_airport` varchar(128) NULL DEFAULT '' COMMENT '到达机场',
|
||||
`classes` varchar(128) NULL DEFAULT '' COMMENT '舱位/班次',
|
||||
`status` varchar(32) NULL DEFAULT '' COMMENT '状态',
|
||||
`fly_begin_date0` varchar(32) NULL DEFAULT '' COMMENT '计划起飞时间',
|
||||
`fly_end_date0` varchar(32) NULL DEFAULT '' COMMENT '计划到达时间',
|
||||
`fly_begin_date1` varchar(32) NULL DEFAULT '' COMMENT '实际起飞时间',
|
||||
`fly_end_date1` varchar(32) NULL DEFAULT '' COMMENT '实际到达时间',
|
||||
`sign_in_time` varchar(32) NULL DEFAULT '' COMMENT '签到时间',
|
||||
`aircraft_no` varchar(32) NULL DEFAULT '' COMMENT '机号/编号',
|
||||
`airline` varchar(64) NULL DEFAULT '' COMMENT '航司',
|
||||
`aircraft_type` varchar(64) NULL DEFAULT '' COMMENT '机型',
|
||||
`gate` varchar(32) NULL DEFAULT '' COMMENT '登机口',
|
||||
`begin_stand` varchar(32) NULL DEFAULT '' COMMENT '出发机场停机位',
|
||||
`end_stand` varchar(32) NULL DEFAULT '' COMMENT '到达机场停机位',
|
||||
`cargo_close` varchar(32) NULL DEFAULT '' COMMENT '货关时间',
|
||||
`pax_close` varchar(32) NULL DEFAULT '' COMMENT '客关时间',
|
||||
`leave_port` varchar(32) NULL DEFAULT '' COMMENT '离港时间',
|
||||
`take_off` varchar(32) NULL DEFAULT '' COMMENT '起飞时间',
|
||||
`land` varchar(32) NULL DEFAULT '' COMMENT '落地时间',
|
||||
`arrive_port` varchar(32) NULL DEFAULT '' COMMENT '到港时间',
|
||||
`prev_flight` varchar(255) NULL DEFAULT '' COMMENT '前序航班',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
`creator` varchar(64) NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` varchar(64) NULL DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_account_id` (`account_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='航空App用户自建航线';
|
||||
|
||||
-- 我的页面:积点/钱包(每账号一行)
|
||||
CREATE TABLE IF NOT EXISTS `member_airline_point` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||
`account_id` bigint NOT NULL COMMENT '航空账号编号 member_airline_account.id',
|
||||
`current_month` varchar(16) NULL DEFAULT '00' COMMENT '当月飞行',
|
||||
`current_year` varchar(16) NULL DEFAULT '00' COMMENT '年度飞行',
|
||||
`jixiao` varchar(16) NULL DEFAULT '00' COMMENT '绩效积点',
|
||||
`yanwu` varchar(16) NULL DEFAULT '00' COMMENT '延误积点',
|
||||
`current_point` varchar(16) NULL DEFAULT '00' COMMENT '当前积点',
|
||||
`wallet` varchar(16) NULL DEFAULT '00' COMMENT '钱包余额',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
`creator` varchar(64) NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` varchar(64) NULL DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_account_id` (`account_id`, `deleted`, `tenant_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='航空App我的积点钱包';
|
||||
@@ -0,0 +1,21 @@
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- member_airline_flight 补齐航线详情字段(登机口/货关/客关/停机位/离港/起飞/落地/到港等)
|
||||
-- 背景:早期云同步表只建了 15 个基础字段,客户端上报的详情字段被服务端丢弃,
|
||||
-- 拉取后详情页登机口、货关、客关等为空。本脚本为存量库补列。
|
||||
-- 已执行过本脚本的库无需重复执行(列已存在会报 Duplicate column)。
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
ALTER TABLE `member_airline_flight`
|
||||
ADD COLUMN `aircraft_no` varchar(32) NULL DEFAULT '' COMMENT '机号/编号' AFTER `sign_in_time`,
|
||||
ADD COLUMN `airline` varchar(64) NULL DEFAULT '' COMMENT '航司' AFTER `aircraft_no`,
|
||||
ADD COLUMN `aircraft_type` varchar(64) NULL DEFAULT '' COMMENT '机型' AFTER `airline`,
|
||||
ADD COLUMN `gate` varchar(32) NULL DEFAULT '' COMMENT '登机口' AFTER `aircraft_type`,
|
||||
ADD COLUMN `begin_stand` varchar(32) NULL DEFAULT '' COMMENT '出发机场停机位' AFTER `gate`,
|
||||
ADD COLUMN `end_stand` varchar(32) NULL DEFAULT '' COMMENT '到达机场停机位' AFTER `begin_stand`,
|
||||
ADD COLUMN `cargo_close` varchar(32) NULL DEFAULT '' COMMENT '货关时间' AFTER `end_stand`,
|
||||
ADD COLUMN `pax_close` varchar(32) NULL DEFAULT '' COMMENT '客关时间' AFTER `cargo_close`,
|
||||
ADD COLUMN `leave_port` varchar(32) NULL DEFAULT '' COMMENT '离港时间' AFTER `pax_close`,
|
||||
ADD COLUMN `take_off` varchar(32) NULL DEFAULT '' COMMENT '起飞时间' AFTER `leave_port`,
|
||||
ADD COLUMN `land` varchar(32) NULL DEFAULT '' COMMENT '落地时间' AFTER `take_off`,
|
||||
ADD COLUMN `arrive_port` varchar(32) NULL DEFAULT '' COMMENT '到港时间' AFTER `land`,
|
||||
ADD COLUMN `prev_flight` varchar(255) NULL DEFAULT '' COMMENT '前序航班' AFTER `arrive_port`;
|
||||
@@ -0,0 +1,18 @@
|
||||
-- 会员互动消息(被查看 / 微信被解锁)
|
||||
CREATE TABLE IF NOT EXISTS `member_interaction` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||
`receiver_id` bigint NOT NULL COMMENT '接收人(被查看 / 被解锁用户)',
|
||||
`actor_id` bigint NOT NULL COMMENT '操作人',
|
||||
`biz_type` varchar(32) NOT NULL COMMENT 'VIEWED / WECHAT_UNLOCKED',
|
||||
`ref_id` bigint NULL DEFAULT NULL COMMENT '关联业务 id(访客记录 / 操作记录)',
|
||||
`read_status` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已读',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
`creator` varchar(64) NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` varchar(64) NULL DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_receiver_read` (`receiver_id`, `read_status`) USING BTREE,
|
||||
KEY `idx_receiver_time` (`receiver_id`, `id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='会员互动消息';
|
||||
@@ -0,0 +1,18 @@
|
||||
-- 会员推送设备(极光 RegistrationID 绑定)
|
||||
CREATE TABLE IF NOT EXISTS `member_push_device` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||
`member_id` bigint NOT NULL COMMENT '会员编号',
|
||||
`registration_id` varchar(64) NOT NULL COMMENT '极光 RegistrationID',
|
||||
`alias` varchar(64) NULL DEFAULT NULL COMMENT '极光别名,如 u{memberId}',
|
||||
`platform` varchar(16) NOT NULL COMMENT 'ios / android',
|
||||
`provider` varchar(16) NOT NULL DEFAULT 'jpush' COMMENT '推送服务商',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
`creator` varchar(64) NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` varchar(64) NULL DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_registration_id` (`registration_id`) USING BTREE,
|
||||
KEY `idx_member_id` (`member_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='会员推送设备';
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""骑遇骑行记录:建表 + 模拟数据(chat_v1 库)。可重复执行(幂等:表已存在跳过,模拟数据按标题去重)。"""
|
||||
import json
|
||||
import math
|
||||
import pymysql
|
||||
|
||||
DB = dict(host='139.224.25.207', port=3306, user='chat_v1',
|
||||
password='BpJ2Ebt4zMPjBcCL', database='chat_v1', charset='utf8mb4',
|
||||
connect_timeout=15)
|
||||
|
||||
DDL = [
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS `member_ride_record` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||
`user_id` bigint(20) NOT NULL COMMENT '骑手编号 member_user.id',
|
||||
`title` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '战报标题',
|
||||
`road_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '路线名(如:南昆山)',
|
||||
`corner_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '弯道名(如:观音潭右弯),空表示非跑山记录',
|
||||
`distance_km` decimal(8,1) NOT NULL COMMENT '里程(公里)',
|
||||
`duration_sec` int(11) NOT NULL COMMENT '骑行时长(秒)',
|
||||
`top_speed_kmh` decimal(5,1) NOT NULL COMMENT '极速(km/h)',
|
||||
`curve_count` int(11) NOT NULL DEFAULT '0' COMMENT '过弯次数',
|
||||
`max_lean` decimal(4,1) NOT NULL COMMENT '最大压弯角度(度)',
|
||||
`track_json` mediumtext COLLATE utf8mb4_unicode_ci COMMENT '轨迹点 JSON [{lat,lng,speed}],仅详情接口返回',
|
||||
`like_count` int(11) NOT NULL DEFAULT '0' COMMENT '点赞数(冗余计数)',
|
||||
`report_count` int(11) NOT NULL DEFAULT '0' COMMENT '被举报次数',
|
||||
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0 正常 1 已下架',
|
||||
`tenant_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '租户编号',
|
||||
`creator` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user` (`user_id`),
|
||||
KEY `idx_rank` (`road_name`, `corner_name`, `max_lean`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='骑遇骑行记录(战报)'
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS `member_ride_record_like` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||
`record_id` bigint(20) NOT NULL COMMENT '战报编号 member_ride_record.id',
|
||||
`user_id` bigint(20) NOT NULL COMMENT '点赞人编号 member_user.id',
|
||||
`tenant_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '租户编号',
|
||||
`creator` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_record_user` (`record_id`, `user_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='骑遇骑行战报点赞'
|
||||
""",
|
||||
]
|
||||
|
||||
# (user_id, title, road, corner, km, sec, top, curves, max_lean, days_ago, like_user_ids)
|
||||
MOCK = [
|
||||
(131, '南昆山 63 连弯,今天左弯终于不虚了', '南昆山', '观音潭右弯', 87, 5760, 138, 63, 52.0, 1, [133, 132, 134]),
|
||||
(133, '清晨跑山,观音潭右弯刷出个人最佳', '南昆山', '观音潭右弯', 84, 5400, 132, 58, 49.5, 2, [131, 129]),
|
||||
(129, '压弯一时爽,一直压弯一直爽', '南昆山', '观音潭右弯', 86, 5900, 129, 61, 46.0, 4, [131]),
|
||||
(132, '第一次跑南昆山,右弯还是不敢深压', '南昆山', '观音潭右弯', 88, 6300, 112, 57, 33.5, 5, [134]),
|
||||
(131, '连续 S 弯练车,大腿已废', '南昆山', '连续 S 弯', 52, 3900, 118, 41, 44.5, 1, [133, 129]),
|
||||
(133, 'S 弯 41 连过,人车合一', '南昆山', '连续 S 弯', 52, 3950, 112, 41, 40.0, 3, [131, 134, 132]),
|
||||
(129, '大夫山夜骑放风,发卡弯有点意思', '大夫山', '发卡弯', 44, 3660, 105, 38, 43.0, 1, [131, 133]),
|
||||
(131, '大夫山发卡弯,极速压到 43 度', '大夫山', '发卡弯', 43, 3500, 108, 35, 43.2, 3, [133]),
|
||||
(134, '傍晚放风,夕阳配弯道,舒服', '大夫山', '发卡弯', 44, 3800, 98, 36, 29.0, 4, [132]),
|
||||
(131, '罗浮山风门坳长弯,全程 6 档', '罗浮山', '风门坳长弯', 121, 7680, 121, 22, 39.0, 2, [133, 129]),
|
||||
(133, '风门坳长弯磨膝体验,就一次', '罗浮山', '风门坳长弯', 118, 7500, 126, 20, 47.5, 6, [131]),
|
||||
(129, '周末摩旅,156 公里赶到营地', '', '', 156, 7680, 121, 22, 25.0, 3, [131, 133, 132, 134]),
|
||||
(133, '市区通勤顺路兜风,绿道休闲骑', '', '', 32, 3000, 85, 12, 18.5, 1, [131]),
|
||||
(132, '新手第一份战报,求带飞', '', '', 26, 2700, 76, 9, 15.0, 2, [134]),
|
||||
]
|
||||
|
||||
|
||||
def track_points(seed: int) -> str:
|
||||
"""生成一个小环形轨迹 JSON(约 60 点),让战报详情有图可画。"""
|
||||
r = 0.004 + (seed % 7) * 0.0004
|
||||
pts = []
|
||||
n = 60
|
||||
for i in range(n):
|
||||
t = i / n * math.pi * 4
|
||||
radius = r + r * 0.35 * math.sin(t * 2.3 + seed)
|
||||
lat = 23.17 + seed * 0.002 + radius * math.cos(t)
|
||||
lng = 113.36 + seed * 0.003 + radius * 1.4 * math.sin(t)
|
||||
speed = round(40 + 60 * (0.5 + 0.5 * math.sin(t * 1.7 + seed)), 1)
|
||||
pts.append({'lat': round(lat, 6), 'lng': round(lng, 6), 'speed': speed})
|
||||
return json.dumps(pts, separators=(',', ':'))
|
||||
|
||||
|
||||
def main():
|
||||
conn = pymysql.connect(**DB)
|
||||
cur = conn.cursor()
|
||||
|
||||
for ddl in DDL:
|
||||
cur.execute(ddl)
|
||||
conn.commit()
|
||||
print('DDL ok')
|
||||
|
||||
inserted = 0
|
||||
for idx, (uid, title, road, corner, km, sec, top, curves, lean, days_ago, likers) in enumerate(MOCK):
|
||||
cur.execute("SELECT id FROM member_ride_record WHERE title=%s AND deleted=0", (title,))
|
||||
if cur.fetchone():
|
||||
print('skip (exists):', title)
|
||||
continue
|
||||
cur.execute(
|
||||
"""INSERT INTO member_ride_record
|
||||
(user_id, title, road_name, corner_name, distance_km, duration_sec,
|
||||
top_speed_kmh, curve_count, max_lean, track_json, like_count, report_count,
|
||||
status, tenant_id, creator, create_time)
|
||||
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,0,0,1,%s,
|
||||
DATE_SUB(NOW(), INTERVAL %s DAY) - INTERVAL %s HOUR)""",
|
||||
(uid, title, road, corner, km, sec, top, curves, lean,
|
||||
track_points(idx), len(likers), uid, days_ago, (idx * 3) % 20))
|
||||
record_id = cur.lastrowid
|
||||
for liker in likers:
|
||||
cur.execute(
|
||||
"""INSERT IGNORE INTO member_ride_record_like
|
||||
(record_id, user_id, tenant_id, creator, create_time)
|
||||
VALUES (%s,%s,1,%s, DATE_SUB(NOW(), INTERVAL 1 DAY))""",
|
||||
(record_id, liker, uid))
|
||||
inserted += 1
|
||||
print('inserted:', title)
|
||||
conn.commit()
|
||||
|
||||
cur.execute("SELECT COUNT(*) FROM member_ride_record WHERE deleted=0")
|
||||
print('total records:', cur.fetchone()[0])
|
||||
cur.execute("SELECT road_name, corner_name, COUNT(*) FROM member_ride_record "
|
||||
"WHERE deleted=0 AND corner_name<>'' GROUP BY road_name, corner_name")
|
||||
for r in cur.fetchall():
|
||||
print('board:', r)
|
||||
conn.close()
|
||||
print('done, inserted =', inserted)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,20 @@
|
||||
-- Banner 位置字典:补「动态页面」= 6(App 动态顶部轮播)
|
||||
-- 若库中已有 value=6,执行会因业务唯一性报错时可忽略;或先查:
|
||||
-- SELECT * FROM system_dict_data WHERE dict_type='promotion_banner_position' AND value='6';
|
||||
|
||||
INSERT INTO `system_dict_data` (
|
||||
`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`,
|
||||
`remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`
|
||||
)
|
||||
SELECT 0, '动态页面', '6', 'promotion_banner_position', 0, 'primary', '',
|
||||
'App 动态页 Banner', '1', NOW(), '1', NOW(), b'0'
|
||||
FROM (SELECT 1 AS _) AS t
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `system_dict_data`
|
||||
WHERE `dict_type` = 'promotion_banner_position' AND `value` = '6' AND `deleted` = b'0'
|
||||
);
|
||||
|
||||
-- 确保 Banner 菜单可见:营销中心 → 内容管理 → Banner
|
||||
UPDATE `system_menu`
|
||||
SET `status` = 0, `visible` = b'1', `deleted` = b'0', `update_time` = NOW()
|
||||
WHERE `id` IN (2030, 2387, 2025);
|
||||
@@ -0,0 +1,17 @@
|
||||
-- 后台菜单与授权:子账号管理 + 会员管理拆分(男号/女号)
|
||||
-- 执行库:chat_v1
|
||||
-- 1) 新增菜单(幂等:先删后插)
|
||||
DELETE FROM system_menu WHERE id IN (5810, 5820, 5821);
|
||||
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, updater, deleted)
|
||||
VALUES
|
||||
(5810, '子账号管理', 'system:sub-account:manage', 2, 20, 0, '/sub-account', 'ep:user', 'system/subaccount/index', 'SystemSubAccount', 0, b'1', b'1', b'1', '1', '1', b'0'),
|
||||
(5820, '男号管理', 'member:user:query', 2, 1, 2262, 'male-user', 'ep:male', 'member/user/male', 'MemberUserMale', 0, b'1', b'1', b'1', '1', '1', b'0'),
|
||||
(5821, '女号管理', 'member:user:query', 2, 2, 2262, 'female-user', 'ep:female', 'member/user/female', 'MemberUserFemale', 0, b'1', b'1', b'1', '1', '1', b'0');
|
||||
|
||||
-- 2) 授权给「APP管理员」角色(AppHolder 当前使用的角色)
|
||||
DELETE FROM system_role_menu WHERE role_id = 158 AND menu_id IN (5810, 5820, 5821);
|
||||
INSERT INTO system_role_menu (role_id, menu_id, creator, updater, deleted, tenant_id)
|
||||
VALUES
|
||||
(158, 5810, '1', '1', b'0', 1),
|
||||
(158, 5820, '1', '1', b'0', 1),
|
||||
(158, 5821, '1', '1', b'0', 1);
|
||||
@@ -0,0 +1,22 @@
|
||||
-- ----------------------------
|
||||
-- 航空账号专用 OAuth2 客户端
|
||||
-- 用途:airline-login 签发的 Token 归属 airline 客户端,access/refresh token 有效期极长,
|
||||
-- App 端登录一次即可长期使用,无需续期;每次打开 App 仅通过 /airline-check
|
||||
-- 校验账号状态/有效期/设备绑定(见 MemberAuthServiceImpl#createTokenAfterAirlineLoginSuccess)。
|
||||
-- 注意:不影响 default 客户端(交友 App 会员仍为 30 分钟 accessToken + 30 天 refreshToken)。
|
||||
-- 部署:执行本脚本后再部署新 yudao-server.jar,否则 airline-login 会报"客户端不存在"。
|
||||
-- ----------------------------
|
||||
|
||||
INSERT INTO `system_oauth2_client`
|
||||
(`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`,
|
||||
`access_token_validity_seconds`, `refresh_token_validity_seconds`,
|
||||
`redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`,
|
||||
`authorities`, `resource_ids`, `additional_information`,
|
||||
`creator`, `create_time`, `updater`, `update_time`, `deleted`)
|
||||
SELECT 43, 'airline', 'airline123', '航空App专用客户端', '', '南航/东航/海航 App 专用,Token 长期有效', 0,
|
||||
2147483647, 2147483647,
|
||||
'[]', '["password","refresh_token"]', '[]', '[]',
|
||||
'[]', '[]', '{}',
|
||||
'1', NOW(), '1', NOW(), b'0'
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `system_oauth2_client` WHERE `client_id` = 'airline' AND `deleted` = b'0');
|
||||
@@ -0,0 +1,17 @@
|
||||
-- 子账号管理:holder 与 子账号/专属角色 的绑定关系
|
||||
-- 执行位置:chat_v1 库
|
||||
CREATE TABLE IF NOT EXISTS `system_sub_account` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`holder_user_id` bigint NOT NULL COMMENT '管理者用户编号(system_users.id)',
|
||||
`sub_user_id` bigint NOT NULL COMMENT '子账号用户编号(system_users.id)',
|
||||
`role_id` bigint NOT NULL COMMENT '子账号专属角色编号(system_role.id)',
|
||||
`creator` varchar(64) NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` varchar(64) NULL DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_holder_user_id` (`holder_user_id`),
|
||||
KEY `idx_sub_user_id` (`sub_user_id`)
|
||||
) ENGINE = InnoDB COMMENT '后台子账号表';
|
||||
+55
-2
@@ -1,27 +1,80 @@
|
||||
package cn.iocoder.yudao.framework.common.util.json.databind;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 基于时间戳的 LocalDateTime 反序列化器
|
||||
*
|
||||
* 支持两种 JSON 写法:
|
||||
* 1. 时间戳:13 位毫秒(yudao 前端默认),兼容 10 位秒级时间戳
|
||||
* 2. 日期字符串:"yyyy-MM-dd HH:mm:ss"、"yyyy-MM-dd"、ISO-8601("yyyy-MM-dd'T'HH:mm:ss")
|
||||
*
|
||||
* 注意:原实现直接 {@code p.getValueAsLong()},传入 "yyyy-MM-dd HH:mm:ss" 字符串时
|
||||
* 会静默解析失败并返回 0,导致入库时间变成 1970-01-01 08:00:00(东八区 epoch 0)。
|
||||
*
|
||||
* @author 老五
|
||||
*/
|
||||
public class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
|
||||
|
||||
public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer();
|
||||
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
@Override
|
||||
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
// 将 Long 时间戳,转换为 LocalDateTime 对象
|
||||
return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
|
||||
// 1. 数字:直接按毫秒时间戳处理(与原行为一致)
|
||||
if (p.getCurrentToken() == JsonToken.VALUE_NUMBER_INT) {
|
||||
return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getLongValue()), ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
// 2. 字符串:先尝试时间戳,再尝试常见日期格式
|
||||
String text = p.getValueAsString();
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
text = text.trim();
|
||||
if (text.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// 2.1 纯数字:13 位毫秒、10 位秒
|
||||
if (text.matches("\\d+")) {
|
||||
long timestamp = Long.parseLong(text);
|
||||
if (text.length() <= 10) { // 秒级时间戳,转毫秒
|
||||
timestamp *= 1000;
|
||||
}
|
||||
return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
|
||||
}
|
||||
// 2.2 "yyyy-MM-dd HH:mm:ss"
|
||||
try {
|
||||
return LocalDateTime.parse(text, DATE_TIME_FORMATTER);
|
||||
} catch (Exception ignore) {
|
||||
// 继续尝试其它格式
|
||||
}
|
||||
// 2.3 "yyyy-MM-dd"(按当天 00:00:00)
|
||||
try {
|
||||
return LocalDate.parse(text, DATE_FORMATTER).atStartOfDay();
|
||||
} catch (Exception ignore) {
|
||||
// 继续尝试 ISO 格式
|
||||
}
|
||||
// 2.4 ISO-8601:"yyyy-MM-dd'T'HH:mm:ss"
|
||||
try {
|
||||
return LocalDateTime.parse(text);
|
||||
} catch (Exception ignore) {
|
||||
// 落到最后的报错
|
||||
}
|
||||
throw ctxt.weirdStringException(text, LocalDateTime.class,
|
||||
"Expected timestamp (millis or seconds) or date string (yyyy-MM-dd HH:mm:ss)");
|
||||
}
|
||||
|
||||
}
|
||||
+6
@@ -387,6 +387,12 @@ public class GlobalExceptionHandler {
|
||||
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
||||
"[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
|
||||
}
|
||||
// 7.1 会员互动消息
|
||||
if (message.contains("member_interaction")) {
|
||||
log.error("[会员互动消息表 member_interaction 未导入][请执行 friend_back/sql/mysql/member/member_interaction.sql]");
|
||||
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
||||
"会员互动消息表未导入,请执行 sql/mysql/member/member_interaction.sql");
|
||||
}
|
||||
// 8. AI 大模型
|
||||
if (message.contains("ai_")) {
|
||||
log.error("[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
|
||||
|
||||
+8
@@ -30,6 +30,14 @@ public interface TradeBrokerageRecordApi {
|
||||
*/
|
||||
void addBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, @Valid List<BrokerageAddDTO> list);
|
||||
|
||||
/**
|
||||
* 取消佣金:将佣金记录状态修改为已失效
|
||||
*
|
||||
* @param bizType 业务类型
|
||||
* @param bizId 业务编号
|
||||
*/
|
||||
void cancelBrokerage(BrokerageRecordBizTypeEnum bizType, String bizId);
|
||||
|
||||
/**
|
||||
* 微信转账
|
||||
* @param id 提现单id
|
||||
|
||||
+3
-2
@@ -16,8 +16,9 @@ import java.util.Arrays;
|
||||
public enum BrokerageRecordBizTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
ORDER(1, "获得推广佣金", "获得推广佣金 {}", true),
|
||||
WITHDRAW(2, "提现申请", "提现申请扣除佣金 {}", false),
|
||||
WITHDRAW_REJECT(3, "提现申请驳回", "提现申请驳回,返还佣金 {}", true),
|
||||
OPEN_TYPE(2, "获得会员佣金", "获得会员佣金 {}", true),
|
||||
WITHDRAW(3, "提现申请", "提现申请扣除佣金 {}", false),
|
||||
WITHDRAW_REJECT(4, "提现申请驳回", "提现申请驳回,返还佣金 {}", true),
|
||||
;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BrokerageRecordBizTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
+9
-7
@@ -18,8 +18,12 @@ public enum BrokerageWithdrawTypeEnum implements ArrayValuable<Integer> {
|
||||
WALLET(1, "钱包"),
|
||||
BANK(2, "银行卡"),
|
||||
WECHAT(3, "微信"), // 手动打款
|
||||
ALIPAY(4, "支付宝"),
|
||||
WECHAT_API(5, "微信零钱"), // 自动打款,通过微信转账 API
|
||||
ALIPAY(4, "支付宝"), // 手动打款
|
||||
/**
|
||||
* 历史命名 WECHAT_API;App 端佣金提现实际传 type=5 + 支付宝账号,运营后台手动打款。
|
||||
* 切勿再走微信转账 API。
|
||||
*/
|
||||
WECHAT_API(5, "支付宝"),
|
||||
;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BrokerageWithdrawTypeEnum::getType).toArray(Integer[]::new);
|
||||
@@ -39,13 +43,11 @@ public enum BrokerageWithdrawTypeEnum implements ArrayValuable<Integer> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否通过支付平台的 API 打款
|
||||
*
|
||||
* @param type 类型
|
||||
* @return 是否
|
||||
* 是否通过支付平台的 API 打款。
|
||||
* 当前业务均为运营手动打款,不再走微信/支付宝自动转账。
|
||||
*/
|
||||
public static boolean isApi(Integer type) {
|
||||
return WECHAT_API.getType().equals(type);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+5
@@ -63,6 +63,11 @@ public class TradeBrokerageRecordApiImpl implements TradeBrokerageRecordApi {
|
||||
brokerageRecordService.addBrokerage(userId, bizType, BeanUtils.toBean(list, BrokerageAddReqBO.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelBrokerage(BrokerageRecordBizTypeEnum bizType, String bizId) {
|
||||
brokerageRecordService.cancelBrokerage(bizType, bizId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信转账
|
||||
*
|
||||
|
||||
+44
@@ -1,10 +1,15 @@
|
||||
package cn.iocoder.yudao.module.trade.controller.admin.brokerage;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.pay.api.notify.dto.PayTransferNotifyReqDTO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawExcelVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawRejectReqVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawRespVO;
|
||||
@@ -22,9 +27,15 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
@@ -81,6 +92,39 @@ public class BrokerageWithdrawController {
|
||||
return success(BrokerageWithdrawConvert.INSTANCE.convertPage(pageResult, userMap));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出佣金提现 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:query')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportBrokerageWithdrawExcel(@Valid BrokerageWithdrawPageReqVO pageVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
PageResult<BrokerageWithdrawDO> pageResult = brokerageWithdrawService.getBrokerageWithdrawPage(pageVO);
|
||||
Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(
|
||||
convertSet(pageResult.getList(), BrokerageWithdrawDO::getUserId));
|
||||
|
||||
List<BrokerageWithdrawExcelVO> excelList = new ArrayList<>(pageResult.getList().size());
|
||||
for (BrokerageWithdrawDO row : pageResult.getList()) {
|
||||
BrokerageWithdrawExcelVO excel = new BrokerageWithdrawExcelVO();
|
||||
excel.setId(row.getId());
|
||||
excel.setUserId(row.getUserId());
|
||||
excel.setUserNickname(Optional.ofNullable(userMap.get(row.getUserId()))
|
||||
.map(MemberUserRespDTO::getNickname).orElse(null));
|
||||
excel.setPriceYuan(row.getPrice() == null ? "0" : MoneyUtils.fenToYuanStr(row.getPrice()));
|
||||
excel.setFeePriceYuan(row.getFeePrice() == null ? "0" : MoneyUtils.fenToYuanStr(row.getFeePrice()));
|
||||
excel.setTypeName("支付宝");
|
||||
excel.setAccountNo(row.getAccountNo());
|
||||
excel.setName(row.getName());
|
||||
excel.setStatus(row.getStatus());
|
||||
excel.setAuditReason(row.getAuditReason());
|
||||
excel.setAuditTime(row.getAuditTime());
|
||||
excel.setCreateTime(row.getCreateTime());
|
||||
excel.setRemark(row.getRemark());
|
||||
excelList.add(excel);
|
||||
}
|
||||
ExcelUtils.write(response, "佣金提现.xls", "数据", BrokerageWithdrawExcelVO.class, excelList);
|
||||
}
|
||||
|
||||
// TODO @luchi:update-transferred,url 改成这个。和 update-paid 、update-refunded 保持一致
|
||||
@PostMapping("/update-transfer")
|
||||
@Operation(summary = "更新转账订单为转账成功") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw;
|
||||
|
||||
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
|
||||
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
|
||||
import cn.iocoder.yudao.module.trade.enums.DictTypeConstants;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 佣金提现 Excel VO
|
||||
*/
|
||||
@Data
|
||||
public class BrokerageWithdrawExcelVO {
|
||||
|
||||
@ExcelProperty("编号")
|
||||
private Long id;
|
||||
|
||||
@ExcelProperty("用户编号")
|
||||
private Long userId;
|
||||
|
||||
@ExcelProperty("用户昵称")
|
||||
private String userNickname;
|
||||
|
||||
@ExcelProperty("提现金额(元)")
|
||||
private String priceYuan;
|
||||
|
||||
@ExcelProperty("手续费(元)")
|
||||
private String feePriceYuan;
|
||||
|
||||
@ExcelProperty("提现方式")
|
||||
private String typeName;
|
||||
|
||||
@ExcelProperty("支付宝账号")
|
||||
private String accountNo;
|
||||
|
||||
@ExcelProperty("真实姓名")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty(value = "状态", converter = DictConvert.class)
|
||||
@DictFormat(DictTypeConstants.BROKERAGE_WITHDRAW_STATUS)
|
||||
private Integer status;
|
||||
|
||||
@ExcelProperty("审核原因")
|
||||
private String auditReason;
|
||||
|
||||
@ExcelProperty("审核时间")
|
||||
private LocalDateTime auditTime;
|
||||
|
||||
@ExcelProperty("申请时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@ExcelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
}
|
||||
+9
@@ -30,6 +30,15 @@ public class AppBrokerageWithdrawRespVO {
|
||||
@Schema(description = "提现时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "审核驳回原因", example = "信息不完整")
|
||||
private String auditReason;
|
||||
|
||||
@Schema(description = "转账失败原因", example = "用户未确认收款")
|
||||
private String transferErrorMsg;
|
||||
|
||||
@Schema(description = "备注", example = "账号信息有误")
|
||||
private String remark;
|
||||
|
||||
// ========== 微信转账专属 ==========
|
||||
|
||||
@Schema(description = "转账单编号", example = "1024")
|
||||
|
||||
+12
-13
@@ -38,7 +38,8 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.BROKERAGE_C
|
||||
@Validated
|
||||
public class BrokerageCodeServiceImpl implements BrokerageCodeService {
|
||||
|
||||
private static final String POSTER_VERSION_MARKER = "#poster-v2-hd";
|
||||
/** v4:邀请链接改为 /invite.html?code=&userId=,二维码同步刷新 */
|
||||
private static final String POSTER_VERSION_MARKER = "#poster-v4-invite-html";
|
||||
|
||||
private static final String BROKERAGE_CODE_GENERATE_LOCK_KEY = "trade:brokerage-code:generate:";
|
||||
|
||||
@@ -144,17 +145,14 @@ public class BrokerageCodeServiceImpl implements BrokerageCodeService {
|
||||
if (ObjectUtil.isEmpty(currentCode) || isLatestPoster(currentCode.getPosterPic())) {
|
||||
return currentCode;
|
||||
}
|
||||
// 海报图统一印分销码 code,与手动填推荐码查表字段一致
|
||||
String posterCode = currentCode.getCode();
|
||||
String userAvatar = "";
|
||||
MemberUserRespDTO user = memberUserApi.getUser(userId);
|
||||
if (ObjectUtil.isNotEmpty(user)) {
|
||||
userAvatar = user.getAvatar();
|
||||
if (ObjectUtil.isNotEmpty(user.getUserCode())) {
|
||||
posterCode = user.getUserCode();
|
||||
}
|
||||
}
|
||||
String posterUrl = ObjectUtil.isNotEmpty(currentCode.getUrl())
|
||||
? currentCode.getUrl() : generatePosterUrl(currentCode.getCode(), userId);
|
||||
String posterUrl = generatePosterUrl(currentCode.getCode(), userId);
|
||||
String posterPic = generatePosterPic(userAvatar, posterCode, posterUrl);
|
||||
if (ObjectUtil.isEmpty(posterPic)) {
|
||||
return currentCode;
|
||||
@@ -204,19 +202,15 @@ public class BrokerageCodeServiceImpl implements BrokerageCodeService {
|
||||
isRepet = ObjectUtil.isNotEmpty(brokerageCode);
|
||||
}
|
||||
|
||||
// 查询用户获取用户头像
|
||||
// 查询用户获取用户头像;海报图印分销码 code(与绑定查表一致)
|
||||
String userAvatar = "";
|
||||
String posterCode = code;
|
||||
MemberUserRespDTO user = memberUserApi.getUser(userId);
|
||||
if (ObjectUtil.isNotEmpty(user)) {
|
||||
userAvatar = user.getAvatar();
|
||||
if (ObjectUtil.isNotEmpty(user.getUserCode())) {
|
||||
posterCode = user.getUserCode();
|
||||
}
|
||||
}
|
||||
|
||||
String url = generatePosterUrl(code, userId);
|
||||
BrokerageCodeDO saveObj = BrokerageCodeDO.builder().memberId(userId).code(code).posterPic(generatePosterPic(userAvatar, posterCode, url)).url(url).build();
|
||||
BrokerageCodeDO saveObj = BrokerageCodeDO.builder().memberId(userId).code(code).posterPic(generatePosterPic(userAvatar, code, url)).url(url).build();
|
||||
brokerageCodeMapper.insert(saveObj);
|
||||
return saveObj;
|
||||
}
|
||||
@@ -237,7 +231,12 @@ public class BrokerageCodeServiceImpl implements BrokerageCodeService {
|
||||
}
|
||||
|
||||
private String generatePosterUrl(String code, Long userId) {
|
||||
return brokerageConfiguration.getDownloadUrl() + "?code=" + code + "&userId=" + userId;
|
||||
String base = brokerageConfiguration.getDownloadUrl();
|
||||
if (base != null && base.endsWith("/")) {
|
||||
base = base.substring(0, base.length() - 1);
|
||||
}
|
||||
// 格式:http://download.htysapp.com/invite.html?code=569876&userId=90
|
||||
return base + "/invite.html?code=" + code + "&userId=" + userId;
|
||||
}
|
||||
|
||||
}
|
||||
+6
-10
@@ -79,14 +79,10 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
|
||||
public void addBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, List<BrokerageAddReqBO> list) {
|
||||
TradeConfigDO memberConfig = tradeConfigService.getTradeConfig();
|
||||
// 0 未启用分销功能
|
||||
if (memberConfig == null || !BooleanUtil.isTrue(memberConfig.getBrokerageEnabled())) {
|
||||
log.error("[addBrokerage][增加佣金失败:brokerageEnabled 未配置,userId({}) bizType({}) list({})", userId, bizType, list);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.1 获得一级推广人
|
||||
// 1.1 获得一级推广人(人人分销:只要绑定了上级即可分佣,不校验上级推广员开关)
|
||||
BrokerageUserDO firstUser = brokerageUserService.getBindBrokerageUser(userId);
|
||||
if (firstUser == null || !BooleanUtil.isTrue(firstUser.getBrokerageEnabled())) {
|
||||
if (firstUser == null) {
|
||||
return;
|
||||
}
|
||||
// 1.2 计算一级分佣
|
||||
@@ -98,7 +94,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
|
||||
return;
|
||||
}
|
||||
BrokerageUserDO secondUser = brokerageUserService.getBrokerageUser(firstUser.getBindUserId());
|
||||
if (secondUser == null || !BooleanUtil.isTrue(secondUser.getBrokerageEnabled())) {
|
||||
if (secondUser == null) {
|
||||
return;
|
||||
}
|
||||
// 2.2 计算二级分佣
|
||||
@@ -124,9 +120,9 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.1 获得一级推广人
|
||||
// 1.1 获得一级推广人(人人分销:只要绑定了上级即可分佣,不校验上级推广员开关)
|
||||
BrokerageUserDO firstUser = brokerageUserService.getBindBrokerageUser(userId);
|
||||
if (firstUser == null || !BooleanUtil.isTrue(firstUser.getBrokerageEnabled())) {
|
||||
if (firstUser == null) {
|
||||
return;
|
||||
}
|
||||
// 1.1.1查看一级分销员是否已经分销过
|
||||
@@ -145,7 +141,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
|
||||
return;
|
||||
}
|
||||
BrokerageUserDO secondUser = brokerageUserService.getBrokerageUser(firstUser.getBindUserId());
|
||||
if (secondUser == null || !BooleanUtil.isTrue(secondUser.getBrokerageEnabled())) {
|
||||
if (secondUser == null) {
|
||||
return;
|
||||
}
|
||||
// 2.1.1查看二级分销员是否已经分销过
|
||||
|
||||
+2
-7
@@ -189,14 +189,9 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
|
||||
}
|
||||
// 2.3 校验能否绑定
|
||||
validateCanBindUser(brokerageUser, bindUserId);
|
||||
// 2.3 绑定用户
|
||||
// 2.3 绑定用户(人人分销:新用户默认具备分销资格)
|
||||
if (isNewBrokerageUser) {
|
||||
Integer enabledCondition = tradeConfigService.getTradeConfig().getBrokerageEnabledCondition();
|
||||
if (BrokerageEnabledConditionEnum.ALL.getCondition().equals(enabledCondition)) { // 人人分销:用户默认就有分销资格
|
||||
brokerageUser.setBrokerageEnabled(true).setBrokerageTime(LocalDateTime.now());
|
||||
} else {
|
||||
brokerageUser.setBrokerageEnabled(false).setBrokerageTime(LocalDateTime.now());
|
||||
}
|
||||
brokerageUser.setBrokerageEnabled(true).setBrokerageTime(LocalDateTime.now());
|
||||
brokerageUserMapper.insert(fillBindUserData(bindUserId, brokerageUser));
|
||||
} else {
|
||||
brokerageUserMapper.updateById(fillBindUserData(bindUserId, new BrokerageUserDO().setId(userId)));
|
||||
|
||||
+5
-8
@@ -124,17 +124,14 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
|
||||
.setUserId(withdraw.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue())
|
||||
.setBizType(PayWalletBizTypeEnum.BROKERAGE_WITHDRAW.getType()).setBizId(withdraw.getId().toString())
|
||||
.setPrice(withdraw.getPrice()));
|
||||
// 1.2 微信 API
|
||||
} else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(withdraw.getType())) {
|
||||
// TODO @luchi:这里,要加个转账单号的记录;另外,调用 API 转账,是立马成功,还是有延迟的哈?
|
||||
createPayTransfer(withdraw);
|
||||
// 1.3 剩余类型,都是手动打款,所以不处理
|
||||
// 1.2 历史 type=5 曾走微信转账 API;现业务为支付宝账号提现,统一手动打款
|
||||
// 若再走 createPayTransfer,无小程序 openid 会 NPE → 管理端「系统异常」
|
||||
} else {
|
||||
// TODO 可优化:未来可以考虑,接入支付宝、银联等 API 转账,实现自动打款
|
||||
log.info("[auditBrokerageWithdrawSuccess][withdraw({}) 类型({}) 手动打款,无需处理]", withdraw.getId(), withdraw.getType());
|
||||
log.info("[auditBrokerageWithdrawSuccess][withdraw({}) 类型({}) 手动打款,账号={}]",
|
||||
withdraw.getId(), withdraw.getType(), withdraw.getAccountNo());
|
||||
}
|
||||
|
||||
// 2. 非支付 API,则直接体现成功
|
||||
// 2. 非支付 API,则直接提现成功
|
||||
if (!BrokerageWithdrawTypeEnum.isApi(withdraw.getType())) {
|
||||
brokerageWithdrawMapper.updateByIdAndStatus(withdraw.getId(), BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus(),
|
||||
new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus()));
|
||||
|
||||
+4
-6
@@ -55,7 +55,7 @@ public class PosterPicGen {
|
||||
|
||||
private int pageHeight = 700;
|
||||
|
||||
private String title = "荷塘月色";
|
||||
private String title = "荷塘助手";
|
||||
|
||||
private String description = "荷塘湖畔 相伴月色";
|
||||
|
||||
@@ -160,10 +160,8 @@ public class PosterPicGen {
|
||||
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||
}
|
||||
|
||||
private void drawUserAvatar(BufferedImage avatar) {
|
||||
if (avatar == null) {
|
||||
return;
|
||||
}
|
||||
private void drawUserAvatar(BufferedImage avatar) throws IOException{
|
||||
BufferedImage logoAvater = ImageIO.read(ResourceUtil.getResource("image/logo.png"));
|
||||
graphics.setColor(Color.WHITE);
|
||||
graphics.fill(new RoundRectangle2D.Double(
|
||||
AVATAR_X - AVATAR_CLEAR_MARGIN,
|
||||
@@ -172,7 +170,7 @@ public class PosterPicGen {
|
||||
AVATAR_SIZE + AVATAR_CLEAR_MARGIN * 2.0,
|
||||
(AVATAR_CORNER_RADIUS + AVATAR_CLEAR_MARGIN) * 2.0,
|
||||
(AVATAR_CORNER_RADIUS + AVATAR_CLEAR_MARGIN) * 2.0));
|
||||
drawAvatar(avatar, AVATAR_X, AVATAR_Y, AVATAR_SIZE,
|
||||
drawAvatar(logoAvater, AVATAR_X, AVATAR_Y, AVATAR_SIZE,
|
||||
new RoundRectangle2D.Double(0, 0, AVATAR_SIZE, AVATAR_SIZE,
|
||||
AVATAR_CORNER_RADIUS * 2.0, AVATAR_CORNER_RADIUS * 2.0));
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 351 KiB |
+20
@@ -91,4 +91,24 @@ public interface MemberErrorCode {
|
||||
ErrorCode CHAT_USER_COUNT_EXCEED = new ErrorCode(1_004_018_000, "当天免费聊天人数已用完");
|
||||
ErrorCode CHAT_MSG_COUNT_EXCEED = new ErrorCode(1_004_018_001, "当天免费聊天次数已用完");
|
||||
ErrorCode CHAT_TOKEN_CREATE_FAILED = new ErrorCode(1_004_018_002, "聊天登录凭证签发失败,请稍后重试");
|
||||
|
||||
//========== 航空账号 1-004-019-000 ==========
|
||||
ErrorCode AIRLINE_ACCOUNT_NOT_EXISTS = new ErrorCode(1_004_019_000, "航空账号不存在");
|
||||
ErrorCode AIRLINE_ACCOUNT_MOBILE_EXISTS = new ErrorCode(1_004_019_001, "该手机号已存在航空账号");
|
||||
ErrorCode AIRLINE_ACCOUNT_DISABLED = new ErrorCode(1_004_019_002, "航空账号已禁用");
|
||||
ErrorCode AIRLINE_ACCOUNT_NOT_STARTED = new ErrorCode(1_004_019_003, "航空账号尚未生效");
|
||||
ErrorCode AIRLINE_ACCOUNT_EXPIRED = new ErrorCode(1_004_019_004, "航空账号已到期,无法登录");
|
||||
ErrorCode AIRLINE_ACCOUNT_DEVICE_BOUND = new ErrorCode(1_004_019_005, "该账号已绑定其他手机,请联系管理员解绑");
|
||||
ErrorCode AIRLINE_ACCOUNT_DEVICE_USED = new ErrorCode(1_004_019_006, "该手机已绑定其他航空账号");
|
||||
ErrorCode AIRLINE_ACCOUNT_TIME_INVALID = new ErrorCode(1_004_019_007, "结束时间必须晚于开始时间");
|
||||
ErrorCode AIRLINE_ACCOUNT_BAD_CREDENTIALS = new ErrorCode(1_004_019_008, "航空账号手机号或密码不正确");
|
||||
|
||||
// ========== 骑遇发车局 / 骑行标签 1-004-020-000 ==========
|
||||
ErrorCode RIDE_PLAN_NOT_EXISTS = new ErrorCode(1_004_020_000, "发车局不存在或已下架");
|
||||
ErrorCode RIDE_PLAN_MEET_TIME_INVALID = new ErrorCode(1_004_020_001, "集合时间必须晚于当前时间");
|
||||
ErrorCode RIDE_PLAN_FULL = new ErrorCode(1_004_020_002, "该发车局已满员");
|
||||
ErrorCode RIDE_PLAN_ALREADY_JOINED = new ErrorCode(1_004_020_003, "已加入该发车局,请勿重复加入");
|
||||
ErrorCode RIDE_PLAN_NOT_JOINED = new ErrorCode(1_004_020_004, "尚未加入该发车局");
|
||||
ErrorCode RIDE_TAG_NOT_EXISTS = new ErrorCode(1_004_020_005, "骑行标签不存在或已停用");
|
||||
ErrorCode RIDE_RECORD_NOT_EXISTS = new ErrorCode(1_004_020_006, "骑行战报不存在或已下架");
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.iocoder.yudao.module.member.enums.interaction;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 互动消息类型
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MemberInteractionBizTypeEnum {
|
||||
|
||||
VIEWED("VIEWED", "查看了你"),
|
||||
WECHAT_UNLOCKED("WECHAT_UNLOCKED", "解锁了你的微信");
|
||||
|
||||
private final String type;
|
||||
private final String contentSuffix;
|
||||
|
||||
}
|
||||
@@ -43,6 +43,11 @@
|
||||
<artifactId>yudao-module-pay-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-trade-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
@@ -101,6 +106,13 @@
|
||||
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 极光推送 -->
|
||||
<dependency>
|
||||
<groupId>cn.jpush.api</groupId>
|
||||
<artifactId>jpush-client</artifactId>
|
||||
<version>3.7.7</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 文字、图片风险认证 -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ public final class ChatConstant {
|
||||
* 客服用户id
|
||||
*/
|
||||
public static final Long COM_SERVICE_USER_ID = -200L;
|
||||
public static final String COM_SERVICE_USER_NAME = "荷塘月色客服";
|
||||
public static final String COM_SERVICE_USER_NAME = "荷塘助手客服";
|
||||
public static final String COM_SERVICE_USER_AVATAR = "https://api.htysapp.com/admin-api/infra/file/4/get/affa4cac924cb13fe5706100828685015d512bca7dfa0e26ef82422b31877abc.png";
|
||||
|
||||
public static final List<Long> IGNORE_USER_IDS = Arrays.asList(0L, COM_MESSAGE_USER_ID, COM_SERVICE_USER_ID);
|
||||
|
||||
+5
@@ -29,6 +29,11 @@ public interface ChatApi {
|
||||
*/
|
||||
boolean createOrUpdate(@Valid ChatCreateOrUpdateUserReqVO dto);
|
||||
|
||||
/**
|
||||
* 封禁聊天用户并推送下线
|
||||
*/
|
||||
boolean banUser(Long userId, String reason);
|
||||
|
||||
/**
|
||||
* 根据userId查询用户详情
|
||||
*/
|
||||
|
||||
+30
@@ -121,6 +121,36 @@ public class ChatApiImpl implements ChatApi {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 封禁聊天用户并推送下线
|
||||
*/
|
||||
@Override
|
||||
public boolean banUser(Long userId, String reason) {
|
||||
if (userId == null) {
|
||||
return false;
|
||||
}
|
||||
ChatApiTool.API api = setApiConfig(ChatApiTool.USER_BAN);
|
||||
try {
|
||||
ChatBanUserReqVO dto = new ChatBanUserReqVO(userId, reason);
|
||||
String body = JSONUtil.toJsonStr(dto);
|
||||
String res = HttpRequest.post(api.getFullPath()).header(apiCountersign(body)).body(body).execute().body();
|
||||
log.debug("封禁聊天用户,res:{}", res);
|
||||
if (ObjectUtil.isEmpty(res)) {
|
||||
log.error("封禁聊天用户,res为空");
|
||||
return false;
|
||||
}
|
||||
ChatBaseRespVO baseRes = JSONUtil.toBean(res, ChatBaseRespVO.class);
|
||||
if (ObjectUtil.isEmpty(baseRes) || !baseRes.isSuccess()) {
|
||||
log.error("封禁聊天用户,返回错误,res:{}", res);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("封禁聊天用户,意外的错误,userId={}", userId, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据userId查询用户详情
|
||||
*/
|
||||
|
||||
+188
-4
@@ -10,6 +10,7 @@ import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -42,6 +43,8 @@ final class FaceVerifyImageProcessor {
|
||||
private static final int MAX_WIDTH = 1080;
|
||||
/** 阿里云允许的图片最大高度 */
|
||||
private static final int MAX_HEIGHT = 1920;
|
||||
/** 横图转竖图时使用的目标宽高比(宽:高),约等于 3:4 */
|
||||
private static final double PORTRAIT_ASPECT = 3D / 4D;
|
||||
|
||||
/** JPEG 输出质量:0.9,用于兼顾人脸清晰度和文件大小 */
|
||||
private static final float JPEG_QUALITY = 0.90F;
|
||||
@@ -85,9 +88,26 @@ final class FaceVerifyImageProcessor {
|
||||
throw new IllegalArgumentException("人脸认证图片格式不支持或文件已损坏");
|
||||
}
|
||||
|
||||
// iPhone 竖拍常是「横像素 + EXIF Orientation」,必须先转正再判断竖/横图
|
||||
int orientation = readJpegExifOrientation(sourceImage);
|
||||
if (orientation > 1) {
|
||||
BufferedImage oriented = applyExifOrientation(decodedImage, orientation);
|
||||
decodedImage.flush();
|
||||
decodedImage = oriented;
|
||||
log.info("|人脸识别|,已按 EXIF Orientation={} 转正比对图", orientation);
|
||||
}
|
||||
|
||||
int sourceWidth = decodedImage.getWidth();
|
||||
int sourceHeight = decodedImage.getHeight();
|
||||
// 阿里云要求人脸比对照片为竖图;横图不做裁剪,避免裁掉人脸区域。
|
||||
// 阿里云要求人脸比对照片为竖图(高>宽);横图居中裁成竖图,避免直接失败
|
||||
if (sourceHeight <= sourceWidth) {
|
||||
BufferedImage portrait = cropToPortrait(decodedImage);
|
||||
decodedImage.flush();
|
||||
decodedImage = portrait;
|
||||
sourceWidth = decodedImage.getWidth();
|
||||
sourceHeight = decodedImage.getHeight();
|
||||
log.info("|人脸识别|,横图已居中裁成竖图,尺寸={}x{}", sourceWidth, sourceHeight);
|
||||
}
|
||||
if (sourceHeight <= sourceWidth) {
|
||||
decodedImage.flush();
|
||||
throw new IllegalArgumentException("人脸认证图片必须为竖图,且高度大于宽度");
|
||||
@@ -113,11 +133,175 @@ final class FaceVerifyImageProcessor {
|
||||
encodedImage = encodeJpeg(processedImage);
|
||||
}
|
||||
|
||||
log.info("|人脸识别|,图片尺寸处理完成,原始尺寸={}x{},处理后尺寸={}x{}", sourceWidth, sourceHeight, processedImage.getWidth(), processedImage.getHeight());
|
||||
log.info("|人脸识别|,图片尺寸处理完成,原始尺寸={}x{},处理后尺寸={}x{}",
|
||||
sourceWidth, sourceHeight, processedImage.getWidth(), processedImage.getHeight());
|
||||
processedImage.flush();
|
||||
return encodedImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将横图居中裁成竖图(约 3:4),尽量保留中间人脸区域。
|
||||
*/
|
||||
private static BufferedImage cropToPortrait(BufferedImage source) {
|
||||
int width = source.getWidth();
|
||||
int height = source.getHeight();
|
||||
int cropHeight = height;
|
||||
int cropWidth = (int) Math.floor(cropHeight * PORTRAIT_ASPECT);
|
||||
if (cropWidth >= cropHeight) {
|
||||
cropWidth = Math.max(1, cropHeight - 1);
|
||||
}
|
||||
if (cropWidth > width) {
|
||||
cropWidth = width;
|
||||
cropHeight = (int) Math.floor(cropWidth / PORTRAIT_ASPECT);
|
||||
if (cropHeight > height) {
|
||||
cropHeight = height;
|
||||
}
|
||||
if (cropHeight <= cropWidth) {
|
||||
cropHeight = Math.min(height, cropWidth + 1);
|
||||
}
|
||||
}
|
||||
int x = Math.max(0, (width - cropWidth) / 2);
|
||||
int y = Math.max(0, (height - cropHeight) / 2);
|
||||
cropWidth = Math.min(cropWidth, width - x);
|
||||
cropHeight = Math.min(cropHeight, height - y);
|
||||
return source.getSubimage(x, y, cropWidth, cropHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 JPEG EXIF Orientation(Tag 0x0112),非 JPEG / 失败返回 1。
|
||||
*/
|
||||
private static int readJpegExifOrientation(byte[] jpeg) {
|
||||
try {
|
||||
if (jpeg == null || jpeg.length < 4 || (jpeg[0] & 0xFF) != 0xFF || (jpeg[1] & 0xFF) != 0xD8) {
|
||||
return 1;
|
||||
}
|
||||
int offset = 2;
|
||||
while (offset + 4 < jpeg.length) {
|
||||
if ((jpeg[offset] & 0xFF) != 0xFF) {
|
||||
break;
|
||||
}
|
||||
int marker = jpeg[offset + 1] & 0xFF;
|
||||
offset += 2;
|
||||
if (marker == 0xD9 || marker == 0xDA) {
|
||||
break;
|
||||
}
|
||||
if (offset + 2 > jpeg.length) {
|
||||
break;
|
||||
}
|
||||
int segLen = ((jpeg[offset] & 0xFF) << 8) | (jpeg[offset + 1] & 0xFF);
|
||||
if (segLen < 2 || offset + segLen > jpeg.length) {
|
||||
break;
|
||||
}
|
||||
if (marker == 0xE1 && segLen >= 8) {
|
||||
int exifStart = offset + 2;
|
||||
if (exifStart + 6 <= jpeg.length
|
||||
&& jpeg[exifStart] == 'E' && jpeg[exifStart + 1] == 'x'
|
||||
&& jpeg[exifStart + 2] == 'i' && jpeg[exifStart + 3] == 'f'
|
||||
&& jpeg[exifStart + 4] == 0 && jpeg[exifStart + 5] == 0) {
|
||||
int tiff = exifStart + 6;
|
||||
boolean little = jpeg[tiff] == 'I' && jpeg[tiff + 1] == 'I';
|
||||
if (!(little || (jpeg[tiff] == 'M' && jpeg[tiff + 1] == 'M'))) {
|
||||
return 1;
|
||||
}
|
||||
int ifd0 = tiff + readInt(jpeg, tiff + 4, 4, little);
|
||||
if (ifd0 + 2 > jpeg.length) {
|
||||
return 1;
|
||||
}
|
||||
int entries = readInt(jpeg, ifd0, 2, little);
|
||||
for (int i = 0; i < entries; i++) {
|
||||
int entry = ifd0 + 2 + i * 12;
|
||||
if (entry + 12 > jpeg.length) {
|
||||
break;
|
||||
}
|
||||
int tag = readInt(jpeg, entry, 2, little);
|
||||
if (tag == 0x0112) {
|
||||
return readInt(jpeg, entry + 8, 2, little);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += segLen;
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
// ignore
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int readInt(byte[] data, int off, int len, boolean littleEndian) {
|
||||
int v = 0;
|
||||
if (littleEndian) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
v |= (data[off + i] & 0xFF) << (8 * i);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < len; i++) {
|
||||
v = (v << 8) | (data[off + i] & 0xFF);
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
private static BufferedImage applyExifOrientation(BufferedImage src, int orientation) {
|
||||
int w = src.getWidth();
|
||||
int h = src.getHeight();
|
||||
AffineTransform tx = new AffineTransform();
|
||||
int newW = w;
|
||||
int newH = h;
|
||||
switch (orientation) {
|
||||
case 2:
|
||||
tx.scale(-1, 1);
|
||||
tx.translate(-w, 0);
|
||||
break;
|
||||
case 3:
|
||||
tx.translate(w, h);
|
||||
tx.rotate(Math.PI);
|
||||
break;
|
||||
case 4:
|
||||
tx.scale(1, -1);
|
||||
tx.translate(0, -h);
|
||||
break;
|
||||
case 5:
|
||||
tx.rotate(Math.PI / 2);
|
||||
tx.scale(-1, 1);
|
||||
newW = h;
|
||||
newH = w;
|
||||
break;
|
||||
case 6: // 90 CW —— iPhone 竖拍最常见
|
||||
tx.translate(h, 0);
|
||||
tx.rotate(Math.PI / 2);
|
||||
newW = h;
|
||||
newH = w;
|
||||
break;
|
||||
case 7:
|
||||
tx.scale(-1, 1);
|
||||
tx.translate(-h, 0);
|
||||
tx.translate(0, w);
|
||||
tx.rotate(3 * Math.PI / 2);
|
||||
newW = h;
|
||||
newH = w;
|
||||
break;
|
||||
case 8:
|
||||
tx.translate(0, w);
|
||||
tx.rotate(3 * Math.PI / 2);
|
||||
newW = h;
|
||||
newH = w;
|
||||
break;
|
||||
default:
|
||||
return src;
|
||||
}
|
||||
BufferedImage dst = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g = dst.createGraphics();
|
||||
try {
|
||||
g.setColor(Color.WHITE);
|
||||
g.fillRect(0, 0, newW, newH);
|
||||
g.drawImage(src, tx, null);
|
||||
} finally {
|
||||
g.dispose();
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据阿里云尺寸限制计算目标宽高,保持图片原始宽高比。
|
||||
* 大图优先将宽度缩放至 720 像素,小图仅放大至接口要求的最小尺寸。
|
||||
@@ -133,7 +317,8 @@ final class FaceVerifyImageProcessor {
|
||||
int targetWidth = Math.max(1, (int) Math.floor(sourceWidth * scale));
|
||||
int targetHeight = Math.max(1, (int) Math.floor(sourceHeight * scale));
|
||||
if (targetWidth < MIN_WIDTH || targetHeight < MIN_HEIGHT || targetWidth > MAX_WIDTH || targetHeight > MAX_HEIGHT) {
|
||||
throw new IllegalArgumentException(String.format("人脸认证图片比例无法满足阿里云尺寸要求,原始尺寸:%dx%d", sourceWidth, sourceHeight));
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"人脸认证图片比例无法满足阿里云尺寸要求,原始尺寸:%dx%d", sourceWidth, sourceHeight));
|
||||
}
|
||||
return new int[]{targetWidth, targetHeight};
|
||||
}
|
||||
@@ -216,7 +401,6 @@ final class FaceVerifyImageProcessor {
|
||||
throw new IllegalArgumentException("人脸认证原始图片不能超过10MB");
|
||||
}
|
||||
|
||||
// 即使服务端未返回 Content-Length,也在读取过程中限制总字节数,避免占用过多内存。
|
||||
try (InputStream inputStream = connection.getInputStream();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(
|
||||
contentLength > 0 ? Math.min(contentLength, MAX_SOURCE_IMAGE_BYTES) : 8192)) {
|
||||
|
||||
+67
-7
@@ -2,6 +2,9 @@ package cn.iocoder.yudao.module.member.client.service;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.iocoder.yudao.module.member.client.vo.thirdfaceverify.ThirdFindFaceVerifyReqVo;
|
||||
import cn.iocoder.yudao.module.member.client.vo.thirdfaceverify.ThirdFindFaceVerifyResVo;
|
||||
import cn.iocoder.yudao.module.member.client.vo.thirdfaceverify.ThirdFaceVerifyReqVO;
|
||||
@@ -56,8 +59,23 @@ public class ThirdFaceVerifyServiceImpl implements ThirdFaceVerifyService {
|
||||
// 参数
|
||||
request.setOuterOrderNo(dto.getCode());
|
||||
request.setUserId(String.valueOf(dto.getFaceVerifyUserId()));
|
||||
request.setMetaInfo(dto.getMetaInfoJson());
|
||||
// request.setFaceContrastPictureUrl(dto.getFaceVerifyUrl());
|
||||
// MetaInfo 必须是客户端 getMetaInfos 原始字符串;错误包装会导致 SDK 不弹刷脸页
|
||||
String metaInfo = dto.getMetaInfoJson();
|
||||
if (StrUtil.isNotBlank(metaInfo) && metaInfo.trim().startsWith("{") && metaInfo.contains("\"metaInfoJson\"")) {
|
||||
try {
|
||||
JSONObject obj = JSONUtil.parseObj(metaInfo);
|
||||
String nested = obj.getStr("metaInfoJson");
|
||||
if (StrUtil.isNotBlank(nested)) {
|
||||
log.warn("|人脸识别|,检测到 MetaInfo 被包了一层 authExtraJson,已自动解开");
|
||||
metaInfo = nested;
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
// keep original
|
||||
}
|
||||
}
|
||||
request.setMetaInfo(metaInfo);
|
||||
log.info("|人脸识别|,InitFaceVerify outerOrderNo={} userId={} metaLen={}",
|
||||
dto.getCode(), dto.getFaceVerifyUserId(), metaInfo == null ? 0 : metaInfo.length());
|
||||
|
||||
request.setFaceContrastPicture(base64Image);
|
||||
// 配置
|
||||
@@ -137,16 +155,58 @@ public class ThirdFaceVerifyServiceImpl implements ThirdFaceVerifyService {
|
||||
log.error("|人脸识别|,第三方接口无响应");
|
||||
return null;
|
||||
}
|
||||
ThirdFindFaceVerifyResVo result = new ThirdFindFaceVerifyResVo();
|
||||
DescribeFaceVerifyResponseBody body = response.getBody();
|
||||
if (ObjectUtil.notEqual(body.getCode(), "200")) {
|
||||
result.setErrorMessage(body.getMessage());
|
||||
}
|
||||
ThirdFindFaceVerifyResVo result = new ThirdFindFaceVerifyResVo();
|
||||
DescribeFaceVerifyResponseBody.DescribeFaceVerifyResponseBodyResultObject resultObject = body.getResultObject();
|
||||
result = BeanUtil.toBean(resultObject, ThirdFindFaceVerifyResVo.class);
|
||||
if (ObjectUtil.isNotEmpty(resultObject)) {
|
||||
result = BeanUtil.toBean(resultObject, ThirdFindFaceVerifyResVo.class);
|
||||
if (result == null) {
|
||||
result = new ThirdFindFaceVerifyResVo();
|
||||
}
|
||||
}
|
||||
if (ObjectUtil.notEqual(body.getCode(), "200")) {
|
||||
// 接口级失败信息(不可被 ResultObject 拷贝覆盖)
|
||||
result.setErrorMessage(body.getMessage());
|
||||
log.warn("|人脸识别|,DescribeFaceVerify 失败 code={} msg={} certifyId={}",
|
||||
body.getCode(), body.getMessage(), code.getCertifyId());
|
||||
} else if (ObjectUtil.isEmpty(result.getErrorMessage())) {
|
||||
result.setErrorMessage(describeSubCode(result.getSubCode(), result.getPassed()));
|
||||
}
|
||||
log.info("|人脸识别|,DescribeFaceVerify certifyId={} passed={} subCode={} err={}",
|
||||
code.getCertifyId(), result.getPassed(), result.getSubCode(), result.getErrorMessage());
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 阿里云 ResultObject.SubCode → 可读原因(对齐官方错误码说明) */
|
||||
private static String describeSubCode(String subCode, String passed) {
|
||||
if (ObjectUtil.equals("T", passed) || ObjectUtil.equals("200", subCode)) {
|
||||
return "认证通过";
|
||||
}
|
||||
if (ObjectUtil.isEmpty(subCode)) {
|
||||
return ObjectUtil.equals("F", passed) ? "认证未通过" : null;
|
||||
}
|
||||
switch (subCode) {
|
||||
case "201":
|
||||
return "姓名和身份证不一致";
|
||||
case "202":
|
||||
return "查询不到身份信息";
|
||||
case "203":
|
||||
return "查询不到照片或照片不可用";
|
||||
case "204":
|
||||
return "人脸比对不一致(与上传头像不是同一人或活体质量偏低)";
|
||||
case "205":
|
||||
return "活体检测存在风险";
|
||||
case "206":
|
||||
return "业务策略限制(设备环境异常/高频认证等)";
|
||||
case "207":
|
||||
return "人脸与身份证人脸比对不一致";
|
||||
case "209":
|
||||
return "权威比对源异常";
|
||||
default:
|
||||
return "认证未通过(subCode=" + subCode + ")";
|
||||
}
|
||||
}
|
||||
|
||||
private DescribeFaceVerifyResponse describeFaceVerifyAutoRoute(DescribeFaceVerifyRequest request) {
|
||||
DescribeFaceVerifyResponse lastResponse = null;
|
||||
for (int i = 0; i < endpoints.size(); i++) {
|
||||
|
||||
+1
@@ -21,6 +21,7 @@ public class ChatApiTool {
|
||||
public static final API USER_LOGIN = API.builder().summary("用户登录").url("/third/user/login").method(HttpMethod.POST).build();
|
||||
public static final API USER_GET = API.builder().summary("根据userId查询用户详情").url("/third/user/getUserByUserId").method(HttpMethod.POST).build();
|
||||
public static final API USER_SAVE_OR_UPDATE = API.builder().summary("创建或者更新用户").url("/third/user/createOrUpdate").method(HttpMethod.POST).build();
|
||||
public static final API USER_BAN = API.builder().summary("封禁用户并踢下线").url("/third/user/ban").method(HttpMethod.POST).build();
|
||||
public static final API GET_USER_ONLINE_STATUS_BY_ID = API.builder().summary("查询用户在线状态").url("/third/user/getUserOnlineStatusById").method(HttpMethod.POST).build();
|
||||
|
||||
// 朋友圈
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.yudao.module.member.client.vo.chat.user;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "封禁聊天用户")
|
||||
public class ChatBanUserReqVO {
|
||||
|
||||
@NotNull(message = "用户id不可为空")
|
||||
@Schema(description = "用户id")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "封禁原因")
|
||||
private String reason;
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package cn.iocoder.yudao.module.member.controller.admin.airline;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.member.controller.admin.airline.vo.*;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.airline.MemberAirlineAccountDO;
|
||||
import cn.iocoder.yudao.module.member.service.airline.MemberAirlineAccountService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 航空账号")
|
||||
@RestController
|
||||
@RequestMapping("/member/airline-account")
|
||||
@Validated
|
||||
public class MemberAirlineAccountController {
|
||||
|
||||
@Resource
|
||||
private MemberAirlineAccountService airlineAccountService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建航空账号")
|
||||
@PreAuthorize("@ss.hasPermission('member:airline-account:create')")
|
||||
public CommonResult<Long> createAirlineAccount(@Valid @RequestBody MemberAirlineAccountCreateReqVO createReqVO) {
|
||||
return success(airlineAccountService.createAirlineAccount(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新航空账号")
|
||||
@PreAuthorize("@ss.hasPermission('member:airline-account:update')")
|
||||
public CommonResult<Boolean> updateAirlineAccount(@Valid @RequestBody MemberAirlineAccountUpdateReqVO updateReqVO) {
|
||||
airlineAccountService.updateAirlineAccount(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除航空账号")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('member:airline-account:delete')")
|
||||
public CommonResult<Boolean> deleteAirlineAccount(@RequestParam("id") Long id) {
|
||||
airlineAccountService.deleteAirlineAccount(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/unbind-device")
|
||||
@Operation(summary = "解绑设备(换机后需重新登录绑定)")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('member:airline-account:unbind')")
|
||||
public CommonResult<Boolean> unbindDevice(@RequestParam("id") Long id) {
|
||||
airlineAccountService.unbindDevice(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得航空账号")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('member:airline-account:query')")
|
||||
public CommonResult<MemberAirlineAccountRespVO> getAirlineAccount(@RequestParam("id") Long id) {
|
||||
return success(convert(airlineAccountService.getAirlineAccount(id)));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得航空账号分页")
|
||||
@PreAuthorize("@ss.hasPermission('member:airline-account:query')")
|
||||
public CommonResult<PageResult<MemberAirlineAccountRespVO>> getAirlineAccountPage(
|
||||
@Valid MemberAirlineAccountPageReqVO pageReqVO) {
|
||||
PageResult<MemberAirlineAccountDO> pageResult = airlineAccountService.getAirlineAccountPage(pageReqVO);
|
||||
List<MemberAirlineAccountRespVO> list = pageResult.getList().stream()
|
||||
.map(this::convert).collect(Collectors.toList());
|
||||
return success(new PageResult<>(list, pageResult.getTotal()));
|
||||
}
|
||||
|
||||
private MemberAirlineAccountRespVO convert(MemberAirlineAccountDO account) {
|
||||
if (account == null) {
|
||||
return null;
|
||||
}
|
||||
MemberAirlineAccountRespVO vo = BeanUtils.toBean(account, MemberAirlineAccountRespVO.class);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
vo.setExpired(account.getEndTime() != null && now.isAfter(account.getEndTime()));
|
||||
vo.setNotStarted(account.getStartTime() != null && now.isBefore(account.getStartTime()));
|
||||
return vo;
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package cn.iocoder.yudao.module.member.controller.admin.airline.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 航空账号新增/修改 Base VO")
|
||||
@Data
|
||||
public class MemberAirlineAccountBaseVO {
|
||||
|
||||
@Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13800138000")
|
||||
@NotEmpty(message = "手机号不能为空")
|
||||
@Length(min = 11, max = 11, message = "手机号长度为 11 位")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "品牌渠道(CZ南航/MU东航/HU海航)", requiredMode = Schema.RequiredMode.REQUIRED, example = "CZ")
|
||||
@NotEmpty(message = "品牌渠道不能为空")
|
||||
@Pattern(regexp = "CZ|MU|HU", message = "品牌渠道仅支持 CZ(南航)/ MU(东航)/ HU(海航)")
|
||||
private String channel;
|
||||
|
||||
@Schema(description = "生效开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "开始时间不能为空")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime startTime;
|
||||
|
||||
@Schema(description = "生效结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "结束时间不能为空")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime endTime;
|
||||
|
||||
@Schema(description = "状态(0开启 1关闭)", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||
@NotNull(message = "状态不能为空")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "备注", example = "某某航司临时账号")
|
||||
private String remark;
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.yudao.module.member.controller.admin.airline.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
@Schema(description = "管理后台 - 航空账号创建 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class MemberAirlineAccountCreateReqVO extends MemberAirlineAccountBaseVO {
|
||||
|
||||
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
private String password;
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package cn.iocoder.yudao.module.member.controller.admin.airline.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 航空账号分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class MemberAirlineAccountPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "手机号", example = "13800138000")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "品牌渠道(CZ南航/MU东航/HU海航)", example = "CZ")
|
||||
private String channel;
|
||||
|
||||
@Schema(description = "状态(0开启 1关闭)", example = "0")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "设备唯一识别号", example = "A1B2C3D4")
|
||||
private String deviceId;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package cn.iocoder.yudao.module.member.controller.admin.airline.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 航空账号 Response VO")
|
||||
@Data
|
||||
public class MemberAirlineAccountRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13800138000")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "品牌渠道(CZ南航/MU东航/HU海航)", requiredMode = Schema.RequiredMode.REQUIRED, example = "CZ")
|
||||
private String channel;
|
||||
|
||||
@Schema(description = "生效开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime startTime;
|
||||
|
||||
@Schema(description = "生效结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime endTime;
|
||||
|
||||
@Schema(description = "设备唯一识别号", example = "A1B2C3D4")
|
||||
private String deviceId;
|
||||
|
||||
@Schema(description = "状态(0开启 1关闭)", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "关联会员用户编号")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "是否已过期")
|
||||
private Boolean expired;
|
||||
|
||||
@Schema(description = "是否尚未生效")
|
||||
private Boolean notStarted;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package cn.iocoder.yudao.module.member.controller.admin.airline.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - 航空账号更新 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class MemberAirlineAccountUpdateReqVO extends MemberAirlineAccountBaseVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "密码(不传或空则不修改)", example = "123456")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
private String password;
|
||||
|
||||
}
|
||||
+8
@@ -71,6 +71,14 @@ public class MemberUserController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/update-status")
|
||||
@Operation(summary = "更新会员用户状态(禁用后踢出 App 登录,且无法再登录)")
|
||||
@PreAuthorize("@ss.hasPermission('member:user:update')")
|
||||
public CommonResult<Boolean> updateUserStatus(@Valid @RequestBody MemberUserUpdateStatusReqVO reqVO) {
|
||||
memberUserService.updateUserStatus(reqVO.getId(), reqVO.getStatus());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/fill-missing-user-codes")
|
||||
@Operation(summary = "为历史会员补齐用户 Code")
|
||||
// @PreAuthorize("@ss.hasPermission('member:user:update')")
|
||||
|
||||
+6
@@ -41,6 +41,12 @@ public class MemberUserPageReqVO extends PageParam {
|
||||
@Schema(description = "用户分组编号", example = "1")
|
||||
private Long groupId;
|
||||
|
||||
@Schema(description = "用户 Code(App 我的页面展示的 ID)", example = "924199")
|
||||
private String userCode;
|
||||
|
||||
@Schema(description = "性别:1 男 2 女", example = "1")
|
||||
private Integer sex;
|
||||
|
||||
// TODO 芋艿:注册用户类型;
|
||||
|
||||
// TODO 芋艿:登录用户类型;
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package cn.iocoder.yudao.module.member.controller.admin.user.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - 会员用户更新状态 Request VO")
|
||||
@Data
|
||||
public class MemberUserUpdateStatusReqVO {
|
||||
|
||||
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "用户编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "状态,见 CommonStatusEnum 枚举。0 开启,1 禁用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "状态不能为空")
|
||||
@InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.airline;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.member.controller.app.airline.vo.AirlineFlightVO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.airline.vo.AirlinePointVO;
|
||||
import cn.iocoder.yudao.module.member.service.airline.MemberAirlineDataService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* 免登录接口(@PermitAll):身份由 mobile + channel + deviceId 三要素定位,
|
||||
* 与 airline-check 相同的账号状态/有效期/设备绑定校验,防冒用。
|
||||
*/
|
||||
@Tag(name = "用户 APP - 航空 App 数据云同步(自建航线/我的积点钱包)")
|
||||
@RestController
|
||||
@RequestMapping("/member/airline-data")
|
||||
@Validated
|
||||
public class AppAirlineDataController {
|
||||
|
||||
@Resource
|
||||
private MemberAirlineDataService airlineDataService;
|
||||
|
||||
// ─── 航线 ────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/flight/list")
|
||||
@Operation(summary = "航线列表")
|
||||
@PermitAll
|
||||
public CommonResult<List<AirlineFlightVO>> flightList(@RequestParam("mobile") String mobile,
|
||||
@RequestParam("deviceId") String deviceId,
|
||||
@RequestParam("channel") String channel) {
|
||||
return success(airlineDataService.getFlightList(mobile, deviceId, channel));
|
||||
}
|
||||
|
||||
@PostMapping("/flight/create")
|
||||
@Operation(summary = "新增航线")
|
||||
@PermitAll
|
||||
public CommonResult<Long> flightCreate(@RequestParam("mobile") String mobile,
|
||||
@RequestParam("deviceId") String deviceId,
|
||||
@RequestParam("channel") String channel,
|
||||
@RequestBody AirlineFlightVO vo) {
|
||||
return success(airlineDataService.createFlight(mobile, deviceId, channel, vo));
|
||||
}
|
||||
|
||||
@PutMapping("/flight/update")
|
||||
@Operation(summary = "更新航线")
|
||||
@PermitAll
|
||||
public CommonResult<Boolean> flightUpdate(@RequestParam("mobile") String mobile,
|
||||
@RequestParam("deviceId") String deviceId,
|
||||
@RequestParam("channel") String channel,
|
||||
@RequestBody AirlineFlightVO vo) {
|
||||
airlineDataService.updateFlight(mobile, deviceId, channel, vo);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/flight/delete")
|
||||
@Operation(summary = "删除航线")
|
||||
@PermitAll
|
||||
public CommonResult<Boolean> flightDelete(@RequestParam("mobile") String mobile,
|
||||
@RequestParam("deviceId") String deviceId,
|
||||
@RequestParam("channel") String channel,
|
||||
@RequestParam("id") Long id) {
|
||||
airlineDataService.deleteFlight(mobile, deviceId, channel, id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// ─── 我的:积点/钱包 ─────────────────────────────────────
|
||||
|
||||
@GetMapping("/point/get")
|
||||
@Operation(summary = "我的积点/钱包")
|
||||
@PermitAll
|
||||
public CommonResult<AirlinePointVO> pointGet(@RequestParam("mobile") String mobile,
|
||||
@RequestParam("deviceId") String deviceId,
|
||||
@RequestParam("channel") String channel) {
|
||||
return success(airlineDataService.getPoint(mobile, deviceId, channel));
|
||||
}
|
||||
|
||||
@PutMapping("/point/update")
|
||||
@Operation(summary = "保存我的积点/钱包")
|
||||
@PermitAll
|
||||
public CommonResult<Boolean> pointUpdate(@RequestParam("mobile") String mobile,
|
||||
@RequestParam("deviceId") String deviceId,
|
||||
@RequestParam("channel") String channel,
|
||||
@RequestBody AirlinePointVO vo) {
|
||||
airlineDataService.updatePoint(mobile, deviceId, channel, vo);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.airline.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "用户 APP - 航空 App 自建航线 VO")
|
||||
@Data
|
||||
public class AirlineFlightVO {
|
||||
|
||||
@Schema(description = "编号(新增时不传,更新时必传)", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "航班号", example = "CZ3101")
|
||||
private String flightNo;
|
||||
|
||||
@Schema(description = "任务/航班名称")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "出发城市")
|
||||
private String beginCity;
|
||||
|
||||
@Schema(description = "出发机场")
|
||||
private String beginAirport;
|
||||
|
||||
@Schema(description = "到达城市")
|
||||
private String endCity;
|
||||
|
||||
@Schema(description = "到达机场")
|
||||
private String endAirport;
|
||||
|
||||
@Schema(description = "舱位/班次")
|
||||
private String classes;
|
||||
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "计划起飞时间")
|
||||
private String flyBeginDate0;
|
||||
|
||||
@Schema(description = "计划到达时间")
|
||||
private String flyEndDate0;
|
||||
|
||||
@Schema(description = "实际起飞时间")
|
||||
private String flyBeginDate1;
|
||||
|
||||
@Schema(description = "实际到达时间")
|
||||
private String flyEndDate1;
|
||||
|
||||
@Schema(description = "签到时间")
|
||||
private String signInTime;
|
||||
|
||||
@Schema(description = "机号/编号")
|
||||
private String aircraftNo;
|
||||
|
||||
@Schema(description = "航司")
|
||||
private String airline;
|
||||
|
||||
@Schema(description = "机型")
|
||||
private String aircraftType;
|
||||
|
||||
@Schema(description = "登机口")
|
||||
private String gate;
|
||||
|
||||
@Schema(description = "出发机场停机位")
|
||||
private String beginStand;
|
||||
|
||||
@Schema(description = "到达机场停机位")
|
||||
private String endStand;
|
||||
|
||||
@Schema(description = "货关时间")
|
||||
private String cargoClose;
|
||||
|
||||
@Schema(description = "客关时间")
|
||||
private String paxClose;
|
||||
|
||||
@Schema(description = "离港时间")
|
||||
private String leavePort;
|
||||
|
||||
@Schema(description = "起飞时间")
|
||||
private String takeOff;
|
||||
|
||||
@Schema(description = "落地时间")
|
||||
private String land;
|
||||
|
||||
@Schema(description = "到港时间")
|
||||
private String arrivePort;
|
||||
|
||||
@Schema(description = "前序航班")
|
||||
private String prevFlight;
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.airline.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "用户 APP - 航空 App 我的积点/钱包 VO")
|
||||
@Data
|
||||
public class AirlinePointVO {
|
||||
|
||||
@Schema(description = "当月飞行", example = "12")
|
||||
private String currentMonth;
|
||||
|
||||
@Schema(description = "年度飞行", example = "130")
|
||||
private String currentYear;
|
||||
|
||||
@Schema(description = "绩效积点", example = "08")
|
||||
private String jixiao;
|
||||
|
||||
@Schema(description = "延误积点", example = "00")
|
||||
private String yanwu;
|
||||
|
||||
@Schema(description = "当前积点", example = "260")
|
||||
private String currentPoint;
|
||||
|
||||
@Schema(description = "钱包余额", example = "1280.00")
|
||||
private String wallet;
|
||||
|
||||
}
|
||||
+21
@@ -49,6 +49,27 @@ public class AppAuthController {
|
||||
return success(authService.login(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/airline-login")
|
||||
@Operation(summary = "航空账号登录(手机 + 密码 + 设备唯一识别号)")
|
||||
@PermitAll
|
||||
public CommonResult<AppAuthLoginRespVO> airlineLogin(@RequestBody @Valid AppAuthAirlineLoginReqVO reqVO) {
|
||||
return success(authService.airlineLogin(reqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/airline-check")
|
||||
@Operation(summary = "航空账号会话校验(免登录,兼容新旧客户端:Token/手机号+渠道/设备识别号 任一定位)")
|
||||
@Parameter(name = "deviceId", description = "设备唯一识别号", required = true)
|
||||
@Parameter(name = "mobile", description = "手机号(新客户端传)")
|
||||
@Parameter(name = "channel", description = "品牌渠道 CZ/MU/HU(新客户端传)")
|
||||
@PermitAll
|
||||
public CommonResult<Boolean> airlineCheck(
|
||||
@RequestParam(value = "mobile", required = false) String mobile,
|
||||
@RequestParam("deviceId") String deviceId,
|
||||
@RequestParam(value = "channel", required = false) String channel) {
|
||||
authService.airlineCheck(getLoginUserId(), mobile, deviceId, channel);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
@Operation(summary = "登出系统")
|
||||
@PermitAll
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.Mobile;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@Schema(description = "用户 APP - 航空账号登录 Request VO")
|
||||
@Data
|
||||
public class AppAuthAirlineLoginReqVO {
|
||||
|
||||
@Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13800138000")
|
||||
@NotEmpty(message = "手机号不能为空")
|
||||
@Mobile
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "品牌渠道", requiredMode = Schema.RequiredMode.REQUIRED, example = "CZ")
|
||||
@NotEmpty(message = "品牌渠道不能为空")
|
||||
@Pattern(regexp = "CZ|MU|HU", message = "品牌渠道仅支持 CZ(南航)/ MU(东航)/ HU(海航)")
|
||||
private String channel;
|
||||
|
||||
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "手机唯一识别号(重装/重启后须保持不变)", requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
example = "android-id-or-keychain-uuid")
|
||||
@NotEmpty(message = "设备唯一识别号不能为空")
|
||||
@Length(min = 8, max = 128, message = "设备唯一识别号长度为 8-128 位")
|
||||
private String deviceId;
|
||||
|
||||
}
|
||||
+8
-1
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.member.client.vo.chat.friend.ChatAddFriendReqVO;
|
||||
import cn.iocoder.yudao.module.member.client.vo.chat.user.ChatPrivateMessageSendReqVO;
|
||||
import cn.iocoder.yudao.module.member.client.vo.chat.user.ChatPrivateMessageSendResVO;
|
||||
import cn.iocoder.yudao.module.member.service.opentype.OpenTypeService;
|
||||
import cn.iocoder.yudao.module.member.service.push.MemberPushService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -27,6 +28,8 @@ public class AppChatController {
|
||||
|
||||
@Resource
|
||||
private ChatApi chatApi;
|
||||
@Resource
|
||||
private MemberPushService memberPushService;
|
||||
|
||||
@PostMapping("/send-private-msg")
|
||||
@Operation(summary = "发送私聊消息(有会员限制)")
|
||||
@@ -35,7 +38,11 @@ public class AppChatController {
|
||||
openTypeService.isUserCanSendMessage(sendId, body.getRecvId());
|
||||
body.setSendId(sendId);
|
||||
body.setTerminal(1);
|
||||
return success(chatApi.send(body));
|
||||
ChatPrivateMessageSendResVO result = chatApi.send(body);
|
||||
if (result != null) {
|
||||
memberPushService.pushPrivateMessage(body);
|
||||
}
|
||||
return success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/add-friend")
|
||||
|
||||
+7
-6
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.member.controller.app.comauth.vo.CreateManAuthReq
|
||||
import cn.iocoder.yudao.module.member.controller.app.comauth.vo.CreateWechatAuthReqVo;
|
||||
import cn.iocoder.yudao.module.member.controller.app.comauth.vo.CreateWechatErrorAuthReqVo;
|
||||
import cn.iocoder.yudao.module.member.controller.app.comauth.vo.CreateWomanAuthReqVo;
|
||||
import cn.iocoder.yudao.module.member.client.vo.thirdfaceverify.ThirdFindFaceVerifyResVo;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.comauth.ComAuthDO;
|
||||
import cn.iocoder.yudao.module.member.service.comauth.ComAuthService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -47,12 +48,6 @@ public class AppComAuthController {
|
||||
return success(comAuthService.createWechatAuth(dto));
|
||||
}
|
||||
|
||||
@PostMapping("/create-man-auth")
|
||||
@Operation(summary = "创建男性认证")
|
||||
public CommonResult<ComAuthDO> createManAuth(@Valid @RequestBody CreateManAuthReqVo dto) {
|
||||
dto.setMemberId(getLoginUserId());
|
||||
return success(comAuthService.createManAuth(dto));
|
||||
}
|
||||
|
||||
@PostMapping("/create-woman-auth")
|
||||
@Operation(summary = "创建女性认证")
|
||||
@@ -81,4 +76,10 @@ public class AppComAuthController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/find-face-verify")
|
||||
@Operation(summary = "查询阿里云人脸认证结果(SDK 失败排查)")
|
||||
public CommonResult<ThirdFindFaceVerifyResVo> findFaceVerify(@RequestParam("authId") Long authId) {
|
||||
return success(comAuthService.findFaceVerify(authId, getLoginUserId()));
|
||||
}
|
||||
|
||||
}
|
||||
+1
-2
@@ -19,8 +19,7 @@ public class CreateWechatAuthReqVo {
|
||||
@NotEmpty(message = "微信号不能为空")
|
||||
private String wechatNumber;
|
||||
|
||||
@Schema(description = "微信二维码")
|
||||
@NotEmpty(message = "微信二维码不能为空")
|
||||
@Schema(description = "微信二维码(选填)")
|
||||
private String wechatQrcode;
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.interaction;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.member.controller.app.interaction.vo.AppInteractionRespVO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.interaction.vo.AppInteractionSummaryRespVO;
|
||||
import cn.iocoder.yudao.module.member.service.interaction.MemberInteractionService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Tag(name = "用户 App - 互动消息")
|
||||
@RestController
|
||||
@RequestMapping("/member/interaction")
|
||||
@Validated
|
||||
public class AppMemberInteractionController {
|
||||
|
||||
@Resource
|
||||
private MemberInteractionService interactionService;
|
||||
|
||||
@GetMapping("/summary")
|
||||
@Operation(summary = "互动消息摘要(未读数 + 最近一条)")
|
||||
public CommonResult<AppInteractionSummaryRespVO> getSummary() {
|
||||
return success(interactionService.getSummary(getLoginUserId()));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "互动消息分页")
|
||||
public CommonResult<PageResult<AppInteractionRespVO>> getPage(@Valid PageParam pageParam) {
|
||||
return success(interactionService.getPage(getLoginUserId(), pageParam));
|
||||
}
|
||||
|
||||
@PostMapping("/read-all")
|
||||
@Operation(summary = "全部标为已读")
|
||||
public CommonResult<Boolean> readAll() {
|
||||
interactionService.readAll(getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.interaction.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "用户 App - 互动消息 Response VO")
|
||||
@Data
|
||||
public class AppInteractionRespVO {
|
||||
|
||||
@Schema(description = "编号")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "类型:VIEWED / WECHAT_UNLOCKED")
|
||||
private String bizType;
|
||||
|
||||
@Schema(description = "文案,如「张三 查看了你」")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "操作人编号")
|
||||
private Long actorId;
|
||||
|
||||
@Schema(description = "操作人头像")
|
||||
private String actorAvatar;
|
||||
|
||||
@Schema(description = "操作人昵称")
|
||||
private String actorNickname;
|
||||
|
||||
@Schema(description = "是否已读")
|
||||
private Boolean readStatus;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.interaction.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "用户 App - 互动消息摘要 Response VO")
|
||||
@Data
|
||||
public class AppInteractionSummaryRespVO {
|
||||
|
||||
@Schema(description = "未读数")
|
||||
private Long unreadCount;
|
||||
|
||||
@Schema(description = "最近一条文案")
|
||||
private String lastContent;
|
||||
|
||||
@Schema(description = "最近一条时间")
|
||||
private LocalDateTime lastTime;
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.push;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.member.controller.app.push.vo.AppPushBindReqVO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.push.vo.AppPushUnbindReqVO;
|
||||
import cn.iocoder.yudao.module.member.service.push.MemberPushDeviceService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Tag(name = "用户 App - 推送设备")
|
||||
@RestController
|
||||
@RequestMapping("/member/push")
|
||||
@Validated
|
||||
public class AppMemberPushController {
|
||||
|
||||
@Resource
|
||||
private MemberPushDeviceService pushDeviceService;
|
||||
|
||||
@PostMapping("/bind")
|
||||
@Operation(summary = "绑定推送设备(极光 RegistrationID)")
|
||||
public CommonResult<Boolean> bind(@Valid @RequestBody AppPushBindReqVO reqVO) {
|
||||
pushDeviceService.bind(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/unbind")
|
||||
@Operation(summary = "解绑推送设备")
|
||||
public CommonResult<Boolean> unbind(@Valid @RequestBody AppPushUnbindReqVO reqVO) {
|
||||
pushDeviceService.unbind(getLoginUserId(), reqVO.getRegistrationId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.push.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "用户 App - 推送设备绑定 Request VO")
|
||||
@Data
|
||||
public class AppPushBindReqVO {
|
||||
|
||||
@Schema(description = "极光 RegistrationID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1a2b3c")
|
||||
@NotBlank(message = "registrationId 不能为空")
|
||||
private String registrationId;
|
||||
|
||||
@Schema(description = "极光别名", example = "u1024")
|
||||
private String alias;
|
||||
|
||||
@Schema(description = "平台:ios / android", requiredMode = Schema.RequiredMode.REQUIRED, example = "android")
|
||||
@NotBlank(message = "platform 不能为空")
|
||||
private String platform;
|
||||
|
||||
@Schema(description = "推送服务商", example = "jpush")
|
||||
private String provider;
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.push.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "用户 App - 推送设备解绑 Request VO")
|
||||
@Data
|
||||
public class AppPushUnbindReqVO {
|
||||
|
||||
@Schema(description = "极光 RegistrationID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1a2b3c")
|
||||
@NotBlank(message = "registrationId 不能为空")
|
||||
private String registrationId;
|
||||
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.rideplan;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.member.controller.app.rideplan.vo.AppRidePlanCreateReqVO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.rideplan.vo.AppRidePlanPageReqVO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.rideplan.vo.AppRidePlanRespVO;
|
||||
import cn.iocoder.yudao.module.member.service.rideplan.MemberRidePlanService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
/**
|
||||
* 骑遇发车局:
|
||||
* - 查询(page/get)@PermitAll 免登录,游客可浏览, RespVO 已做字段脱敏(只暴露昵称/头像)
|
||||
* - 发布/加入/取消加入/举报 需登录(/app-api/** 默认拦截,未带有效 token 返回 401)
|
||||
*/
|
||||
@Tag(name = "用户 APP - 骑遇发车局")
|
||||
@RestController
|
||||
@RequestMapping("/member/ride-plan")
|
||||
@Validated
|
||||
public class AppRidePlanController {
|
||||
|
||||
@Resource
|
||||
private MemberRidePlanService ridePlanService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "发车局分页(免登录)", description = "游客可查;登录后返回 joinedByMe")
|
||||
@PermitAll
|
||||
public CommonResult<PageResult<AppRidePlanRespVO>> page(@Valid AppRidePlanPageReqVO reqVO) {
|
||||
return success(ridePlanService.getRidePlanPage(reqVO, getLoginUserId()));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "发车局详情(免登录)")
|
||||
@Parameter(name = "id", description = "发车局编号", required = true, example = "1")
|
||||
@PermitAll
|
||||
public CommonResult<AppRidePlanRespVO> get(@RequestParam("id") Long id) {
|
||||
return success(ridePlanService.getRidePlan(id, getLoginUserId()));
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "发布发车局(需登录)")
|
||||
public CommonResult<Long> create(@Valid @RequestBody AppRidePlanCreateReqVO reqVO) {
|
||||
return success(ridePlanService.createRidePlan(getLoginUserId(), reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/join")
|
||||
@Operation(summary = "加入发车局(需登录)")
|
||||
@Parameter(name = "planId", description = "发车局编号", required = true, example = "1")
|
||||
public CommonResult<Boolean> join(@RequestParam("planId") Long planId) {
|
||||
ridePlanService.joinRidePlan(getLoginUserId(), planId);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/cancel-join")
|
||||
@Operation(summary = "取消加入发车局(需登录)")
|
||||
@Parameter(name = "planId", description = "发车局编号", required = true, example = "1")
|
||||
public CommonResult<Boolean> cancelJoin(@RequestParam("planId") Long planId) {
|
||||
ridePlanService.cancelJoinRidePlan(getLoginUserId(), planId);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/report")
|
||||
@Operation(summary = "举报发车局(需登录),达到阈值自动下架")
|
||||
@Parameter(name = "planId", description = "发车局编号", required = true, example = "1")
|
||||
public CommonResult<Boolean> report(@RequestParam("planId") Long planId) {
|
||||
ridePlanService.reportRidePlan(getLoginUserId(), planId);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.rideplan;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.member.controller.app.rideplan.vo.AppRideRecordCreateReqVO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.rideplan.vo.AppRideRecordPageReqVO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.rideplan.vo.AppRideRecordRespVO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.rideplan.vo.AppRideRecordStatsRespVO;
|
||||
import cn.iocoder.yudao.module.member.service.rideplan.MemberRideRecordService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
/**
|
||||
* 骑遇骑行记录(战报):
|
||||
* - 战报墙 page / 详情 get / 天梯榜 rank @PermitAll 免登录,游客可浏览,RespVO 已做字段脱敏(只暴露昵称/头像)
|
||||
* - 上报 create / 点赞 like / 举报 report 需登录(/app-api/** 默认拦截,未带有效 token 返回 401)
|
||||
*/
|
||||
@Tag(name = "用户 APP - 骑遇骑行记录")
|
||||
@RestController
|
||||
@RequestMapping("/member/ride-record")
|
||||
@Validated
|
||||
public class AppRideRecordController {
|
||||
|
||||
@Resource
|
||||
private MemberRideRecordService rideRecordService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "上报骑行战报(需登录)", description = "完骑后提交;cornerName 有值则参与天梯榜")
|
||||
public CommonResult<Long> create(@Valid @RequestBody AppRideRecordCreateReqVO reqVO) {
|
||||
return success(rideRecordService.createRideRecord(getLoginUserId(), reqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "战报墙分页(免登录)", description = "游客可查;登录后返回 likedByMe")
|
||||
@PermitAll
|
||||
public CommonResult<PageResult<AppRideRecordRespVO>> page(@Valid AppRideRecordPageReqVO reqVO) {
|
||||
return success(rideRecordService.getRideRecordPage(reqVO, getLoginUserId()));
|
||||
}
|
||||
|
||||
@GetMapping("/my")
|
||||
@Operation(summary = "我的骑行记录分页(需登录)")
|
||||
public CommonResult<PageResult<AppRideRecordRespVO>> my(@Valid AppRideRecordPageReqVO reqVO) {
|
||||
return success(rideRecordService.getMyRideRecordPage(getLoginUserId(), reqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "战报详情(免登录,含轨迹)")
|
||||
@Parameter(name = "id", description = "战报编号", required = true, example = "1")
|
||||
@PermitAll
|
||||
public CommonResult<AppRideRecordRespVO> get(@RequestParam("id") Long id) {
|
||||
return success(rideRecordService.getRideRecord(id, getLoginUserId()));
|
||||
}
|
||||
|
||||
@GetMapping("/rank")
|
||||
@Operation(summary = "弯道天梯榜(免登录)", description = "按路线+弯道分组,组内按压弯角度降序;支持过滤")
|
||||
@PermitAll
|
||||
public CommonResult<List<AppRideRecordRespVO.CornerBoard>> rank(
|
||||
@RequestParam(value = "roadName", required = false)
|
||||
@Size(max = 50, message = "路线名长度不能超过 50") String roadName,
|
||||
@RequestParam(value = "cornerName", required = false)
|
||||
@Size(max = 50, message = "弯道名长度不能超过 50") String cornerName) {
|
||||
return success(rideRecordService.getCornerRank(roadName, cornerName));
|
||||
}
|
||||
|
||||
@GetMapping("/stats")
|
||||
@Operation(summary = "批量骑手战报统计(免登录)", description = "返回每个骑手的战报数/最佳压弯/总里程;最多 100 人")
|
||||
@PermitAll
|
||||
public CommonResult<List<AppRideRecordStatsRespVO>> stats(
|
||||
@RequestParam("userIds") @Size(max = 100, message = "一次最多查询 100 个骑手") List<Long> userIds) {
|
||||
return success(rideRecordService.getRideRecordStats(userIds));
|
||||
}
|
||||
|
||||
@PostMapping("/like")
|
||||
@Operation(summary = "点赞/取消点赞(需登录)", description = "幂等切换,返回操作后是否处于点赞状态")
|
||||
@Parameter(name = "recordId", description = "战报编号", required = true, example = "1")
|
||||
public CommonResult<Boolean> like(@RequestParam("recordId") Long recordId) {
|
||||
return success(rideRecordService.toggleLike(getLoginUserId(), recordId));
|
||||
}
|
||||
|
||||
@PostMapping("/report")
|
||||
@Operation(summary = "举报战报(需登录)", description = "达到阈值自动下架")
|
||||
@Parameter(name = "recordId", description = "战报编号", required = true, example = "1")
|
||||
public CommonResult<Boolean> report(@RequestParam("recordId") Long recordId) {
|
||||
rideRecordService.reportRideRecord(getLoginUserId(), recordId);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.rideplan;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.member.controller.app.rideplan.vo.AppRideTagRespVO;
|
||||
import cn.iocoder.yudao.module.member.controller.app.rideplan.vo.AppRideUserTagUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.member.service.rideplan.MemberRidePlanService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
/**
|
||||
* 骑遇骑行标签:
|
||||
* - 标签字典 list @PermitAll 免登录(发局选标签、列表展示用)
|
||||
* - 我的标签读写 需登录
|
||||
*/
|
||||
@Tag(name = "用户 APP - 骑遇骑行标签")
|
||||
@RestController
|
||||
@RequestMapping("/member/ride-tag")
|
||||
@Validated
|
||||
public class AppRideTagController {
|
||||
|
||||
@Resource
|
||||
private MemberRidePlanService ridePlanService;
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "骑行标签字典(免登录,只返回启用的)")
|
||||
@PermitAll
|
||||
public CommonResult<List<AppRideTagRespVO>> list() {
|
||||
return success(ridePlanService.getRideTagList());
|
||||
}
|
||||
|
||||
@GetMapping("/my")
|
||||
@Operation(summary = "我的骑行标签(需登录)")
|
||||
public CommonResult<List<AppRideTagRespVO>> my() {
|
||||
return success(ridePlanService.getMyRideTags(getLoginUserId()));
|
||||
}
|
||||
|
||||
@PutMapping("/my")
|
||||
@Operation(summary = "全量替换我的骑行标签(需登录)")
|
||||
public CommonResult<Boolean> updateMy(@Valid @RequestBody AppRideUserTagUpdateReqVO reqVO) {
|
||||
ridePlanService.updateMyRideTags(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.rideplan.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "用户 APP - 发布发车局 Request VO")
|
||||
@Data
|
||||
public class AppRidePlanCreateReqVO {
|
||||
|
||||
@Schema(description = "路线名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "南昆山经典环线")
|
||||
@NotBlank(message = "路线名称不能为空")
|
||||
@Size(max = 100, message = "路线名称长度不能超过 100")
|
||||
private String routeName;
|
||||
|
||||
@Schema(description = "集合地点", requiredMode = Schema.RequiredMode.REQUIRED, example = "增城广场加油站")
|
||||
@NotBlank(message = "集合地点不能为空")
|
||||
@Size(max = 200, message = "集合地点长度不能超过 200")
|
||||
private String meetPlace;
|
||||
|
||||
@Schema(description = "集合时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "集合时间不能为空")
|
||||
private LocalDateTime meetTime;
|
||||
|
||||
@Schema(description = "全程约多少公里", example = "50")
|
||||
@DecimalMin(value = "0.0", message = "公里数不能为负数")
|
||||
private BigDecimal distanceKm;
|
||||
|
||||
@Schema(description = "城市(同城过滤)", example = "广州")
|
||||
@Size(max = 64, message = "城市长度不能超过 64")
|
||||
private String city;
|
||||
|
||||
@Schema(description = "标签名列表", example = "[\"跑山\",\"新手友好\"]")
|
||||
@Size(max = 5, message = "最多选择 5 个标签")
|
||||
private List<String> tags;
|
||||
|
||||
@Schema(description = "人数上限", example = "10")
|
||||
@NotNull(message = "人数上限不能为空")
|
||||
@Min(value = 2, message = "人数上限最少 2 人")
|
||||
@Max(value = 100, message = "人数上限最多 100 人")
|
||||
private Integer capacity;
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.rideplan.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
@Schema(description = "用户 APP - 发车局分页查询 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class AppRidePlanPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "城市(同城过滤)", example = "广州")
|
||||
@Size(max = 64, message = "城市长度不能超过 64")
|
||||
private String city;
|
||||
|
||||
@Schema(description = "关键词,匹配路线名称", example = "南昆山")
|
||||
@Size(max = 100, message = "关键词长度不能超过 100")
|
||||
private String keyword;
|
||||
|
||||
@Schema(description = "标签名,精确匹配", example = "跑山")
|
||||
@Size(max = 32, message = "标签长度不能超过 32")
|
||||
private String tag;
|
||||
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.rideplan.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "用户 APP - 发车局 Response VO(免登录可查,已做字段脱敏)")
|
||||
@Data
|
||||
public class AppRidePlanRespVO {
|
||||
|
||||
@Schema(description = "发车局编号", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "路线名称", example = "南昆山经典环线")
|
||||
private String routeName;
|
||||
|
||||
@Schema(description = "集合地点", example = "增城广场加油站")
|
||||
private String meetPlace;
|
||||
|
||||
@Schema(description = "集合时间")
|
||||
private LocalDateTime meetTime;
|
||||
|
||||
@Schema(description = "全程约多少公里", example = "50")
|
||||
private BigDecimal distanceKm;
|
||||
|
||||
@Schema(description = "城市", example = "广州")
|
||||
private String city;
|
||||
|
||||
@Schema(description = "标签名列表", example = "[\"跑山\",\"新手友好\"]")
|
||||
private List<String> tags;
|
||||
|
||||
@Schema(description = "人数上限", example = "10")
|
||||
private Integer capacity;
|
||||
|
||||
@Schema(description = "已报名人数", example = "3")
|
||||
private Integer joinCount;
|
||||
|
||||
// —— 发起人(只暴露昵称/头像,不含手机号等敏感字段)——
|
||||
|
||||
@Schema(description = "发起人编号", example = "1024")
|
||||
private Long hostUserId;
|
||||
|
||||
@Schema(description = "发起人昵称", example = "山路信天游")
|
||||
private String hostNickname;
|
||||
|
||||
@Schema(description = "发起人头像")
|
||||
private String hostAvatar;
|
||||
|
||||
@Schema(description = "已报名骑手列表(昵称 + 头像)")
|
||||
private List<JoinUser> joinedUsers;
|
||||
|
||||
@Schema(description = "当前登录用户是否已加入(游客恒为 false)")
|
||||
private Boolean joinedByMe;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "已报名骑手(只暴露昵称 + 头像)")
|
||||
@Data
|
||||
public static class JoinUser {
|
||||
|
||||
@Schema(description = "用户编号", example = "2048")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "昵称", example = "压弯不压车")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "头像")
|
||||
private String avatar;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.rideplan.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.DecimalMax;
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "用户 APP - 骑行战报上报 Request VO(完骑后提交,需登录)")
|
||||
@Data
|
||||
public class AppRideRecordCreateReqVO {
|
||||
|
||||
@Schema(description = "战报标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "南昆山 63 连弯,今天左弯终于不虚了")
|
||||
@NotBlank(message = "战报标题不能为空")
|
||||
@Size(max = 100, message = "战报标题不能超过 100 字")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "路线名(可选)", example = "南昆山")
|
||||
@Size(max = 50, message = "路线名不能超过 50 字")
|
||||
private String roadName;
|
||||
|
||||
@Schema(description = "弯道名(可选,有值参与天梯榜)", example = "观音潭右弯")
|
||||
@Size(max = 50, message = "弯道名不能超过 50 字")
|
||||
private String cornerName;
|
||||
|
||||
@Schema(description = "里程(公里)", requiredMode = Schema.RequiredMode.REQUIRED, example = "87.3")
|
||||
@NotNull(message = "里程不能为空")
|
||||
@DecimalMin(value = "0.1", message = "里程至少 0.1 公里")
|
||||
@DecimalMax(value = "5000", message = "里程异常")
|
||||
private BigDecimal distanceKm;
|
||||
|
||||
@Schema(description = "骑行时长(秒)", requiredMode = Schema.RequiredMode.REQUIRED, example = "5760")
|
||||
@NotNull(message = "骑行时长不能为空")
|
||||
@Min(value = 30, message = "骑行时长太短")
|
||||
private Integer durationSec;
|
||||
|
||||
@Schema(description = "极速(km/h)", requiredMode = Schema.RequiredMode.REQUIRED, example = "138")
|
||||
@NotNull(message = "极速不能为空")
|
||||
@DecimalMin(value = "0", message = "极速不能为负")
|
||||
@DecimalMax(value = "500", message = "极速异常")
|
||||
private BigDecimal topSpeedKmh;
|
||||
|
||||
@Schema(description = "过弯次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "63")
|
||||
@NotNull(message = "过弯次数不能为空")
|
||||
@Min(value = 0, message = "过弯次数不能为负")
|
||||
@Max(value = 10000, message = "过弯次数异常")
|
||||
private Integer curveCount;
|
||||
|
||||
@Schema(description = "最大压弯角度(度)", requiredMode = Schema.RequiredMode.REQUIRED, example = "47.2")
|
||||
@NotNull(message = "最大压弯角度不能为空")
|
||||
@DecimalMin(value = "0", message = "压弯角度不能为负")
|
||||
@DecimalMax(value = "90", message = "压弯角度异常")
|
||||
private BigDecimal maxLean;
|
||||
|
||||
@Schema(description = "轨迹点 JSON:[{\"lat\":23.1,\"lng\":113.3,\"speed\":88}],可选,仅详情接口回显")
|
||||
@Size(max = 2_000_000, message = "轨迹数据过大")
|
||||
private String trackJson;
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.rideplan.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
@Schema(description = "用户 APP - 骑行战报分页查询 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class AppRideRecordPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "按骑手过滤(查某人战报)", example = "1024")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "路线名过滤", example = "南昆山")
|
||||
@Size(max = 50, message = "路线名长度不能超过 50")
|
||||
private String roadName;
|
||||
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.rideplan.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "用户 APP - 骑行战报 Response VO(列表不含轨迹,详情含)")
|
||||
@Data
|
||||
public class AppRideRecordRespVO {
|
||||
|
||||
@Schema(description = "战报编号", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "战报标题", example = "南昆山 63 连弯,今天左弯终于不虚了")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "路线名", example = "南昆山")
|
||||
private String roadName;
|
||||
|
||||
@Schema(description = "弯道名", example = "观音潭右弯")
|
||||
private String cornerName;
|
||||
|
||||
@Schema(description = "里程(公里)", example = "87.3")
|
||||
private BigDecimal distanceKm;
|
||||
|
||||
@Schema(description = "骑行时长(秒)", example = "5760")
|
||||
private Integer durationSec;
|
||||
|
||||
@Schema(description = "极速(km/h)", example = "138")
|
||||
private BigDecimal topSpeedKmh;
|
||||
|
||||
@Schema(description = "过弯次数", example = "63")
|
||||
private Integer curveCount;
|
||||
|
||||
@Schema(description = "最大压弯角度(度)", example = "47.2")
|
||||
private BigDecimal maxLean;
|
||||
|
||||
@Schema(description = "轨迹点 JSON,仅详情接口返回")
|
||||
private String trackJson;
|
||||
|
||||
@Schema(description = "点赞数", example = "128")
|
||||
private Integer likeCount;
|
||||
|
||||
@Schema(description = "当前登录用户是否已点赞(游客恒为 false)")
|
||||
private Boolean likedByMe;
|
||||
|
||||
// —— 骑手(只暴露昵称/头像,不含手机号等敏感字段)——
|
||||
|
||||
@Schema(description = "骑手编号", example = "1024")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "骑手昵称", example = "山路信天游")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "骑手头像")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "发布时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "天梯榜条目(rank 接口专用)")
|
||||
@Data
|
||||
public static class RankItem {
|
||||
|
||||
@Schema(description = "名次,从 1 开始", example = "1")
|
||||
private Integer rank;
|
||||
|
||||
@Schema(description = "战报编号", example = "5")
|
||||
private Long recordId;
|
||||
|
||||
@Schema(description = "路线名", example = "南昆山")
|
||||
private String roadName;
|
||||
|
||||
@Schema(description = "弯道名", example = "观音潭右弯")
|
||||
private String cornerName;
|
||||
|
||||
@Schema(description = "最大压弯角度(度)", example = "52.0")
|
||||
private BigDecimal maxLean;
|
||||
|
||||
@Schema(description = "极速(km/h)", example = "62")
|
||||
private BigDecimal topSpeedKmh;
|
||||
|
||||
@Schema(description = "骑手编号", example = "1024")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "骑手昵称", example = "压弯不压车")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "骑手头像")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "骑行时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "弯道榜单(rank 接口返回,按弯道分组)")
|
||||
@Data
|
||||
public static class CornerBoard {
|
||||
|
||||
@Schema(description = "路线名", example = "南昆山")
|
||||
private String roadName;
|
||||
|
||||
@Schema(description = "弯道名", example = "观音潭右弯")
|
||||
private String cornerName;
|
||||
|
||||
@Schema(description = "该弯道上榜条目(已按压弯角度降序)")
|
||||
private List<RankItem> entries;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.rideplan.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 用户 APP - 骑手战报统计 Response VO
|
||||
*
|
||||
* 供首页骑友列表 / 骑友主页展示「战报数 / 最佳压弯 / 总里程」骑行数据行,
|
||||
* 批量接口避免列表逐人查询。
|
||||
*/
|
||||
@Schema(description = "用户 APP - 骑手战报统计 Response VO")
|
||||
@Data
|
||||
public class AppRideRecordStatsRespVO {
|
||||
|
||||
@Schema(description = "骑手用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "战报数量(正常状态)", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
|
||||
private Long reportCount;
|
||||
|
||||
@Schema(description = "最佳压弯角度(度)", requiredMode = Schema.RequiredMode.REQUIRED, example = "38.5")
|
||||
private Double bestLean;
|
||||
|
||||
@Schema(description = "总里程(公里)", requiredMode = Schema.RequiredMode.REQUIRED, example = "260.8")
|
||||
private Double totalKm;
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.rideplan.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "用户 APP - 骑行标签 Response VO")
|
||||
@Data
|
||||
public class AppRideTagRespVO {
|
||||
|
||||
@Schema(description = "标签编号", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "标签名", example = "跑山党")
|
||||
private String name;
|
||||
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.rideplan.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "用户 APP - 设置我的骑行标签 Request VO")
|
||||
@Data
|
||||
public class AppRideUserTagUpdateReqVO {
|
||||
|
||||
@Schema(description = "标签编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
|
||||
@NotEmpty(message = "标签编号列表不能为空")
|
||||
private List<Long> tagIds;
|
||||
|
||||
}
|
||||
+18
@@ -1,6 +1,7 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.user;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.member.controller.app.comauth.vo.CreateWechatAuthReqVo;
|
||||
import cn.iocoder.yudao.module.member.service.comauth.ComAuthService;
|
||||
@@ -16,6 +17,8 @@ import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
|
||||
import cn.iocoder.yudao.module.member.service.opentype.OpenTypeService;
|
||||
import cn.iocoder.yudao.module.member.service.user.MemberRecentTagService;
|
||||
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
||||
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -28,8 +31,10 @@ import javax.validation.Valid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
import static cn.iocoder.yudao.module.member.enums.MemberErrorCode.AUTH_LOGIN_USER_DISABLED;
|
||||
|
||||
@Tag(name = "用户 APP - 用户个人中心")
|
||||
@RestController
|
||||
@@ -50,11 +55,24 @@ public class AppMemberUserController {
|
||||
private ComAuthService comAuthService;
|
||||
@Resource
|
||||
private LocationService locationService;
|
||||
@Resource
|
||||
private OAuth2TokenApi oauth2TokenApi;
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得基本信息")
|
||||
public CommonResult<AppMemberUserInfoRespVO> getUserInfo() {
|
||||
MemberUserDO user = userService.getUser(getLoginUserId());
|
||||
// 禁用态已由 MemberUserDisableInterceptor 统一拦截;此处再兜底一次
|
||||
if (user == null || CommonStatusEnum.isDisable(user.getStatus())) {
|
||||
if (user != null) {
|
||||
try {
|
||||
oauth2TokenApi.removeAccessTokenByUserId(user.getId(), UserTypeEnum.MEMBER.getValue());
|
||||
} catch (Exception e) {
|
||||
log.warn("禁用用户拉资料时清 token 失败, userId={}", user.getId(), e);
|
||||
}
|
||||
}
|
||||
throw exception(AUTH_LOGIN_USER_DISABLED);
|
||||
}
|
||||
MemberLevelDO level = levelService.getLevel(user.getLevelId());
|
||||
AppMemberUserInfoRespVO result = MemberUserConvert.INSTANCE.convert(user, level);
|
||||
// 获取用户tag
|
||||
|
||||
+5
-5
@@ -82,11 +82,11 @@ public class AppViewController {
|
||||
put("search-action", "SQUARE_VIP_MALE");
|
||||
put("label", "VIP男士");
|
||||
}});
|
||||
menu.add(new HashMap<String, String>() {{
|
||||
put("code", "5000");
|
||||
put("search-action", "SQUARE_FEMALE");
|
||||
put("label", "女生");
|
||||
}});
|
||||
// menu.add(new HashMap<String, String>() {{
|
||||
// put("code", "5000");
|
||||
// put("search-action", "SQUARE_FEMALE");
|
||||
// put("label", "女生");
|
||||
// }});
|
||||
}
|
||||
return success(menu);
|
||||
}
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package cn.iocoder.yudao.module.member.dal.dataobject.airline;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 航空账号 DO
|
||||
*/
|
||||
@TableName("member_airline_account")
|
||||
@KeySequence("member_airline_account_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberAirlineAccountDO extends TenantBaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
/** 手机号(同一手机号可在不同渠道各有一个账号) */
|
||||
private String mobile;
|
||||
/** 品牌渠道:CZ 南航 / MU 东航 / HU 海航 */
|
||||
private String channel;
|
||||
/** 密码(BCrypt) */
|
||||
private String password;
|
||||
/** 生效开始时间 */
|
||||
private LocalDateTime startTime;
|
||||
/** 生效结束时间 */
|
||||
private LocalDateTime endTime;
|
||||
/**
|
||||
* 手机唯一识别号。首次登录绑定;客户端需保证重装/重启后不变(如 Android ANDROID_ID、iOS Keychain UUID)。
|
||||
*/
|
||||
private String deviceId;
|
||||
/**
|
||||
* 状态:0 开启 1 关闭,见 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
/** 关联会员用户编号 */
|
||||
private Long userId;
|
||||
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package cn.iocoder.yudao.module.member.dal.dataobject.airline;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 航空 App 用户自建航线 DO
|
||||
*/
|
||||
@TableName("member_airline_flight")
|
||||
@KeySequence("member_airline_flight_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberAirlineFlightDO extends TenantBaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
/** 航空账号编号 member_airline_account.id */
|
||||
private Long accountId;
|
||||
/** 航班号 */
|
||||
private String flightNo;
|
||||
/** 任务/航班名称 */
|
||||
private String title;
|
||||
/** 出发城市 */
|
||||
private String beginCity;
|
||||
/** 出发机场 */
|
||||
private String beginAirport;
|
||||
/** 到达城市 */
|
||||
private String endCity;
|
||||
/** 到达机场 */
|
||||
private String endAirport;
|
||||
/** 舱位/班次 */
|
||||
private String classes;
|
||||
/** 状态 */
|
||||
private String status;
|
||||
/** 计划起飞时间 */
|
||||
private String flyBeginDate0;
|
||||
/** 计划到达时间 */
|
||||
private String flyEndDate0;
|
||||
/** 实际起飞时间 */
|
||||
private String flyBeginDate1;
|
||||
/** 实际到达时间 */
|
||||
private String flyEndDate1;
|
||||
/** 签到时间 */
|
||||
private String signInTime;
|
||||
/** 机号/编号 */
|
||||
private String aircraftNo;
|
||||
/** 航司 */
|
||||
private String airline;
|
||||
/** 机型 */
|
||||
private String aircraftType;
|
||||
/** 登机口 */
|
||||
private String gate;
|
||||
/** 出发机场停机位 */
|
||||
private String beginStand;
|
||||
/** 到达机场停机位 */
|
||||
private String endStand;
|
||||
/** 货关时间 */
|
||||
private String cargoClose;
|
||||
/** 客关时间 */
|
||||
private String paxClose;
|
||||
/** 离港时间 */
|
||||
private String leavePort;
|
||||
/** 起飞时间 */
|
||||
private String takeOff;
|
||||
/** 落地时间 */
|
||||
private String land;
|
||||
/** 到港时间 */
|
||||
private String arrivePort;
|
||||
/** 前序航班 */
|
||||
private String prevFlight;
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package cn.iocoder.yudao.module.member.dal.dataobject.airline;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 航空 App 我的积点/钱包 DO(每账号一行)
|
||||
*/
|
||||
@TableName("member_airline_point")
|
||||
@KeySequence("member_airline_point_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberAirlinePointDO extends TenantBaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
/** 航空账号编号 member_airline_account.id */
|
||||
private Long accountId;
|
||||
/** 当月飞行 */
|
||||
private String currentMonth;
|
||||
/** 年度飞行 */
|
||||
private String currentYear;
|
||||
/** 绩效积点 */
|
||||
private String jixiao;
|
||||
/** 延误积点 */
|
||||
private String yanwu;
|
||||
/** 当前积点 */
|
||||
private String currentPoint;
|
||||
/** 钱包余额 */
|
||||
private String wallet;
|
||||
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package cn.iocoder.yudao.module.member.dal.dataobject.interaction;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 会员互动消息 DO
|
||||
*/
|
||||
@TableName("member_interaction")
|
||||
@KeySequence("member_interaction_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberInteractionDO extends TenantBaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 接收人(被查看 / 被解锁)
|
||||
*/
|
||||
private Long receiverId;
|
||||
|
||||
/**
|
||||
* 操作人
|
||||
*/
|
||||
private Long actorId;
|
||||
|
||||
/**
|
||||
* VIEWED / WECHAT_UNLOCKED
|
||||
*/
|
||||
private String bizType;
|
||||
|
||||
/**
|
||||
* 关联业务 id
|
||||
*/
|
||||
private Long refId;
|
||||
|
||||
/**
|
||||
* 是否已读
|
||||
*/
|
||||
private Boolean readStatus;
|
||||
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package cn.iocoder.yudao.module.member.dal.dataobject.push;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 会员推送设备 DO
|
||||
*/
|
||||
@TableName("member_push_device")
|
||||
@KeySequence("member_push_device_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberPushDeviceDO extends TenantBaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 会员编号
|
||||
*/
|
||||
private Long memberId;
|
||||
|
||||
/**
|
||||
* 极光 RegistrationID
|
||||
*/
|
||||
private String registrationId;
|
||||
|
||||
/**
|
||||
* 极光别名
|
||||
*/
|
||||
private String alias;
|
||||
|
||||
/**
|
||||
* 平台:ios / android
|
||||
*/
|
||||
private String platform;
|
||||
|
||||
/**
|
||||
* 推送服务商:jpush
|
||||
*/
|
||||
private String provider;
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package cn.iocoder.yudao.module.member.dal.dataobject.rideplan;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 骑遇发车局 DO
|
||||
*/
|
||||
@TableName("member_ride_plan")
|
||||
@KeySequence("member_ride_plan_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberRidePlanDO extends TenantBaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
/** 发起人编号 member_user.id */
|
||||
private Long userId;
|
||||
/** 路线名称 */
|
||||
private String routeName;
|
||||
/** 集合地点 */
|
||||
private String meetPlace;
|
||||
/** 集合时间 */
|
||||
private LocalDateTime meetTime;
|
||||
/** 全程约多少公里 */
|
||||
private BigDecimal distanceKm;
|
||||
/** 城市(同城过滤) */
|
||||
private String city;
|
||||
/** 标签名,逗号分隔 */
|
||||
private String tags;
|
||||
/** 人数上限 */
|
||||
private Integer capacity;
|
||||
/** 已报名人数(冗余计数) */
|
||||
private Integer joinCount;
|
||||
/** 被举报次数 */
|
||||
private Integer reportCount;
|
||||
/**
|
||||
* 状态:0 正常 1 已取消/下架
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package cn.iocoder.yudao.module.member.dal.dataobject.rideplan;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 骑遇发车局报名 DO
|
||||
*/
|
||||
@TableName("member_ride_plan_join")
|
||||
@KeySequence("member_ride_plan_join_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberRidePlanJoinDO extends TenantBaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
/** 发车局编号 member_ride_plan.id */
|
||||
private Long planId;
|
||||
/** 报名人编号 member_user.id */
|
||||
private Long userId;
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package cn.iocoder.yudao.module.member.dal.dataobject.rideplan;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 骑遇骑行记录(战报)DO
|
||||
*/
|
||||
@TableName("member_ride_record")
|
||||
@KeySequence("member_ride_record_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberRideRecordDO extends TenantBaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
/** 骑手编号 member_user.id */
|
||||
private Long userId;
|
||||
/** 战报标题 */
|
||||
private String title;
|
||||
/** 路线名(如:南昆山) */
|
||||
private String roadName;
|
||||
/** 弯道名(如:观音潭右弯),为空表示非跑山记录 */
|
||||
private String cornerName;
|
||||
/** 里程(公里) */
|
||||
private BigDecimal distanceKm;
|
||||
/** 骑行时长(秒) */
|
||||
private Integer durationSec;
|
||||
/** 极速(km/h) */
|
||||
private BigDecimal topSpeedKmh;
|
||||
/** 过弯次数 */
|
||||
private Integer curveCount;
|
||||
/** 最大压弯角度(度) */
|
||||
private BigDecimal maxLean;
|
||||
/** 轨迹点 JSON([{"lat":..,"lng":..,"speed":..}]),仅详情接口返回 */
|
||||
private String trackJson;
|
||||
/** 点赞数(冗余计数) */
|
||||
private Integer likeCount;
|
||||
/** 被举报次数 */
|
||||
private Integer reportCount;
|
||||
/**
|
||||
* 状态:0 正常 1 已下架
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package cn.iocoder.yudao.module.member.dal.dataobject.rideplan;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 骑行战报点赞 DO(一条记录一个用户只能赞一次,重复点赞视为取消)
|
||||
*/
|
||||
@TableName("member_ride_record_like")
|
||||
@KeySequence("member_ride_record_like_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberRideRecordLikeDO extends TenantBaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
/** 战报编号 member_ride_record.id */
|
||||
private Long recordId;
|
||||
/** 点赞人编号 member_user.id */
|
||||
private Long userId;
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package cn.iocoder.yudao.module.member.dal.dataobject.rideplan;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 骑遇骑行标签字典 DO(跑山党 / 新手友好 / 摩旅组队 ...)
|
||||
*/
|
||||
@TableName("member_ride_tag")
|
||||
@KeySequence("member_ride_tag_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberRideTagDO extends TenantBaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
/** 标签名 */
|
||||
private String name;
|
||||
/** 显示顺序(越小越靠前) */
|
||||
private Integer sort;
|
||||
/**
|
||||
* 状态:0 启用 1 停用,见 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package cn.iocoder.yudao.module.member.dal.dataobject.rideplan;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 骑手个人骑行标签关联 DO(Rider.tags)
|
||||
*/
|
||||
@TableName("member_ride_user_tag")
|
||||
@KeySequence("member_ride_user_tag_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberRideUserTagDO extends TenantBaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
/** 用户编号 member_user.id */
|
||||
private Long userId;
|
||||
/** 标签编号 member_ride_tag.id */
|
||||
private Long tagId;
|
||||
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package cn.iocoder.yudao.module.member.dal.mysql.airline;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.member.controller.admin.airline.vo.MemberAirlineAccountPageReqVO;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.airline.MemberAirlineAccountDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface MemberAirlineAccountMapper extends BaseMapperX<MemberAirlineAccountDO> {
|
||||
|
||||
default PageResult<MemberAirlineAccountDO> selectPage(MemberAirlineAccountPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<MemberAirlineAccountDO>()
|
||||
.likeIfPresent(MemberAirlineAccountDO::getMobile, reqVO.getMobile())
|
||||
.eqIfPresent(MemberAirlineAccountDO::getChannel, reqVO.getChannel())
|
||||
.eqIfPresent(MemberAirlineAccountDO::getStatus, reqVO.getStatus())
|
||||
.likeIfPresent(MemberAirlineAccountDO::getDeviceId, reqVO.getDeviceId())
|
||||
.betweenIfPresent(MemberAirlineAccountDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(MemberAirlineAccountDO::getId));
|
||||
}
|
||||
|
||||
default MemberAirlineAccountDO selectByMobileAndChannel(String mobile, String channel) {
|
||||
return selectOne(new LambdaQueryWrapperX<MemberAirlineAccountDO>()
|
||||
.eq(MemberAirlineAccountDO::getMobile, mobile)
|
||||
.eq(MemberAirlineAccountDO::getChannel, channel));
|
||||
}
|
||||
|
||||
default MemberAirlineAccountDO selectByDeviceIdAndChannel(String deviceId, String channel) {
|
||||
return selectOne(new LambdaQueryWrapperX<MemberAirlineAccountDO>()
|
||||
.eq(MemberAirlineAccountDO::getDeviceId, deviceId)
|
||||
.eq(MemberAirlineAccountDO::getChannel, channel));
|
||||
}
|
||||
|
||||
default List<MemberAirlineAccountDO> selectListByDeviceId(String deviceId) {
|
||||
return selectList(new LambdaQueryWrapperX<MemberAirlineAccountDO>()
|
||||
.eq(MemberAirlineAccountDO::getDeviceId, deviceId));
|
||||
}
|
||||
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package cn.iocoder.yudao.module.member.dal.mysql.airline;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.airline.MemberAirlineFlightDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface MemberAirlineFlightMapper extends BaseMapperX<MemberAirlineFlightDO> {
|
||||
|
||||
default List<MemberAirlineFlightDO> selectListByAccountId(Long accountId) {
|
||||
return selectList(new LambdaQueryWrapperX<MemberAirlineFlightDO>()
|
||||
.eq(MemberAirlineFlightDO::getAccountId, accountId)
|
||||
.orderByDesc(MemberAirlineFlightDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package cn.iocoder.yudao.module.member.dal.mysql.airline;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.airline.MemberAirlinePointDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface MemberAirlinePointMapper extends BaseMapperX<MemberAirlinePointDO> {
|
||||
|
||||
default MemberAirlinePointDO selectByAccountId(Long accountId) {
|
||||
return selectOne(new LambdaQueryWrapperX<MemberAirlinePointDO>()
|
||||
.eq(MemberAirlinePointDO::getAccountId, accountId));
|
||||
}
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package cn.iocoder.yudao.module.member.dal.mysql.interaction;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.interaction.MemberInteractionDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface MemberInteractionMapper extends BaseMapperX<MemberInteractionDO> {
|
||||
|
||||
default PageResult<MemberInteractionDO> selectPageByReceiver(Long receiverId, PageParam pageParam) {
|
||||
return selectPage(pageParam, new LambdaQueryWrapperX<MemberInteractionDO>()
|
||||
.eq(MemberInteractionDO::getReceiverId, receiverId)
|
||||
.orderByDesc(MemberInteractionDO::getId));
|
||||
}
|
||||
|
||||
default Long selectUnreadCount(Long receiverId) {
|
||||
return selectCount(new LambdaQueryWrapperX<MemberInteractionDO>()
|
||||
.eq(MemberInteractionDO::getReceiverId, receiverId)
|
||||
.eq(MemberInteractionDO::getReadStatus, false));
|
||||
}
|
||||
|
||||
default MemberInteractionDO selectLatest(Long receiverId) {
|
||||
List<MemberInteractionDO> list = selectList(new LambdaQueryWrapperX<MemberInteractionDO>()
|
||||
.eq(MemberInteractionDO::getReceiverId, receiverId)
|
||||
.orderByDesc(MemberInteractionDO::getId)
|
||||
.last("LIMIT 1"));
|
||||
return list.isEmpty() ? null : list.get(0);
|
||||
}
|
||||
|
||||
default int markAllRead(Long receiverId) {
|
||||
return update(null, new LambdaUpdateWrapper<MemberInteractionDO>()
|
||||
.set(MemberInteractionDO::getReadStatus, true)
|
||||
.eq(MemberInteractionDO::getReceiverId, receiverId)
|
||||
.eq(MemberInteractionDO::getReadStatus, false));
|
||||
}
|
||||
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package cn.iocoder.yudao.module.member.dal.mysql.push;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.push.MemberPushDeviceDO;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface MemberPushDeviceMapper extends BaseMapperX<MemberPushDeviceDO> {
|
||||
|
||||
default MemberPushDeviceDO selectByRegistrationId(String registrationId) {
|
||||
return selectOne(MemberPushDeviceDO::getRegistrationId, registrationId);
|
||||
}
|
||||
|
||||
default List<MemberPushDeviceDO> selectListByMemberId(Long memberId) {
|
||||
return selectList(new LambdaQueryWrapperX<MemberPushDeviceDO>()
|
||||
.eq(MemberPushDeviceDO::getMemberId, memberId)
|
||||
.orderByDesc(MemberPushDeviceDO::getId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 物理删除,避免逻辑删除后 registration_id 唯一索引冲突。
|
||||
*/
|
||||
@Delete("DELETE FROM member_push_device WHERE id = #{id}")
|
||||
int physicalDeleteById(@Param("id") Long id);
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package cn.iocoder.yudao.module.member.dal.mysql.rideplan;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.rideplan.MemberRidePlanJoinDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface MemberRidePlanJoinMapper extends BaseMapperX<MemberRidePlanJoinDO> {
|
||||
|
||||
default List<MemberRidePlanJoinDO> selectListByPlanId(Long planId) {
|
||||
return selectList(new LambdaQueryWrapperX<MemberRidePlanJoinDO>()
|
||||
.eq(MemberRidePlanJoinDO::getPlanId, planId));
|
||||
}
|
||||
|
||||
default MemberRidePlanJoinDO selectByPlanIdAndUserId(Long planId, Long userId) {
|
||||
return selectOne(new LambdaQueryWrapperX<MemberRidePlanJoinDO>()
|
||||
.eq(MemberRidePlanJoinDO::getPlanId, planId)
|
||||
.eq(MemberRidePlanJoinDO::getUserId, userId));
|
||||
}
|
||||
|
||||
default List<MemberRidePlanJoinDO> selectListByUserId(Long userId) {
|
||||
return selectList(new LambdaQueryWrapperX<MemberRidePlanJoinDO>()
|
||||
.eq(MemberRidePlanJoinDO::getUserId, userId));
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package cn.iocoder.yudao.module.member.dal.mysql.rideplan;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.member.controller.app.rideplan.vo.AppRidePlanPageReqVO;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.rideplan.MemberRidePlanDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Mapper
|
||||
public interface MemberRidePlanMapper extends BaseMapperX<MemberRidePlanDO> {
|
||||
|
||||
/**
|
||||
* 发车局分页(免登录可查):只查未下架、未过期的,按集合时间升序
|
||||
*/
|
||||
default PageResult<MemberRidePlanDO> selectPage(AppRidePlanPageReqVO reqVO) {
|
||||
// 注意:LambdaQueryWrapperX 的 ifPresent 系列须在原生 eq/ge 之前调用,
|
||||
// 原生方法返回值会退化为 LambdaQueryWrapper,链式后续找不到 ifPresent
|
||||
LambdaQueryWrapperX<MemberRidePlanDO> wrapper = new LambdaQueryWrapperX<MemberRidePlanDO>()
|
||||
.eqIfPresent(MemberRidePlanDO::getCity, reqVO.getCity())
|
||||
.likeIfPresent(MemberRidePlanDO::getRouteName, reqVO.getKeyword());
|
||||
wrapper.eq(MemberRidePlanDO::getStatus, 0);
|
||||
wrapper.ge(MemberRidePlanDO::getMeetTime, LocalDateTime.now());
|
||||
if (reqVO.getTag() != null && !reqVO.getTag().isEmpty()) {
|
||||
wrapper.apply("FIND_IN_SET({0}, tags) > 0", reqVO.getTag());
|
||||
}
|
||||
wrapper.orderByAsc(MemberRidePlanDO::getMeetTime);
|
||||
return selectPage(reqVO, wrapper);
|
||||
}
|
||||
|
||||
default MemberRidePlanDO selectByIdAndStatus(Long id, Integer status) {
|
||||
return selectOne(new LambdaQueryWrapperX<MemberRidePlanDO>()
|
||||
.eq(MemberRidePlanDO::getId, id)
|
||||
.eq(MemberRidePlanDO::getStatus, status));
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package cn.iocoder.yudao.module.member.dal.mysql.rideplan;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.rideplan.MemberRideRecordLikeDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface MemberRideRecordLikeMapper extends BaseMapperX<MemberRideRecordLikeDO> {
|
||||
|
||||
default MemberRideRecordLikeDO selectByRecordIdAndUserId(Long recordId, Long userId) {
|
||||
return selectOne(new LambdaQueryWrapperX<MemberRideRecordLikeDO>()
|
||||
.eq(MemberRideRecordLikeDO::getRecordId, recordId)
|
||||
.eq(MemberRideRecordLikeDO::getUserId, userId));
|
||||
}
|
||||
|
||||
default List<MemberRideRecordLikeDO> selectListByUserIdAndRecordIds(Long userId, Collection<Long> recordIds) {
|
||||
return selectList(new LambdaQueryWrapperX<MemberRideRecordLikeDO>()
|
||||
.eq(MemberRideRecordLikeDO::getUserId, userId)
|
||||
.in(MemberRideRecordLikeDO::getRecordId, recordIds));
|
||||
}
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package cn.iocoder.yudao.module.member.dal.mysql.rideplan;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.member.controller.app.rideplan.vo.AppRideRecordPageReqVO;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.rideplan.MemberRideRecordDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface MemberRideRecordMapper extends BaseMapperX<MemberRideRecordDO> {
|
||||
|
||||
/**
|
||||
* 战报墙分页(免登录可查):只查正常状态,按发布时间倒序
|
||||
*/
|
||||
default PageResult<MemberRideRecordDO> selectPage(AppRideRecordPageReqVO reqVO) {
|
||||
LambdaQueryWrapperX<MemberRideRecordDO> wrapper = new LambdaQueryWrapperX<MemberRideRecordDO>()
|
||||
.eqIfPresent(MemberRideRecordDO::getUserId, reqVO.getUserId())
|
||||
.eqIfPresent(MemberRideRecordDO::getRoadName, reqVO.getRoadName());
|
||||
wrapper.eq(MemberRideRecordDO::getStatus, 0);
|
||||
wrapper.orderByDesc(MemberRideRecordDO::getId);
|
||||
return selectPage(reqVO, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的历史骑行(分页)
|
||||
*/
|
||||
default PageResult<MemberRideRecordDO> selectMyPage(Long userId, AppRideRecordPageReqVO reqVO) {
|
||||
LambdaQueryWrapperX<MemberRideRecordDO> wrapper = new LambdaQueryWrapperX<MemberRideRecordDO>()
|
||||
.eq(MemberRideRecordDO::getUserId, userId)
|
||||
.eq(MemberRideRecordDO::getStatus, 0);
|
||||
wrapper.orderByDesc(MemberRideRecordDO::getId);
|
||||
return selectPage(reqVO, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 弯道天梯榜:取最大压弯角度最高的前 N 条(有弯道名的跑山记录)
|
||||
*/
|
||||
default List<MemberRideRecordDO> selectRank(String roadName, String cornerName, int limit) {
|
||||
LambdaQueryWrapperX<MemberRideRecordDO> wrapper = new LambdaQueryWrapperX<MemberRideRecordDO>()
|
||||
.eqIfPresent(MemberRideRecordDO::getRoadName, roadName)
|
||||
.eqIfPresent(MemberRideRecordDO::getCornerName, cornerName);
|
||||
wrapper.eq(MemberRideRecordDO::getStatus, 0);
|
||||
wrapper.isNotNull(MemberRideRecordDO::getCornerName);
|
||||
wrapper.ne(MemberRideRecordDO::getCornerName, "");
|
||||
wrapper.orderByDesc(MemberRideRecordDO::getMaxLean);
|
||||
wrapper.last("LIMIT " + limit);
|
||||
return selectList(wrapper);
|
||||
}
|
||||
|
||||
default MemberRideRecordDO selectByIdAndStatus(Long id, Integer status) {
|
||||
return selectOne(new LambdaQueryWrapperX<MemberRideRecordDO>()
|
||||
.eq(MemberRideRecordDO::getId, id)
|
||||
.eq(MemberRideRecordDO::getStatus, status));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查一批骑手的正常状态战报(用于列表/主页骑行数据统计)
|
||||
*/
|
||||
default List<MemberRideRecordDO> selectNormalByUserIds(Collection<Long> userIds) {
|
||||
return selectList(new LambdaQueryWrapperX<MemberRideRecordDO>()
|
||||
.in(MemberRideRecordDO::getUserId, userIds)
|
||||
.eq(MemberRideRecordDO::getStatus, 0));
|
||||
}
|
||||
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.yudao.module.member.dal.mysql.rideplan;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.rideplan.MemberRideTagDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface MemberRideTagMapper extends BaseMapperX<MemberRideTagDO> {
|
||||
|
||||
default List<MemberRideTagDO> selectListByStatus(Integer status) {
|
||||
return selectList(new LambdaQueryWrapperX<MemberRideTagDO>()
|
||||
.eq(MemberRideTagDO::getStatus, status)
|
||||
.orderByAsc(MemberRideTagDO::getSort));
|
||||
}
|
||||
|
||||
default MemberRideTagDO selectByName(String name) {
|
||||
return selectOne(new LambdaQueryWrapperX<MemberRideTagDO>()
|
||||
.eq(MemberRideTagDO::getName, name));
|
||||
}
|
||||
|
||||
}
|
||||
Loaded 100 of 156 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user