Explorar el Código

营业执照调整为入驻信息

liuchengsen hace 1 mes
padre
commit
58d799444d
Se han modificado 27 ficheros con 466 adiciones y 225 borrados
  1. 19 19
      docs/01-产品文档.md
  2. 5 5
      docs/02-架构文档.md
  3. 10 10
      docs/03-接口文档.md
  4. 2 2
      docs/06-验收流程与标准.md
  5. 1 1
      zhijiayun-activity/src/main/java/com/xuekairui/activity/controller/ActivityController.java
  6. 3 3
      zhijiayun-activity/src/main/java/com/xuekairui/activity/scheduler/ActivityEventScheduler.java
  7. 6 6
      zhijiayun-activity/src/main/java/com/xuekairui/activity/service/ActivityInviteService.java
  8. 1 1
      zhijiayun-common/src/main/java/com/xuekairui/common/activity/ActivityInviteGate.java
  9. 2 2
      zhijiayun-common/src/main/java/com/xuekairui/common/event/ActivityEventRedisService.java
  10. 4 4
      zhijiayun-gateway/src/main/resources/db/migration-v11.sql
  11. 1 1
      zhijiayun-gateway/src/main/resources/db/migration-v8.sql
  12. 3 3
      zhijiayun-gateway/src/main/resources/db/schema.sql
  13. 27 15
      zhijiayun-gateway/src/test/java/com/xuekairui/gateway/AdminLicenseTest.java
  14. 18 11
      zhijiayun-gateway/src/test/java/com/xuekairui/gateway/BusinessLicenseAndCouponTest.java
  15. 26 19
      zhijiayun-gateway/src/test/java/com/xuekairui/gateway/EndToEndBusinessFlowTest.java
  16. 62 4
      zhijiayun-gateway/src/test/resources/test-schema.sql
  17. 72 44
      zhijiayun-test/src/main/resources/static/activity-test.html
  18. 22 13
      zhijiayun-test/src/main/resources/static/invite-test.html
  19. 10 10
      zhijiayun-user/src/main/java/com/xuekairui/user/controller/AdminLicenseController.java
  20. 106 30
      zhijiayun-user/src/main/java/com/xuekairui/user/controller/BusinessLicenseController.java
  21. 3 3
      zhijiayun-user/src/main/java/com/xuekairui/user/controller/ContentController.java
  22. 49 5
      zhijiayun-user/src/main/java/com/xuekairui/user/entity/BusinessLicense.java
  23. 1 1
      zhijiayun-user/src/main/java/com/xuekairui/user/entity/OperationAuditLog.java
  24. 1 1
      zhijiayun-user/src/main/java/com/xuekairui/user/entity/UserMembership.java
  25. 9 9
      zhijiayun-user/src/main/java/com/xuekairui/user/enums/OperationType.java
  26. 2 2
      zhijiayun-user/src/main/java/com/xuekairui/user/mapper/BusinessLicenseMapper.java
  27. 1 1
      zhijiayun-user/src/main/java/com/xuekairui/user/service/AuthService.java

+ 19 - 19
docs/01-产品文档.md

@@ -47,7 +47,7 @@
 | 药店采购员 | USER | 负责药店日常药品采购 | 快速找到最低价格,生成采购链接 |
 | 药店采购员 | USER | 负责药店日常药品采购 | 快速找到最低价格,生成采购链接 |
 | 执业药师 | USER | 审核药品质量和供应商资质 | 查看药品详细信息、供应商信誉 |
 | 执业药师 | USER | 审核药品质量和供应商资质 | 查看药品详细信息、供应商信誉 |
 | 药店老板 | USER | 控制采购成本,优化供应链 | 采购数据分析、成本控制报表 |
 | 药店老板 | USER | 控制采购成本,优化供应链 | 采购数据分析、成本控制报表 |
-| 运营人员 | ADMIN | 管理邀请配置、发放优惠券、审核执照 | 提高用户注册率和活跃度 |
+| 运营人员 | ADMIN | 管理邀请配置、发放优惠券、审核资质 | 提高用户注册率和活跃度 |
 | 超级管理员 | SUPER_ADMIN | 系统全局管理、角色赋权 | 用户管理、数据统计、风控监控 |
 | 超级管理员 | SUPER_ADMIN | 系统全局管理、角色赋权 | 用户管理、数据统计、风控监控 |
 
 
 **三级RBAC权限模型**:
 **三级RBAC权限模型**:
@@ -98,7 +98,7 @@
 | 等级 | 获取方式 |
 | 等级 | 获取方式 |
 |------|---------|
 |------|---------|
 | PLUS | 用户注册即自动获得,不限时长,永久免费 |
 | PLUS | 用户注册即自动获得,不限时长,永久免费 |
-| PRO | ① 付费购买(月卡30天/年卡365天)② 邀请奖励(1个月)③ 营业执照审核通过(1个月)④ 运营手动发放 |
+| PRO | ① 付费购买(月卡30天/年卡365天)② 邀请奖励(1个月)③ 入驻信息审核通过(1个月)④ 运营手动发放 |
 | ULTRA | 运营手动发放(购买入口待开放) |
 | ULTRA | 运营手动发放(购买入口待开放) |
 
 
 **互斥与叠加规则**:
 **互斥与叠加规则**:
@@ -128,7 +128,7 @@
 |------|------|
 |------|------|
 | `RECHARGE` | 用户付费购买 |
 | `RECHARGE` | 用户付费购买 |
 | `INVITE` | 邀请奖励赠送 |
 | `INVITE` | 邀请奖励赠送 |
-| `LICENSE` | 营业执照审核通过赠送 |
+| `LICENSE` | 入驻信息审核通过赠送 |
 | `ADMIN_GRANT` | 运营手动发放 |
 | `ADMIN_GRANT` | 运营手动发放 |
 | `SYSTEM` | 系统默认(普通会员PLUS) |
 | `SYSTEM` | 系统默认(普通会员PLUS) |
 
 
@@ -312,31 +312,31 @@
 | POST /api/invite/click/{code} | 追踪邀请链接被打开(公开) |
 | POST /api/invite/click/{code} | 追踪邀请链接被打开(公开) |
 | GET /api/invite/resolve-link?link=xxx | 智能解析邀请链接或邀请码(公开,适用于粘贴文本场景) |
 | GET /api/invite/resolve-link?link=xxx | 智能解析邀请链接或邀请码(公开,适用于粘贴文本场景) |
 
 
-### 2.6 营业执照上传(可选)
+### 2.6 入驻信息提交(可选)
 
 
 **设计原则**:
 **设计原则**:
-- ✅ 用户可以自愿上传营业执照
-- ✅ 不上传不影响基本使用(注册、查询、购买)
-- ✅ 上传后运营审核,通过后显示"已认证"标识
+- ✅ 用户可以自愿提交入驻信息(含营业执照、药品经营许可证、医疗器械备案等)
+- ✅ 不提交不影响基本使用(注册、查询、购买)
+- ✅ 提交后运营审核,通过后显示"已认证"标识
 - ✅ 认证标识增强用户信任度
 - ✅ 认证标识增强用户信任度
 
 
 **审核流程**:
 **审核流程**:
 ```
 ```
-用户上传营业执照 → 提交审核申请 → 运营后台查看待审队列 
+用户提交入驻信息 → 运营后台查看待审队列 
 → 运营审核(通过/驳回)→ 通过后显示"已认证"标识
 → 运营审核(通过/驳回)→ 通过后显示"已认证"标识
 ```
 ```
 
 
-**营业执照信息**:药店名称、统一社会信用代码、法人姓名、注册地址、经营范围、有效期等。
+**入驻信息**:店铺名称、经营终端类型、所在地区、营业执照、药品经营许可证、医疗器械备案(二类/三类)等。
 
 
-**审核通过奖励**:首次营业执照审核通过,自动赠送30天高级会员(PRO),来源标记为 `LICENSE`。
+**审核通过奖励**:首次入驻信息审核通过,自动赠送30天高级会员(PRO),来源标记为 `LICENSE`。
 
 
 **用户侧接口**:
 **用户侧接口**:
 
 
 | 接口 | 说明 |
 | 接口 | 说明 |
 |------|------|
 |------|------|
-| POST /api/business-license/upload | 上传营业执照(JSON方式,传图片URL) |
-| POST /api/business-license/upload-file | 上传营业执照(文件方式,multipart) |
-| GET /api/business-license/info | 查询我的营业执照信息及审核状态 |
+| POST /api/business-license/upload | 提交入驻信息(JSON方式,传图片URL) |
+| POST /api/business-license/upload-file | 提交入驻信息(文件方式,multipart) |
+| GET /api/business-license/info | 查询我的入驻信息及审核状态 |
 
 
 ### 2.7 优惠券系统
 ### 2.7 优惠券系统
 
 
@@ -359,7 +359,7 @@
 |------|------|
 |------|------|
 | POST /api/upload | 上传单个文件(multipart/form-data),返回文件访问路径 |
 | POST /api/upload | 上传单个文件(multipart/form-data),返回文件访问路径 |
 
 
-**说明**:支持营业执照图片、用户头像等文件上传,文件存储在服务器 `/opt/uploads` 目录。
+**说明**:支持入驻信息图片、用户头像等文件上传,文件存储在服务器 `/opt/uploads` 目录。
 
 
 ### 2.9 支付与会员购买系统
 ### 2.9 支付与会员购买系统
 
 
@@ -442,7 +442,7 @@
 | 优惠券发放 | POST /api/coupon/issue |
 | 优惠券发放 | POST /api/coupon/issue |
 | 支付方案管理 | POST/PUT /api/admin/payment/plans |
 | 支付方案管理 | POST/PUT /api/admin/payment/plans |
 | 平台分布统计 | GET /api/admin/crawler/user-detail |
 | 平台分布统计 | GET /api/admin/crawler/user-detail |
-| 营业执照审核 | GET/POST /api/admin/license/review |
+| 入驻信息审核 | GET/POST /api/admin/license/review |
 | 用户列表管理 | GET /api/admin/users |
 | 用户列表管理 | GET /api/admin/users |
 | 用户角色修改 | PUT /api/admin/users/{userId}/role(仅SUPER_ADMIN) |
 | 用户角色修改 | PUT /api/admin/users/{userId}/role(仅SUPER_ADMIN) |
 | 体验配额配置 | GET/POST/PUT/DELETE /api/admin/trial-quota/*(详见下方) |
 | 体验配额配置 | GET/POST/PUT/DELETE /api/admin/trial-quota/*(详见下方) |
@@ -455,7 +455,7 @@
 
 
 - **记录维度**:操作人、操作时间、IP地址、设备信息、操作模块、操作类型、目标对象、操作前后数据快照、耗时
 - **记录维度**:操作人、操作时间、IP地址、设备信息、操作模块、操作类型、目标对象、操作前后数据快照、耗时
 - **敏感操作标记**:禁用用户、审核操作、配置变更等标记为敏感操作
 - **敏感操作标记**:禁用用户、审核操作、配置变更等标记为敏感操作
-- **操作模块覆盖**:用户管理、平台配置、账号绑定、营业执照、邀请系统、优惠券、体验配额、爬虫配额、系统管理
+- **操作模块覆盖**:用户管理、平台配置、账号绑定、入驻信息、邀请系统、优惠券、体验配额、爬虫配额、系统管理
 
 
 **运营侧接口**:
 **运营侧接口**:
 
 
@@ -628,7 +628,7 @@
 智价云(药店版)采用**"代码预留扩展 + 运营动态配置"**的设计策略:
 智价云(药店版)采用**"代码预留扩展 + 运营动态配置"**的设计策略:
 
 
 1. **平台扩展性**:代码支持34个平台,MVP首期启用3个,后续通过运营配置逐步开放
 1. **平台扩展性**:代码支持34个平台,MVP首期启用3个,后续通过运营配置逐步开放
-2. **营业执照可选**:用户可以自愿上传,非强制要求,审核后显示认证标识
+2. **入驻信息可选**:用户可以自愿提交,非强制要求,审核后显示认证标识
 3. **体验配额可配**:运营可以动态调整体验天数、查询次数等参数
 3. **体验配额可配**:运营可以动态调整体验天数、查询次数等参数
 4. **会员体系灵活**:会员权益配置表支持运营动态调整各等级配额加成
 4. **会员体系灵活**:会员权益配置表支持运营动态调整各等级配额加成
 5. **支付方案可配**:运营可创建/上架/下架会员套餐方案
 5. **支付方案可配**:运营可创建/上架/下架会员套餐方案
@@ -701,7 +701,7 @@
 - [x] **Windows独立桌面应用**(.exe格式,内置WebView2浏览器引擎)
 - [x] **Windows独立桌面应用**(.exe格式,内置WebView2浏览器引擎)
 - [x] 用户认证(短信验证码登录、密码登录、微信扫码登录)
 - [x] 用户认证(短信验证码登录、密码登录、微信扫码登录)
 - [x] 密码管理(设置/修改密码、修改手机号)
 - [x] 密码管理(设置/修改密码、修改手机号)
-- [x] 营业执照上传(可选,审核后显示认证标识)
+- [x] 入驻信息提交(可选,审核后显示认证标识)
 - [x] B2B平台账号绑定(药师帮/药帮忙/1药城,密码AES-256加密存储)
 - [x] B2B平台账号绑定(药师帮/药帮忙/1药城,密码AES-256加密存储)
 - [x] 药品搜索比价功能(消耗配额查询,返回各平台报价)
 - [x] 药品搜索比价功能(消耗配额查询,返回各平台报价)
 - [x] 跳转原平台购买(WebView内嵌跳转,不依赖系统浏览器)
 - [x] 跳转原平台购买(WebView内嵌跳转,不依赖系统浏览器)
@@ -713,7 +713,7 @@
 - [x] 支付系统(模拟支付、支付方案管理、渠道切换、重复支付自动退款、签约协议、安心付;微信/支付宝回调为占位实现,待接入SDK)
 - [x] 支付系统(模拟支付、支付方案管理、渠道切换、重复支付自动退款、签约协议、安心付;微信/支付宝回调为占位实现,待接入SDK)
 - [x] 体验配额管理(运营可配置天数、查询次数、关注品种数)
 - [x] 体验配额管理(运营可配置天数、查询次数、关注品种数)
 - [x] 优惠券系统(批量发放、配额消耗优先使用)
 - [x] 优惠券系统(批量发放、配额消耗优先使用)
-- [x] 运营管理后台(配置管理、执照审核、数据统计、会员管理、支付方案管理)
+- [x] 运营管理后台(配置管理、入驻审核、数据统计、会员管理、支付方案管理)
 - [x] 用户管理(三级RBAC角色模型、用户列表、角色赋权)
 - [x] 用户管理(三级RBAC角色模型、用户列表、角色赋权)
 - [x] 平台配置管理(34个平台预留,MVP启用3个,运营动态配置)
 - [x] 平台配置管理(34个平台预留,MVP启用3个,运营动态配置)
 - [x] 操作审计日志系统(记录所有关键操作,支持追溯)
 - [x] 操作审计日志系统(记录所有关键操作,支持追溯)

+ 5 - 5
docs/02-架构文档.md

@@ -33,7 +33,7 @@
 ```
 ```
 zhijiayun-pharmacy (父工程)
 zhijiayun-pharmacy (父工程)
 ├── zhijiayun-common    # 公共模块:Result、ErrorCode、BusinessException、TraceIdGenerator、ValidateUtil
 ├── zhijiayun-common    # 公共模块:Result、ErrorCode、BusinessException、TraceIdGenerator、ValidateUtil
-├── zhijiayun-user      # 用户模块:认证、会员、优惠券、爬虫配额、搜索、关注、营业执照、审计日志
+├── zhijiayun-user      # 用户模块:认证、会员、优惠券、爬虫配额、搜索、关注、入驻信息、审计日志
 ├── zhijiayun-invite    # 邀请模块:邀请码、裂变、会员奖励、运营配置
 ├── zhijiayun-invite    # 邀请模块:邀请码、裂变、会员奖励、运营配置
 ├── zhijiayun-payment   # 支付模块:微信/支付宝支付、订单管理、套餐方案、二维码生成、重复支付自动退款
 ├── zhijiayun-payment   # 支付模块:微信/支付宝支付、订单管理、套餐方案、二维码生成、重复支付自动退款
 ├── zhijiayun-gateway   # 网关模块:聚合启动、请求日志、Redis限流、全局异常处理
 ├── zhijiayun-gateway   # 网关模块:聚合启动、请求日志、Redis限流、全局异常处理
@@ -84,12 +84,12 @@ com.xuekairui
 │   │   ├── PlatformAccountController.java # B2B平台账号绑定
 │   │   ├── PlatformAccountController.java # B2B平台账号绑定
 │   │   ├── PlatformSessionController.java # 平台会话管理
 │   │   ├── PlatformSessionController.java # 平台会话管理
 │   │   ├── CrawlerPlatformConfigController.java # 平台配置查询
 │   │   ├── CrawlerPlatformConfigController.java # 平台配置查询
-│   │   ├── BusinessLicenseController.java # 营业执照上传
+│   │   ├── BusinessLicenseController.java # 入驻信息提交
 │   │   ├── FileUploadController.java     # 文件上传
 │   │   ├── FileUploadController.java     # 文件上传
 │   │   ├── TrialQuotaConfigController.java # 体验配额查询
 │   │   ├── TrialQuotaConfigController.java # 体验配额查询
 │   │   ├── AdminUserController.java      # 用户管理(SUPER_ADMIN)
 │   │   ├── AdminUserController.java      # 用户管理(SUPER_ADMIN)
 │   │   ├── AdminMembershipController.java # 会员发放管理
 │   │   ├── AdminMembershipController.java # 会员发放管理
-│   │   ├── AdminLicenseController.java   # 营业执照审核
+│   │   ├── AdminLicenseController.java   # 入驻信息审核
 │   │   ├── AdminTrialQuotaConfigController.java # 体验配额配置管理
 │   │   ├── AdminTrialQuotaConfigController.java # 体验配额配置管理
 │   │   └── OperationAuditLogController.java # 审计日志查询
 │   │   └── OperationAuditLogController.java # 审计日志查询
 │   ├── dto/                   # 数据传输对象(34个)
 │   ├── dto/                   # 数据传输对象(34个)
@@ -184,7 +184,7 @@ com.xuekairui
 | t_platform_account | B2B平台账号绑定表 | user_id, platform_code, platform_name, account_encrypted, password_encrypted, enabled, verify_status |
 | t_platform_account | B2B平台账号绑定表 | user_id, platform_code, platform_name, account_encrypted, password_encrypted, enabled, verify_status |
 | t_search_record | 搜索记录表(完整比价快照) | user_id, drug_name, searched_at, status_text, platform_results, search_log |
 | t_search_record | 搜索记录表(完整比价快照) | user_id, drug_name, searched_at, status_text, platform_results, search_log |
 | t_platform_config | 平台配置表 | platform_code, platform_name, enabled, query_cost, priority, official_url, binding_instructions |
 | t_platform_config | 平台配置表 | platform_code, platform_name, enabled, query_cost, priority, official_url, binding_instructions |
-| t_business_license | 营业执照表 | user_id, license_image_url, credit_code, pharmacy_name, review_status(PENDING/APPROVED/REJECTED) |
+| t_business_license | 入驻信息表 | user_id, store_name, terminal_type, province, city, district, store_address, contact_person, contact_phone, license_image_url, drug_license_url, medical_device_url, credit_code, reviewer_id, review_status(PENDING/APPROVED/REJECTED) |
 | t_trial_quota_config | 体验配额配置表 | config_name, trial_days, daily_query_limit, monthly_watchlist_limit, config_type(DEFAULT/COUPON/ACTIVITY) |
 | t_trial_quota_config | 体验配额配置表 | config_name, trial_days, daily_query_limit, monthly_watchlist_limit, config_type(DEFAULT/COUPON/ACTIVITY) |
 | t_operation_audit_log | 操作审计日志表 | operator_id, operator_role, module, operation_type, target_type, target_id, before_data, after_data, result, is_sensitive |
 | t_operation_audit_log | 操作审计日志表 | operator_id, operator_role, module, operation_type, target_type, target_id, before_data, after_data, result, is_sensitive |
 | t_search_history | 搜索历史表(轻量关键词日志,无Java实体) | user_id, keyword, platform, result_count, search_time |
 | t_search_history | 搜索历史表(轻量关键词日志,无Java实体) | user_id, keyword, platform, result_count, search_time |
@@ -200,7 +200,7 @@ t_user ──1:N──> t_crawler_quota_grant (配额发放)
 t_user ──1:N──> t_crawler_usage_log (使用记录)
 t_user ──1:N──> t_crawler_usage_log (使用记录)
 t_user ──1:N──> t_user_coupon (优惠券)
 t_user ──1:N──> t_user_coupon (优惠券)
 t_user ──1:N──> t_platform_account (平台账号绑定)
 t_user ──1:N──> t_platform_account (平台账号绑定)
-t_user ──1:1──> t_business_license (营业执照)
+t_user ──1:1──> t_business_license (入驻信息)
 t_user ──1:N──> t_search_history (搜索历史/关键词日志)
 t_user ──1:N──> t_search_history (搜索历史/关键词日志)
 t_user ──1:N──> t_search_record (搜索记录/比价快照)
 t_user ──1:N──> t_search_record (搜索记录/比价快照)
 t_user ──1:N──> t_watchlist (关注列表)
 t_user ──1:N──> t_watchlist (关注列表)

+ 10 - 10
docs/03-接口文档.md

@@ -1098,9 +1098,9 @@ POST /api/platform-account/verify/{platformCode}
 
 
 ---
 ---
 
 
-## 8. 营业执照模块 /api/business-license
+## 8. 入驻信息模块 /api/business-license
 
 
-### 8.1 上传营业执照
+### 8.1 提交入驻信息
 
 
 ```
 ```
 POST /api/business-license/upload
 POST /api/business-license/upload
@@ -1131,13 +1131,13 @@ POST /api/business-license/upload
 ```
 ```
 
 
 **注意事项:**
 **注意事项:**
-- 营业执照为**可选填写**,不影响基本使用
+- 入驻信息为**可选填写**,不影响基本使用
 - 审核通过后显示认证标识,提升用户信任度
 - 审核通过后显示认证标识,提升用户信任度
 - 图片需要先上传到OSS,获取URL后再调用此接口
 - 图片需要先上传到OSS,获取URL后再调用此接口
 
 
 ---
 ---
 
 
-### 8.2 查询营业执照信息
+### 8.2 查询入驻信息
 
 
 ```
 ```
 GET /api/business-license/info
 GET /api/business-license/info
@@ -1609,7 +1609,7 @@ GET /api/activity/invite/status
 | 字段 | 类型 | 说明 |
 | 字段 | 类型 | 说明 |
 |------|------|------|
 |------|------|------|
 | active | Boolean | 活动是否开启 |
 | active | Boolean | 活动是否开启 |
-| rewardTrigger | String | REGISTRATION-注册即发 / LICENSE_APPROVED-资质审核通过后发 |
+| rewardTrigger | String | REGISTRATION-注册即发 / LICENSE_APPROVED-入驻审核通过后发 |
 | myReceivedCount | Long | 我已获得的奖励次数 |
 | myReceivedCount | Long | 我已获得的奖励次数 |
 | myRemainingCount | Long | 我剩余可获得的奖励次数 |
 | myRemainingCount | Long | 我剩余可获得的奖励次数 |
 
 
@@ -1792,7 +1792,7 @@ PUT /api/admin/activity/invite/config
 | 字段 | 类型 | 必填 | 说明 |
 | 字段 | 类型 | 必填 | 说明 |
 |------|------|------|------|
 |------|------|------|------|
 | enabled | Integer | 是 | 0-停用 1-启用 |
 | enabled | Integer | 是 | 0-停用 1-启用 |
-| rewardTrigger | String | 是 | REGISTRATION-注册即发 LICENSE_APPROVED-资质审核通过后发 |
+| rewardTrigger | String | 是 | REGISTRATION-注册即发 LICENSE_APPROVED-入驻审核通过后发 |
 | requireMiniappInviter | Integer | 是 | 0-不要求 1-要求邀请人为小程序用户 |
 | requireMiniappInviter | Integer | 是 | 0-不要求 1-要求邀请人为小程序用户 |
 | maxInviterReward | Integer | 是 | 邀请人最多获得奖励次数(默认30) |
 | maxInviterReward | Integer | 是 | 邀请人最多获得奖励次数(默认30) |
 
 
@@ -2633,12 +2633,12 @@ POST /api/upload
 
 
 ---
 ---
 
 
-### 18.3 营业执照审核(ADMIN+) /api/admin/license
+### 18.3 入驻信息审核(ADMIN+) /api/admin/license
 
 
 | 接口 | 说明 |
 | 接口 | 说明 |
 |------|------|
 |------|------|
-| GET /api/admin/license/review?reviewStatus=PENDING | 查看待审核营业执照列表(ADMIN+) |
-| POST /api/admin/license/review | 审核营业执照(ADMIN+,通过后赠送30天高级会员) |
+| GET /api/admin/license/review?reviewStatus=PENDING | 查看待审核入驻信息列表(ADMIN+) |
+| POST /api/admin/license/review | 审核入驻信息(ADMIN+,通过后赠送30天高级会员) |
 
 
 **审核请求体:**
 **审核请求体:**
 ```json
 ```json
@@ -2651,7 +2651,7 @@ POST /api/upload
 
 
 | 字段 | 类型 | 必填 | 说明 |
 | 字段 | 类型 | 必填 | 说明 |
 |------|------|------|------|
 |------|------|------|------|
-| licenseId | Long | 是 | 营业执照ID |
+| licenseId | Long | 是 | 入驻信息ID |
 | action | String | 是 | 审核动作:APPROVED/REJECTED |
 | action | String | 是 | 审核动作:APPROVED/REJECTED |
 | rejectReason | String | 否 | 驳回原因(REJECTED时必填) |
 | rejectReason | String | 否 | 驳回原因(REJECTED时必填) |
 
 

+ 2 - 2
docs/06-验收流程与标准.md

@@ -60,7 +60,7 @@
 | FA-22 | 注册即得PLUS | SMS/微信注册自动获得PLUS普通会员,无过期时间 | P0 |
 | FA-22 | 注册即得PLUS | SMS/微信注册自动获得PLUS普通会员,无过期时间 | P0 |
 | FA-23 | 付费购买PRO | 支付成功后立即激活PRO,t_user与t_user_membership实时同步 | P0 |
 | FA-23 | 付费购买PRO | 支付成功后立即激活PRO,t_user与t_user_membership实时同步 | P0 |
 | FA-24 | 邀请奖励PRO | 成功邀请新用户注册→邀请人自动获得30天PRO会员(等级匹配规则) | P0 |
 | FA-24 | 邀请奖励PRO | 成功邀请新用户注册→邀请人自动获得30天PRO会员(等级匹配规则) | P0 |
-| FA-25 | 营业执照审核送PRO | 首次审核通过赠送30天PRO | P1 |
+| FA-25 | 经营资质审核送PRO | 首次审核通过赠送30天PRO | P1 |
 | FA-26 | 同等级续期 | PRO续期PRO→在现有到期时间上叠加30天 | P1 |
 | FA-26 | 同等级续期 | PRO续期PRO→在现有到期时间上叠加30天 | P1 |
 | FA-27 | 升级不降级 | ULTRA用户获邀请奖励→续期ULTRA 30天,不降为PRO | P1 |
 | FA-27 | 升级不降级 | ULTRA用户获邀请奖励→续期ULTRA 30天,不降为PRO | P1 |
 | FA-28 | 到期自动降级 | PRO到期后→自动降为PLUS,t_user同步清除membershipExpireAt | P0 |
 | FA-28 | 到期自动降级 | PRO到期后→自动降为PLUS,t_user同步清除membershipExpireAt | P0 |
@@ -175,7 +175,7 @@
 | FA-94 | 用户列表查询 | 分页查询用户,支持按手机号/角色筛选 | P0 |
 | FA-94 | 用户列表查询 | 分页查询用户,支持按手机号/角色筛选 | P0 |
 | FA-95 | 用户角色修改 | SUPER_ADMIN修改用户角色成功 | P0 |
 | FA-95 | 用户角色修改 | SUPER_ADMIN修改用户角色成功 | P0 |
 | FA-96 | 体验配额配置 | 创建/更新体验配额配置成功 | P1 |
 | FA-96 | 体验配额配置 | 创建/更新体验配额配置成功 | P1 |
-| FA-97 | 营业执照审核 | 运营审核通过/驳回成功 | P1 |
+| FA-97 | 经营资质审核 | 运营审核通过/驳回成功 | P1 |
 
 
 ### 2.10 优惠券模块
 ### 2.10 优惠券模块
 
 

+ 1 - 1
zhijiayun-activity/src/main/java/com/xuekairui/activity/controller/ActivityController.java

@@ -132,7 +132,7 @@ public class ActivityController {
                 ActivityAvailableResponse.ActivityBrief.builder()
                 ActivityAvailableResponse.ActivityBrief.builder()
                         .activityType("INVITE")
                         .activityType("INVITE")
                         .activityName("邀请有礼")
                         .activityName("邀请有礼")
-                        .description("邀请好友注册并通过资质审核,双方各得会员天数奖励")
+                        .description("邀请好友注册并通过入驻审核,双方各得会员天数奖励")
                         .enabled(inviteStatus.getEnabled())
                         .enabled(inviteStatus.getEnabled())
                         .inWindow(inviteStatus.getInWindow())
                         .inWindow(inviteStatus.getInWindow())
                         .rewardedCount(inviteStatus.getRewardedCount())
                         .rewardedCount(inviteStatus.getRewardedCount())

+ 3 - 3
zhijiayun-activity/src/main/java/com/xuekairui/activity/scheduler/ActivityEventScheduler.java

@@ -34,7 +34,7 @@ public class ActivityEventScheduler {
     private final ActivityLotteryService activityLotteryService;
     private final ActivityLotteryService activityLotteryService;
 
 
     /**
     /**
-     * 每 5 秒轮询一次,消费资质审核通过事件。
+     * 每 5 秒轮询一次,消费入驻审核通过事件。
      * 优先消费备份队列中的失败事件(重试),再消费主队列中的新事件。
      * 优先消费备份队列中的失败事件(重试),再消费主队列中的新事件。
      */
      */
     @Scheduled(fixedDelay = 5000)
     @Scheduled(fixedDelay = 5000)
@@ -45,11 +45,11 @@ public class ActivityEventScheduler {
                 Long userId = toLong(event.get("userId"));
                 Long userId = toLong(event.get("userId"));
                 Long licenseId = toLong(event.get("licenseId"));
                 Long licenseId = toLong(event.get("licenseId"));
                 String licenseType = (String) event.get("licenseType");
                 String licenseType = (String) event.get("licenseType");
-                log.info("消费资质审核通过事件: userId={}, licenseId={}", userId, licenseId);
+                log.info("消费入驻审核通过事件: userId={}, licenseId={}", userId, licenseId);
                 activityInviteService.onLicenseApproved(userId, licenseId, licenseType);
                 activityInviteService.onLicenseApproved(userId, licenseId, licenseType);
                 eventRedisService.ackLicenseApprovedEvent(event);
                 eventRedisService.ackLicenseApprovedEvent(event);
             } catch (Exception e) {
             } catch (Exception e) {
-                log.error("处理资质审核通过事件失败,事件将保留在备份队列待重试: event={}", event, e);
+                log.error("处理入驻审核通过事件失败,事件将保留在备份队列待重试: event={}", event, e);
             }
             }
         }
         }
     }
     }

+ 6 - 6
zhijiayun-activity/src/main/java/com/xuekairui/activity/service/ActivityInviteService.java

@@ -31,12 +31,12 @@ import java.util.List;
  * <ul>
  * <ul>
  *   <li>活动未开启 → 按原逻辑发放 30 天高级会员(无活动限制)</li>
  *   <li>活动未开启 → 按原逻辑发放 30 天高级会员(无活动限制)</li>
  *   <li>活动开启 + REGISTRATION 模式 → 立即发放 30 天高级会员(受活动时间窗、30 次上限约束)</li>
  *   <li>活动开启 + REGISTRATION 模式 → 立即发放 30 天高级会员(受活动时间窗、30 次上限约束)</li>
- *   <li>活动开启 + LICENSE_APPROVED 模式 → 暂不发放,等待被邀请人资质审核通过后由事件触发</li>
+ *   <li>活动开启 + LICENSE_APPROVED 模式 → 暂不发放,等待被邀请人入驻审核通过后由事件触发</li>
  * </ul>
  * </ul>
  * <p>
  * <p>
  * 规则要点:
  * 规则要点:
  * 1. 活动时间窗内有效;
  * 1. 活动时间窗内有效;
- * 2. LICENSE_APPROVED 模式:被邀请人需上传资质且审核通过后才发放;
+ * 2. LICENSE_APPROVED 模式:被邀请人需上传入驻信息且审核通过后才发放;
  * 3. 邀请人最多获得 30 次邀请奖励(活动期间内);
  * 3. 邀请人最多获得 30 次邀请奖励(活动期间内);
  * 4. 每个被邀请人只触发一次邀请人奖励(DB 唯一索引兜底)。
  * 4. 每个被邀请人只触发一次邀请人奖励(DB 唯一索引兜底)。
  */
  */
@@ -47,7 +47,7 @@ public class ActivityInviteService implements ActivityInviteGate {
 
 
     /** 奖励触发模式:注册即发 */
     /** 奖励触发模式:注册即发 */
     public static final String TRIGGER_REGISTRATION = "REGISTRATION";
     public static final String TRIGGER_REGISTRATION = "REGISTRATION";
-    /** 奖励触发模式:资质审核通过后发 */
+    /** 奖励触发模式:入驻审核通过后发 */
     public static final String TRIGGER_LICENSE_APPROVED = "LICENSE_APPROVED";
     public static final String TRIGGER_LICENSE_APPROVED = "LICENSE_APPROVED";
 
 
     private static final Long CONFIG_ID = 1L;
     private static final Long CONFIG_ID = 1L;
@@ -82,7 +82,7 @@ public class ActivityInviteService implements ActivityInviteGate {
 
 
         // 活动开启 → 根据触发模式决定
         // 活动开启 → 根据触发模式决定
         if (TRIGGER_LICENSE_APPROVED.equalsIgnoreCase(config.getRewardTrigger())) {
         if (TRIGGER_LICENSE_APPROVED.equalsIgnoreCase(config.getRewardTrigger())) {
-            // LICENSE_APPROVED 模式:注册环节暂不发奖,等待资质审核通过后由 onLicenseApproved 触发
+            // LICENSE_APPROVED 模式:注册环节暂不发奖,等待入驻审核通过后由 onLicenseApproved 触发
             log.info("邀请有礼活动(LICENSE_APPROVED模式),注册环节暂不发奖: inviter={}, invitee={}", inviterId, inviteeId);
             log.info("邀请有礼活动(LICENSE_APPROVED模式),注册环节暂不发奖: inviter={}, invitee={}", inviterId, inviteeId);
             return;
             return;
         }
         }
@@ -133,11 +133,11 @@ public class ActivityInviteService implements ActivityInviteGate {
     }
     }
 
 
     // ==========================================
     // ==========================================
-    // 事件处理:资质审核通过后发放邀请人奖励(LICENSE_APPROVED 模式专用)
+    // 事件处理:入驻审核通过后发放邀请人奖励(LICENSE_APPROVED 模式专用)
     // ==========================================
     // ==========================================
 
 
     /**
     /**
-     * 资质审核通过后发放邀请人奖励。
+     * 入驻审核通过后发放邀请人奖励。
      * <p>
      * <p>
      * 幂等:t_activity_invite_reward_record 的 uk_invitee 唯一索引保证同一被邀请人仅奖励一次。
      * 幂等:t_activity_invite_reward_record 的 uk_invitee 唯一索引保证同一被邀请人仅奖励一次。
      */
      */

+ 1 - 1
zhijiayun-common/src/main/java/com/xuekairui/common/activity/ActivityInviteGate.java

@@ -17,7 +17,7 @@ public interface ActivityInviteGate {
      * <ul>
      * <ul>
      *   <li>活动未开启 → 按原逻辑发放 30 天高级会员</li>
      *   <li>活动未开启 → 按原逻辑发放 30 天高级会员</li>
      *   <li>活动开启 + REGISTRATION 模式 → 立即发放 30 天高级会员</li>
      *   <li>活动开启 + REGISTRATION 模式 → 立即发放 30 天高级会员</li>
-     *   <li>活动开启 + LICENSE_APPROVED 模式 → 暂不发放,等待被邀请人资质审核通过后由事件触发</li>
+     *   <li>活动开启 + LICENSE_APPROVED 模式 → 暂不发放,等待被邀请人入驻审核通过后由事件触发</li>
      * </ul>
      * </ul>
      *
      *
      * @param inviterId 邀请人ID
      * @param inviterId 邀请人ID

+ 2 - 2
zhijiayun-common/src/main/java/com/xuekairui/common/event/ActivityEventRedisService.java

@@ -49,7 +49,7 @@ public class ActivityEventRedisService {
     }
     }
 
 
     /**
     /**
-     * 存入资质审核通过事件
+     * 存入入驻审核通过事件
      */
      */
     public void pushLicenseApproved(Long userId, Long licenseId, String licenseType) {
     public void pushLicenseApproved(Long userId, Long licenseId, String licenseType) {
         Map<String, Object> data = new HashMap<>();
         Map<String, Object> data = new HashMap<>();
@@ -76,7 +76,7 @@ public class ActivityEventRedisService {
     }
     }
 
 
     /**
     /**
-     * 从资质审核通过队列安全取出一条事件(使用 RPOPLPUSH 到备份队列,容错不丢事件)
+     * 从入驻审核通过队列安全取出一条事件(使用 RPOPLPUSH 到备份队列,容错不丢事件)
      */
      */
     public Map<String, Object> popLicenseApprovedEvent() {
     public Map<String, Object> popLicenseApprovedEvent() {
         return popEvent(KEY_LICENSE_APPROVED);
         return popEvent(KEY_LICENSE_APPROVED);

+ 4 - 4
zhijiayun-gateway/src/main/resources/db/migration-v11.sql

@@ -13,7 +13,7 @@ CREATE TABLE IF NOT EXISTS `t_activity_invite_config` (
     `inviter_reward_days`    INT          NOT NULL DEFAULT 30 COMMENT '邀请人奖励会员天数',
     `inviter_reward_days`    INT          NOT NULL DEFAULT 30 COMMENT '邀请人奖励会员天数',
     `invitee_reward_days`    INT          NOT NULL DEFAULT 30 COMMENT '被邀请人奖励会员天数',
     `invitee_reward_days`    INT          NOT NULL DEFAULT 30 COMMENT '被邀请人奖励会员天数',
     `max_inviter_reward`     INT          NOT NULL DEFAULT 30 COMMENT '邀请人最多获得邀请奖励次数',
     `max_inviter_reward`     INT          NOT NULL DEFAULT 30 COMMENT '邀请人最多获得邀请奖励次数',
-    `reward_trigger`         VARCHAR(20)  NOT NULL DEFAULT 'REGISTRATION' COMMENT '奖励触发模式:REGISTRATION-注册即发(原逻辑) LICENSE_APPROVED-资质审核通过后发',
+    `reward_trigger`         VARCHAR(20)  NOT NULL DEFAULT 'REGISTRATION' COMMENT '奖励触发模式:REGISTRATION-注册即发(原逻辑) LICENSE_APPROVED-入驻审核通过后发',
     `require_miniapp_inviter` TINYINT     NOT NULL DEFAULT 0 COMMENT '是否要求邀请人为开邻药惠购小程序用户:0-不要求 1-要求',
     `require_miniapp_inviter` TINYINT     NOT NULL DEFAULT 0 COMMENT '是否要求邀请人为开邻药惠购小程序用户:0-不要求 1-要求',
     `remark`                 VARCHAR(500) DEFAULT NULL,
     `remark`                 VARCHAR(500) DEFAULT NULL,
     `create_time`            DATETIME     DEFAULT CURRENT_TIMESTAMP,
     `create_time`            DATETIME     DEFAULT CURRENT_TIMESTAMP,
@@ -24,10 +24,10 @@ CREATE TABLE IF NOT EXISTS `t_activity_invite_config` (
 INSERT INTO `t_activity_invite_config` (`id`, `enabled`, `start_time`, `end_time`,
 INSERT INTO `t_activity_invite_config` (`id`, `enabled`, `start_time`, `end_time`,
     `inviter_reward_days`, `invitee_reward_days`, `max_inviter_reward`, `reward_trigger`, `require_miniapp_inviter`, `remark`)
     `inviter_reward_days`, `invitee_reward_days`, `max_inviter_reward`, `reward_trigger`, `require_miniapp_inviter`, `remark`)
 VALUES (1, 0, '2026-07-10 00:00:00', '2026-07-31 23:59:59', 30, 30, 30, 'LICENSE_APPROVED', 0,
 VALUES (1, 0, '2026-07-10 00:00:00', '2026-07-31 23:59:59', 30, 30, 30, 'LICENSE_APPROVED', 0,
-    '邀请有礼:默认关闭。启用后需被邀请人资质审核通过才发奖,邀请人最多30次')
+    '邀请有礼:默认关闭。启用后需被邀请人入驻审核通过才发奖,邀请人最多30次')
 ON DUPLICATE KEY UPDATE id = id;
 ON DUPLICATE KEY UPDATE id = id;
 
 
--- 邀请有礼奖励发放记录(活动模式 LICENSE_APPROVED 下,邀请人在被邀请人资质通过后获得的奖励)
+-- 邀请有礼奖励发放记录(活动模式 LICENSE_APPROVED 下,邀请人在被邀请人入驻审核通过后获得的奖励)
 CREATE TABLE IF NOT EXISTS `t_activity_invite_reward_record` (
 CREATE TABLE IF NOT EXISTS `t_activity_invite_reward_record` (
     `id`                  BIGINT       NOT NULL AUTO_INCREMENT,
     `id`                  BIGINT       NOT NULL AUTO_INCREMENT,
     `inviter_id`          BIGINT       NOT NULL COMMENT '邀请人ID',
     `inviter_id`          BIGINT       NOT NULL COMMENT '邀请人ID',
@@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS `t_activity_invite_reward_record` (
     `create_time`         DATETIME     DEFAULT CURRENT_TIMESTAMP,
     `create_time`         DATETIME     DEFAULT CURRENT_TIMESTAMP,
     `update_time`         DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     `update_time`         DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     PRIMARY KEY (`id`),
     PRIMARY KEY (`id`),
-    UNIQUE KEY `uk_invitee` (`invitee_id`) COMMENT '同一被邀请人资质通过仅奖励邀请人一次'
+    UNIQUE KEY `uk_invitee` (`invitee_id`) COMMENT '同一被邀请人入驻审核通过仅奖励邀请人一次'
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请有礼活动奖励发放记录';
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请有礼活动奖励发放记录';
 
 
 
 

+ 1 - 1
zhijiayun-gateway/src/main/resources/db/migration-v8.sql

@@ -13,7 +13,7 @@ ALTER TABLE `t_business_license`
     ADD COLUMN `sync_source` VARCHAR(20) DEFAULT 'MANUAL' COMMENT '来源:MANUAL-手动上传 MINIAPP_SYNC-外部系统同步' AFTER `show_verified_badge`,
     ADD COLUMN `sync_source` VARCHAR(20) DEFAULT 'MANUAL' COMMENT '来源:MANUAL-手动上传 MINIAPP_SYNC-外部系统同步' AFTER `show_verified_badge`,
     ADD COLUMN `external_id` VARCHAR(64) DEFAULT NULL COMMENT '外部系统记录ID' AFTER `sync_source`;
     ADD COLUMN `external_id` VARCHAR(64) DEFAULT NULL COMMENT '外部系统记录ID' AFTER `sync_source`;
 
 
--- 3. 统一用户证书表(营业执照 / 药品经营许可证 / 医疗器械备案凭证)
+-- 3. 统一用户证书表(入驻信息:含营业执照 / 药品经营许可证 / 医疗器械备案凭证)
 --    设计理念:证书的核心是图片,用于验证用户真实性和资质;
 --    设计理念:证书的核心是图片,用于验证用户真实性和资质;
 --    额外信息(编号、有效期等)通过 OCR 或人工审核时补充到 ocr_data JSON 字段。
 --    额外信息(编号、有效期等)通过 OCR 或人工审核时补充到 ocr_data JSON 字段。
 --    注意:t_business_license 为历史表,此后新证书数据统一使用本表。
 --    注意:t_business_license 为历史表,此后新证书数据统一使用本表。

+ 3 - 3
zhijiayun-gateway/src/main/resources/db/schema.sql

@@ -74,7 +74,7 @@ CREATE TABLE IF NOT EXISTS `t_user_membership` (
     `id`                BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
     `id`                BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
     `user_id`           BIGINT        NOT NULL COMMENT '用户ID',
     `user_id`           BIGINT        NOT NULL COMMENT '用户ID',
     `level`             VARCHAR(20)   NOT NULL COMMENT '会员等级:PLUS/PRO/ULTRA',
     `level`             VARCHAR(20)   NOT NULL COMMENT '会员等级:PLUS/PRO/ULTRA',
-    `source`            VARCHAR(20)   NOT NULL DEFAULT 'SYSTEM' COMMENT '来源:SYSTEM-系统默认 RECHARGE-充值购买 INVITE-邀请奖励 ADMIN_GRANT-运营发放 LICENSE-营业执照',
+    `source`            VARCHAR(20)   NOT NULL DEFAULT 'SYSTEM' COMMENT '来源:SYSTEM-系统默认 RECHARGE-充值购买 INVITE-邀请奖励 ADMIN_GRANT-运营发放 LICENSE-入驻信息',
     `effective_from`    DATETIME      DEFAULT CURRENT_TIMESTAMP COMMENT '生效开始时间',
     `effective_from`    DATETIME      DEFAULT CURRENT_TIMESTAMP COMMENT '生效开始时间',
     `effective_to`      DATETIME      DEFAULT NULL COMMENT '生效结束时间(NULL表示永久)',
     `effective_to`      DATETIME      DEFAULT NULL COMMENT '生效结束时间(NULL表示永久)',
     `status`            INT           NOT NULL DEFAULT 1 COMMENT '状态:1-有效 0-过期/停用',
     `status`            INT           NOT NULL DEFAULT 1 COMMENT '状态:1-有效 0-过期/停用',
@@ -423,7 +423,7 @@ CREATE TABLE IF NOT EXISTS `t_platform_account` (
     UNIQUE KEY `uk_user_platform` (`user_id`, `platform_code`)
     UNIQUE KEY `uk_user_platform` (`user_id`, `platform_code`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='B2B平台账号绑定表';
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='B2B平台账号绑定表';
 
 
--- 营业执照
+-- 入驻信息
 CREATE TABLE IF NOT EXISTS `t_business_license` (
 CREATE TABLE IF NOT EXISTS `t_business_license` (
     `id`                    BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
     `id`                    BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
     `user_id`               BIGINT        NOT NULL COMMENT '用户ID',
     `user_id`               BIGINT        NOT NULL COMMENT '用户ID',
@@ -447,7 +447,7 @@ CREATE TABLE IF NOT EXISTS `t_business_license` (
     KEY `idx_user_id` (`user_id`),
     KEY `idx_user_id` (`user_id`),
     KEY `idx_review_status` (`review_status`),
     KEY `idx_review_status` (`review_status`),
     UNIQUE KEY `uk_user_id` (`user_id`)
     UNIQUE KEY `uk_user_id` (`user_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='营业执照表';
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='入驻信息表';
 
 
 -- 平台配置表
 -- 平台配置表
 CREATE TABLE IF NOT EXISTS `t_platform_config` (
 CREATE TABLE IF NOT EXISTS `t_platform_config` (

+ 27 - 15
zhijiayun-gateway/src/test/java/com/xuekairui/gateway/AdminLicenseTest.java

@@ -7,10 +7,10 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
 
 
 /**
 /**
- * 营业执照审核管理接口测试
+ * 入驻信息审核管理接口测试
  * 覆盖:审核列表查询、审核通过/拒绝、异常场景
  * 覆盖:审核列表查询、审核通过/拒绝、异常场景
  */
  */
-@DisplayName("营业执照审核管理接口测试")
+@DisplayName("入驻信息审核管理接口测试")
 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 class AdminLicenseTest extends GatewayBaseTest {
 class AdminLicenseTest extends GatewayBaseTest {
 
 
@@ -18,20 +18,25 @@ class AdminLicenseTest extends GatewayBaseTest {
         return generateAccessToken(100L, "13900000000", "ADMIN");
         return generateAccessToken(100L, "13900000000", "ADMIN");
     }
     }
 
 
-    private Long uploadedLicenseId;
-
-    // ==================== 前置:上传营业执照 ====================
+    // ==================== 前置:上传入驻信息 ====================
 
 
     @Test
     @Test
     @Order(1)
     @Order(1)
-    @DisplayName("前置:上传营业执照用于审核测试")
+    @DisplayName("前置:上传入驻信息用于审核测试")
     void setup_uploadLicense() throws Exception {
     void setup_uploadLicense() throws Exception {
         String body = """
         String body = """
                 {
                 {
-                    "licenseImageUrl": "https://oss.example.com/license/review-test.jpg",
-                    "creditCode": "91110000MA12345678",
-                    "pharmacyName": "审核测试大药房",
-                    "legalPerson": "李四"
+                    "storeName": "审核测试大药房",
+                    "terminalType": "SINGLE",
+                    "province": "广东省",
+                    "city": "深圳市",
+                    "district": "南山区",
+                    "storeAddress": "科技园路1号",
+                    "contactPerson": "李四",
+                    "contactPhone": "13900139001",
+                    "businessLicenseUrl": "https://oss.example.com/license/review-test.jpg",
+                    "drugLicenseUrl": "https://oss.example.com/drug/review-test.jpg",
+                    "creditCode": "91110000MA12345678"
                 }
                 }
                 """;
                 """;
         mockMvc.perform(post("/api/business-license/upload")
         mockMvc.perform(post("/api/business-license/upload")
@@ -210,10 +215,17 @@ class AdminLicenseTest extends GatewayBaseTest {
         // 先用另一个用户上传
         // 先用另一个用户上传
         String uploadBody = """
         String uploadBody = """
                 {
                 {
-                    "licenseImageUrl": "https://oss.example.com/license/reject-test.jpg",
-                    "creditCode": "91110000MA87654321",
-                    "pharmacyName": "拒绝测试药房",
-                    "legalPerson": "王五"
+                    "storeName": "拒绝测试药房",
+                    "terminalType": "CHAIN",
+                    "province": "北京市",
+                    "city": "北京市",
+                    "district": "朝阳区",
+                    "storeAddress": "望京路88号",
+                    "contactPerson": "王五",
+                    "contactPhone": "13900139002",
+                    "businessLicenseUrl": "https://oss.example.com/license/reject-test.jpg",
+                    "drugLicenseUrl": "https://oss.example.com/drug/reject-test.jpg",
+                    "creditCode": "91110000MA87654321"
                 }
                 }
                 """;
                 """;
         mockMvc.perform(post("/api/business-license/upload")
         mockMvc.perform(post("/api/business-license/upload")
@@ -227,7 +239,7 @@ class AdminLicenseTest extends GatewayBaseTest {
                 {
                 {
                     "licenseId": 2,
                     "licenseId": 2,
                     "action": "REJECTED",
                     "action": "REJECTED",
-                    "rejectReason": "营业执照图片不清晰,请重新上传"
+                    "rejectReason": "资质图片不清晰,请重新上传"
                 }
                 }
                 """;
                 """;
         mockMvc.perform(post("/api/admin/license/review")
         mockMvc.perform(post("/api/admin/license/review")

+ 18 - 11
zhijiayun-gateway/src/test/java/com/xuekairui/gateway/BusinessLicenseAndCouponTest.java

@@ -7,14 +7,14 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
 
 
 /**
 /**
- * 营业执照和优惠券模块测试
+ * 入驻信息和优惠券模块测试
  * 
  * 
  * 覆盖场景:
  * 覆盖场景:
- * 1. 营业执照上传/查询(可选功能)
+ * 1. 入驻信息提交/查询
  * 2. 优惠券获取/使用
  * 2. 优惠券获取/使用
  * 3. 用户等级查询
  * 3. 用户等级查询
  */
  */
-@DisplayName("营业执照和优惠券模块测试")
+@DisplayName("入驻信息和优惠券模块测试")
 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 class BusinessLicenseAndCouponTest extends GatewayBaseTest {
 class BusinessLicenseAndCouponTest extends GatewayBaseTest {
 
 
@@ -22,18 +22,25 @@ class BusinessLicenseAndCouponTest extends GatewayBaseTest {
         return generateAccessToken(1L, "13800138000");
         return generateAccessToken(1L, "13800138000");
     }
     }
 
 
-    // ==================== 营业执照上传(可选功能)====================
+    // ==================== 入驻信息提交 ====================
 
 
     @Test
     @Test
     @Order(1)
     @Order(1)
-    @DisplayName("POST /api/business-license/upload - 上传营业执照")
+    @DisplayName("POST /api/business-license/upload - 提交入驻信息")
     void uploadBusinessLicense_shouldSucceed() throws Exception {
     void uploadBusinessLicense_shouldSucceed() throws Exception {
         String body = """
         String body = """
                 {
                 {
-                    "licenseImageUrl": "https://oss.example.com/license/test.jpg",
-                    "creditCode": "91110000XXXXXXXXXX",
-                    "pharmacyName": "测试大药房",
-                    "legalPerson": "张三"
+                    "storeName": "测试大药房",
+                    "terminalType": "SINGLE",
+                    "province": "广东省",
+                    "city": "深圳市",
+                    "district": "南山区",
+                    "storeAddress": "科技园路1号",
+                    "contactPerson": "张三",
+                    "contactPhone": "13800138001",
+                    "businessLicenseUrl": "https://oss.example.com/license/test.jpg",
+                    "drugLicenseUrl": "https://oss.example.com/drug/test.jpg",
+                    "creditCode": "91110000XXXXXXXXXX"
                 }
                 }
                 """;
                 """;
         mockMvc.perform(post("/api/business-license/upload")
         mockMvc.perform(post("/api/business-license/upload")
@@ -50,7 +57,7 @@ class BusinessLicenseAndCouponTest extends GatewayBaseTest {
     void uploadBusinessLicense_missingFields_shouldReturnBadRequest() throws Exception {
     void uploadBusinessLicense_missingFields_shouldReturnBadRequest() throws Exception {
         String body = """
         String body = """
                 {
                 {
-                    "pharmacyName": "测试大药房"
+                    "storeName": "测试大药房"
                 }
                 }
                 """;
                 """;
         mockMvc.perform(post("/api/business-license/upload")
         mockMvc.perform(post("/api/business-license/upload")
@@ -72,7 +79,7 @@ class BusinessLicenseAndCouponTest extends GatewayBaseTest {
 
 
     @Test
     @Test
     @Order(4)
     @Order(4)
-    @DisplayName("GET /api/business-license/info - 查询营业执照信息")
+    @DisplayName("GET /api/business-license/info - 查询入驻信息")
     void getBusinessLicenseInfo_shouldReturnInfo() throws Exception {
     void getBusinessLicenseInfo_shouldReturnInfo() throws Exception {
         mockMvc.perform(get("/api/business-license/info")
         mockMvc.perform(get("/api/business-license/info")
                         .header("Authorization", "Bearer " + userToken()))
                         .header("Authorization", "Bearer " + userToken()))

+ 26 - 19
zhijiayun-gateway/src/test/java/com/xuekairui/gateway/EndToEndBusinessFlowTest.java

@@ -21,7 +21,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
  * 端到端完整业务流程测试
  * 端到端完整业务流程测试
  * 
  * 
  * 完整业务流程:
  * 完整业务流程:
- * 1. 用户注册 → 2. 绑定B2B平台账号 → 3. 上传营业执照 → 4. 查询配额 → 5. 消耗配额比价
+ * 1. 用户注册 → 2. 绑定B2B平台账号 → 3. 提交入驻信息 → 4. 查询配额 → 5. 消耗配额比价
  * 6. 邀请其他用户 → 7. 获得奖励 → 8. 查看操作历史
  * 6. 邀请其他用户 → 7. 获得奖励 → 8. 查看操作历史
  */
  */
 @SpringBootTest
 @SpringBootTest
@@ -120,32 +120,39 @@ public class EndToEndBusinessFlowTest extends GatewayBaseTest {
     }
     }
 
 
     /**
     /**
-     * 阶段3:上传营业执照(可选)
+     * 阶段3:提交入驻信息
      */
      */
     @Test
     @Test
     @Order(3)
     @Order(3)
-    @DisplayName("阶段3:上传营业执照")
+    @DisplayName("阶段3:提交入驻信息")
     public void stage3_UploadBusinessLicense() throws Exception {
     public void stage3_UploadBusinessLicense() throws Exception {
-        log.info("\n========== 阶段3:上传营业执照 ==========");
+        log.info("\n========== 阶段3:提交入驻信息 ==========");
         
         
-        // 3.1 上传营业执照
+        // 3.1 提交入驻信息
         mockMvc.perform(post("/api/business-license/upload")
         mockMvc.perform(post("/api/business-license/upload")
                 .header("Authorization", "Bearer " + userToken)
                 .header("Authorization", "Bearer " + userToken)
                 .contentType(MediaType.APPLICATION_JSON)
                 .contentType(MediaType.APPLICATION_JSON)
                 .content(objectMapper.writeValueAsString(Map.of(
                 .content(objectMapper.writeValueAsString(Map.of(
-                    "licenseImageUrl", "https://oss.example.com/license/test.jpg",
-                    "creditCode", "91110000XXXXXXXXXX",
-                    "pharmacyName", "测试大药房",
-                    "legalPerson", "张三"
+                    "storeName", "测试大药房",
+                    "terminalType", "SINGLE",
+                    "province", "广东省",
+                    "city", "深圳市",
+                    "district", "南山区",
+                    "storeAddress", "科技园路1号",
+                    "contactPerson", "张三",
+                    "contactPhone", "13800138001",
+                    "businessLicenseUrl", "https://oss.example.com/license/test.jpg",
+                    "drugLicenseUrl", "https://oss.example.com/drug/test.jpg",
+                    "creditCode", "91110000XXXXXXXXXX"
                 ))))
                 ))))
                 .andExpect(status().isOk());
                 .andExpect(status().isOk());
         
         
-        // 3.2 查询执照信息(待审核状态)
+        // 3.2 查询入驻信息(待审核状态)
         mockMvc.perform(get("/api/business-license/info")
         mockMvc.perform(get("/api/business-license/info")
                 .header("Authorization", "Bearer " + userToken))
                 .header("Authorization", "Bearer " + userToken))
                 .andExpect(jsonPath("$.data.reviewStatus").value("PENDING"));
                 .andExpect(jsonPath("$.data.reviewStatus").value("PENDING"));
         
         
-        log.info("✅ 阶段3完成: 营业执照已提交,待审核");
+        log.info("✅ 阶段3完成: 入驻信息已提交,待审核");
     }
     }
 
 
     /**
     /**
@@ -238,17 +245,17 @@ public class EndToEndBusinessFlowTest extends GatewayBaseTest {
     }
     }
 
 
     /**
     /**
-     * 阶段7:管理员审核营业执照
+     * 阶段7:管理员审核入驻信息
      */
      */
     @Test
     @Test
     @Order(7)
     @Order(7)
-    @DisplayName("阶段7:管理员审核营业执照")
+    @DisplayName("阶段7:管理员审核入驻信息")
     public void stage7_AdminReviewLicense() throws Exception {
     public void stage7_AdminReviewLicense() throws Exception {
-        log.info("\n========== 阶段7:管理员审核 ==========");
+        log.info("\n========== 阶段7:管理员审核入驻信息 ==========");
 
 
         adminToken = generateAdminToken();
         adminToken = generateAdminToken();
 
 
-        // 7.1 查询待审核执照列表
+        // 7.1 查询待审核入驻信息列表
         mockMvc.perform(get("/api/admin/license/review?reviewStatus=PENDING")
         mockMvc.perform(get("/api/admin/license/review?reviewStatus=PENDING")
                 .header("Authorization", "Bearer " + adminToken))
                 .header("Authorization", "Bearer " + adminToken))
                 .andExpect(status().isOk())
                 .andExpect(status().isOk())
@@ -266,7 +273,7 @@ public class EndToEndBusinessFlowTest extends GatewayBaseTest {
                 .andExpect(status().isOk())
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.code").value(200));
                 .andExpect(jsonPath("$.code").value(200));
 
 
-        log.info("✅ 阶段7完成: 营业执照审核通过");
+        log.info("✅ 阶段7完成: 入驻信息审核通过");
     }
     }
 
 
     /**
     /**
@@ -335,17 +342,17 @@ public class EndToEndBusinessFlowTest extends GatewayBaseTest {
         log.info("\n========== 🎉 端到端业务流程测试完成 ==========");
         log.info("\n========== 🎉 端到端业务流程测试完成 ==========");
         log.info("✅ 阶段1: 用户注册 - 完成");
         log.info("✅ 阶段1: 用户注册 - 完成");
         log.info("✅ 阶段2: 绑定B2B平台账号 - 完成");
         log.info("✅ 阶段2: 绑定B2B平台账号 - 完成");
-        log.info("✅ 阶段3: 上传营业执照 - 完成");
+        log.info("✅ 阶段3: 提交入驻信息 - 完成");
         log.info("✅ 阶段4: 邀请裂变 - 完成");
         log.info("✅ 阶段4: 邀请裂变 - 完成");
         log.info("✅ 阶段5: 获得邀请奖励 - 完成");
         log.info("✅ 阶段5: 获得邀请奖励 - 完成");
         log.info("✅ 阶段6: 消耗配额比价 - 完成");
         log.info("✅ 阶段6: 消耗配额比价 - 完成");
-        log.info("✅ 阶段7: 管理员审核 - 完成");
+        log.info("✅ 阶段7: 管理员审核入驻信息 - 完成");
         log.info("✅ 阶段8: 操作审计日志 - 完成");
         log.info("✅ 阶段8: 操作审计日志 - 完成");
         log.info("✅ 阶段9: 用户完整信息 - 完成");
         log.info("✅ 阶段9: 用户完整信息 - 完成");
         log.info("\n📊 测试覆盖:");
         log.info("\n📊 测试覆盖:");
         log.info("   - 用户认证流程 ✅");
         log.info("   - 用户认证流程 ✅");
         log.info("   - B2B平台账号绑定 ✅");
         log.info("   - B2B平台账号绑定 ✅");
-        log.info("   - 营业执照上传 ✅");
+        log.info("   - 入驻信息提交 ✅");
         log.info("   - 邀请裂变系统 ✅");
         log.info("   - 邀请裂变系统 ✅");
         log.info("   - 配额管理 ✅");
         log.info("   - 配额管理 ✅");
         log.info("   - 操作审计日志 ✅");
         log.info("   - 操作审计日志 ✅");

+ 62 - 4
zhijiayun-gateway/src/test/resources/test-schema.sql

@@ -399,13 +399,24 @@ ON DUPLICATE KEY UPDATE
     app_name = VALUES(app_name),
     app_name = VALUES(app_name),
     status = VALUES(status);
     status = VALUES(status);
 
 
--- 营业执照
+-- 入驻信息
 CREATE TABLE IF NOT EXISTS t_business_license (
 CREATE TABLE IF NOT EXISTS t_business_license (
     id                   BIGINT AUTO_INCREMENT PRIMARY KEY,
     id                   BIGINT AUTO_INCREMENT PRIMARY KEY,
     user_id              BIGINT        NOT NULL,
     user_id              BIGINT        NOT NULL,
-    license_image_url    VARCHAR(500)  DEFAULT NULL,
-    credit_code          VARCHAR(50)   DEFAULT NULL,
-    pharmacy_name        VARCHAR(200)  DEFAULT NULL,
+    license_image_url    VARCHAR(500)  DEFAULT NULL COMMENT '营业执照图片URL',
+    credit_code          VARCHAR(50)   DEFAULT NULL COMMENT '统一社会信用代码',
+    store_name           VARCHAR(200)  DEFAULT NULL COMMENT '店铺名称',
+    terminal_type        VARCHAR(20)   DEFAULT NULL COMMENT '终端类型:SINGLE/CHAIN/CLINIC/COMMUNITY_HEALTH',
+    store_address        VARCHAR(500)  DEFAULT NULL COMMENT '店铺详细地址',
+    province             VARCHAR(50)   DEFAULT NULL COMMENT '省',
+    city                 VARCHAR(50)   DEFAULT NULL COMMENT '市',
+    district             VARCHAR(50)   DEFAULT NULL COMMENT '区',
+    contact_person       VARCHAR(50)   DEFAULT NULL COMMENT '联系人姓名',
+    contact_phone        VARCHAR(20)   DEFAULT NULL COMMENT '联系电话',
+    drug_license_url     VARCHAR(500)  DEFAULT NULL COMMENT '药品经营许可证图片URL',
+    medical_device_class2_url VARCHAR(500) DEFAULT NULL COMMENT '二类医疗器械备案图片URL',
+    medical_device_class3_url VARCHAR(500) DEFAULT NULL COMMENT '三类医疗器械备案图片URL',
+    pharmacy_name        VARCHAR(200)  DEFAULT NULL COMMENT '药店名称(旧字段兼容)',
     legal_person         VARCHAR(50)   DEFAULT NULL,
     legal_person         VARCHAR(50)   DEFAULT NULL,
     registered_address   VARCHAR(500)  DEFAULT NULL,
     registered_address   VARCHAR(500)  DEFAULT NULL,
     business_scope       TEXT          DEFAULT NULL,
     business_scope       TEXT          DEFAULT NULL,
@@ -417,12 +428,59 @@ CREATE TABLE IF NOT EXISTS t_business_license (
     review_time          TIMESTAMP     DEFAULT NULL,
     review_time          TIMESTAMP     DEFAULT NULL,
     reject_reason        VARCHAR(500)  DEFAULT NULL,
     reject_reason        VARCHAR(500)  DEFAULT NULL,
     show_verified_badge  TINYINT(1)    NOT NULL DEFAULT 0,
     show_verified_badge  TINYINT(1)    NOT NULL DEFAULT 0,
+    sync_source          VARCHAR(20)   DEFAULT NULL COMMENT '来源:MANUAL/MINIAPP_SYNC',
+    external_id          VARCHAR(64)   DEFAULT NULL COMMENT '外部系统记录ID',
     create_time          TIMESTAMP     DEFAULT CURRENT_TIMESTAMP,
     create_time          TIMESTAMP     DEFAULT CURRENT_TIMESTAMP,
     update_time          TIMESTAMP     DEFAULT CURRENT_TIMESTAMP,
     update_time          TIMESTAMP     DEFAULT CURRENT_TIMESTAMP,
     INDEX idx_user_id (user_id),
     INDEX idx_user_id (user_id),
     CONSTRAINT uk_user_id UNIQUE (user_id)
     CONSTRAINT uk_user_id UNIQUE (user_id)
 );
 );
 
 
+-- 兼容升级:为 t_business_license 添加新字段
+SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_business_license' AND COLUMN_NAME = 'store_name');
+SET @sql = IF(@col_exists = 0, 'ALTER TABLE t_business_license ADD COLUMN store_name VARCHAR(200) DEFAULT NULL COMMENT ''店铺名称'' AFTER credit_code', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_business_license' AND COLUMN_NAME = 'terminal_type');
+SET @sql = IF(@col_exists = 0, 'ALTER TABLE t_business_license ADD COLUMN terminal_type VARCHAR(20) DEFAULT NULL COMMENT ''终端类型'' AFTER store_name', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_business_license' AND COLUMN_NAME = 'store_address');
+SET @sql = IF(@col_exists = 0, 'ALTER TABLE t_business_license ADD COLUMN store_address VARCHAR(500) DEFAULT NULL COMMENT ''店铺详细地址'' AFTER terminal_type', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_business_license' AND COLUMN_NAME = 'province');
+SET @sql = IF(@col_exists = 0, 'ALTER TABLE t_business_license ADD COLUMN province VARCHAR(50) DEFAULT NULL COMMENT ''省'' AFTER store_address', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_business_license' AND COLUMN_NAME = 'city');
+SET @sql = IF(@col_exists = 0, 'ALTER TABLE t_business_license ADD COLUMN city VARCHAR(50) DEFAULT NULL COMMENT ''市'' AFTER province', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_business_license' AND COLUMN_NAME = 'district');
+SET @sql = IF(@col_exists = 0, 'ALTER TABLE t_business_license ADD COLUMN district VARCHAR(50) DEFAULT NULL COMMENT ''区'' AFTER city', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_business_license' AND COLUMN_NAME = 'contact_person');
+SET @sql = IF(@col_exists = 0, 'ALTER TABLE t_business_license ADD COLUMN contact_person VARCHAR(50) DEFAULT NULL COMMENT ''联系人姓名'' AFTER district', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_business_license' AND COLUMN_NAME = 'contact_phone');
+SET @sql = IF(@col_exists = 0, 'ALTER TABLE t_business_license ADD COLUMN contact_phone VARCHAR(20) DEFAULT NULL COMMENT ''联系电话'' AFTER contact_person', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_business_license' AND COLUMN_NAME = 'drug_license_url');
+SET @sql = IF(@col_exists = 0, 'ALTER TABLE t_business_license ADD COLUMN drug_license_url VARCHAR(500) DEFAULT NULL COMMENT ''药品经营许可证'' AFTER contact_phone', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_business_license' AND COLUMN_NAME = 'medical_device_class2_url');
+SET @sql = IF(@col_exists = 0, 'ALTER TABLE t_business_license ADD COLUMN medical_device_class2_url VARCHAR(500) DEFAULT NULL COMMENT ''二类医疗器械备案'' AFTER drug_license_url', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_business_license' AND COLUMN_NAME = 'medical_device_class3_url');
+SET @sql = IF(@col_exists = 0, 'ALTER TABLE t_business_license ADD COLUMN medical_device_class3_url VARCHAR(500) DEFAULT NULL COMMENT ''三类医疗器械备案'' AFTER medical_device_class2_url', 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
 -- 操作审计日志表
 -- 操作审计日志表
 CREATE TABLE IF NOT EXISTS t_operation_audit_log (
 CREATE TABLE IF NOT EXISTS t_operation_audit_log (
     id               BIGINT        AUTO_INCREMENT PRIMARY KEY,
     id               BIGINT        AUTO_INCREMENT PRIMARY KEY,

+ 72 - 44
zhijiayun-test/src/main/resources/static/activity-test.html

@@ -108,8 +108,8 @@
 </head>
 </head>
 <body>
 <body>
 <div class="container">
 <div class="container">
-    <h1>智价云(药店版) - 活动模块API验收测试工具</h1>
-    <h2>覆盖签到有礼(周期制) + 邀请有礼 + 比价抽奖 用户端 &amp; 运营端 全量API | 独立登录管理 | 开发环境验证码固定 123456</h2>
+    <h1>智价云(药店版) - 入驻信息提交API测试工具</h1>
+    <h2>覆盖入驻信息提交(营业执照、药品经营许可证、医疗器械备案)| 店铺信息 + 资质图片 | 开发环境验证码固定 123456</h2>
 
 
     <!-- 用户侧/运营侧 切换器 -->
     <!-- 用户侧/运营侧 切换器 -->
     <div class="side-switcher">
     <div class="side-switcher">
@@ -188,7 +188,7 @@
         <div class="module-tab active" data-module="checkin">1.签到有礼</div>
         <div class="module-tab active" data-module="checkin">1.签到有礼</div>
         <div class="module-tab" data-module="activity-invite">2.邀请有礼</div>
         <div class="module-tab" data-module="activity-invite">2.邀请有礼</div>
         <div class="module-tab" data-module="activity-lottery">3.比价抽奖</div>
         <div class="module-tab" data-module="activity-lottery">3.比价抽奖</div>
-        <div class="module-tab" data-module="business-license">4.营业执照管理</div>
+        <div class="module-tab" data-module="business-license">4.入驻信息提交</div>
         <div class="module-tab" data-module="activity-available">5.我可参加的活动</div>
         <div class="module-tab" data-module="activity-available">5.我可参加的活动</div>
         <div class="module-tab" data-module="activity-rewards-tab">6.各种活动奖励</div>
         <div class="module-tab" data-module="activity-rewards-tab">6.各种活动奖励</div>
     </div>
     </div>
@@ -199,6 +199,7 @@
         <div class="module-tab" data-module="admin-activity-invite">2.邀请有礼管理</div>
         <div class="module-tab" data-module="admin-activity-invite">2.邀请有礼管理</div>
         <div class="module-tab" data-module="admin-activity-checkin">3.签到送会员管理</div>
         <div class="module-tab" data-module="admin-activity-checkin">3.签到送会员管理</div>
         <div class="module-tab" data-module="admin-activity-lottery">4.比价抽奖管理</div>
         <div class="module-tab" data-module="admin-activity-lottery">4.比价抽奖管理</div>
+        <div class="module-tab" data-module="admin-license">5.入驻信息审核</div>
     </div>
     </div>
 
 
     <div id="panels"></div>
     <div id="panels"></div>
@@ -217,7 +218,7 @@ const SMS_CODE = () => document.getElementById('smsCode')?.value || '123456';
 let stats = { pass: 0, fail: 0, pending: 0, total: 0 };
 let stats = { pass: 0, fail: 0, pending: 0, total: 0 };
 
 
 const USER_MODULES = ['checkin', 'activity-invite', 'activity-lottery', 'business-license', 'activity-available', 'activity-rewards-tab'];
 const USER_MODULES = ['checkin', 'activity-invite', 'activity-lottery', 'business-license', 'activity-available', 'activity-rewards-tab'];
-const ADMIN_MODULES = ['admin-checkin', 'admin-activity-invite', 'admin-activity-checkin', 'admin-activity-lottery'];
+const ADMIN_MODULES = ['admin-checkin', 'admin-activity-invite', 'admin-activity-checkin', 'admin-activity-lottery', 'admin-license'];
 
 
 // ===================== 大整数安全JSON解析 =====================
 // ===================== 大整数安全JSON解析 =====================
 function safeJsonParse(text) {
 function safeJsonParse(text) {
@@ -441,6 +442,7 @@ function renderModule(module) {
         case 'admin-activity-invite': html = renderAdminActivityInviteModule(); break;
         case 'admin-activity-invite': html = renderAdminActivityInviteModule(); break;
         case 'admin-activity-checkin': html = renderAdminActivityCheckinModule(); break;
         case 'admin-activity-checkin': html = renderAdminActivityCheckinModule(); break;
         case 'admin-activity-lottery': html = renderAdminActivityLotteryModule(); break;
         case 'admin-activity-lottery': html = renderAdminActivityLotteryModule(); break;
+        case 'admin-license': html = renderAdminLicenseModule(); break;
     }
     }
     panels.innerHTML = html;
     panels.innerHTML = html;
     panels.querySelectorAll('input[data-raw]').forEach(input => {
     panels.querySelectorAll('input[data-raw]').forEach(input => {
@@ -744,10 +746,19 @@ const FIELD_DESC = {
     'businessLicenseInfo': [
     'businessLicenseInfo': [
         ['data.id', 'Long', '执照ID(null=未上传)'],
         ['data.id', 'Long', '执照ID(null=未上传)'],
         ['data.userId', 'Long', '用户ID'],
         ['data.userId', 'Long', '用户ID'],
-        ['data.licenseImageUrl', 'String', '执照图片URL'],
+        ['data.storeName', 'String', '店铺名称'],
+        ['data.terminalType', 'String', '终端类型:SINGLE-单店/CHAIN-连锁/CLINIC-诊所/COMMUNITY_HEALTH-社康'],
+        ['data.province', 'String', '省'],
+        ['data.city', 'String', '市'],
+        ['data.district', 'String', '区'],
+        ['data.storeAddress', 'String', '店铺详细地址'],
+        ['data.contactPerson', 'String', '联系人姓名'],
+        ['data.contactPhone', 'String', '联系电话'],
+        ['data.licenseImageUrl', 'String', '营业执照图片URL'],
+        ['data.drugLicenseUrl', 'String', '药品经营许可证图片URL'],
+        ['data.medicalDeviceClass2Url', 'String', '二类医疗器械备案图片URL'],
+        ['data.medicalDeviceClass3Url', 'String', '三类医疗器械备案图片URL'],
         ['data.creditCode', 'String', '统一社会信用代码'],
         ['data.creditCode', 'String', '统一社会信用代码'],
-        ['data.pharmacyName', 'String', '药店名称'],
-        ['data.legalPerson', 'String', '法定代表人'],
         ['data.reviewStatus', 'String', '审核状态:PENDING/APPROVED/REJECTED'],
         ['data.reviewStatus', 'String', '审核状态:PENDING/APPROVED/REJECTED'],
         ['data.showVerifiedBadge', 'Boolean', '是否显示认证标识'],
         ['data.showVerifiedBadge', 'Boolean', '是否显示认证标识'],
         ['data.reviewRemark', 'String', '审核备注'],
         ['data.reviewRemark', 'String', '审核备注'],
@@ -913,20 +924,29 @@ function renderActivityLotteryModule() {
         [{label:'条数(limit,可选)', name:'limit', placeholder:'20', default:'20'}], true, 'activityLotteryRecords', '获取当前用户的抽奖历史记录,包含奖品名称、奖励类型、会员天数、是否中奖等完整中奖信息')}`;
         [{label:'条数(limit,可选)', name:'limit', placeholder:'20', default:'20'}], true, 'activityLotteryRecords', '获取当前用户的抽奖历史记录,包含奖品名称、奖励类型、会员天数、是否中奖等完整中奖信息')}`;
 }
 }
 
 
-// ==================== 用户端:营业执照管理(公用模块) ====================
+// ==================== 用户端:入驻信息提交模块 ====================
 function renderBusinessLicenseModule() {
 function renderBusinessLicenseModule() {
     return `
     return `
     <div class="batch-buttons">
     <div class="batch-buttons">
-        <button class="btn btn-warning btn-sm" onclick="runBusinessLicenseTests()">运行营业执照测试</button>
+        <button class="btn btn-warning btn-sm" onclick="runBusinessLicenseTests()">运行入驻信息提交测试</button>
     </div>
     </div>
-    ${testSection('business-license-info', '我的营业执照信息', 'GET', 'get', '/api/business-license/info', '需认证', 'auth-required', null, true, 'businessLicenseInfo', '查询当前用户的营业执照提交状态和审核结果。审核通过后可在多种活动中获得权益:比价抽奖额外机会、邀请有礼资格认定等')}
-    ${testSection('business-license-upload', '提交营业执照(JSON)', 'POST', 'post', '/api/business-license/upload', '需认证', 'auth-required',
+    ${testSection('business-license-info', '我的入驻信息', 'GET', 'get', '/api/business-license/info', '需认证', 'auth-required', null, true, 'businessLicenseInfo', '查询当前用户的入驻信息提交状态和审核结果:店铺名称、终端类型、地址、联系人、营业执照、药品经营许可证、医疗器械备案等信息')}
+    ${testSection('business-license-upload', '提交入驻信息(JSON)', 'POST', 'post', '/api/business-license/upload', '需认证', 'auth-required',
         [
         [
-         {label:'执照图片URL(licenseImageUrl)', name:'licenseImageUrl', placeholder:'https://example.com/license.jpg', default:''},
-         {label:'统一社会信用代码(creditCode)', name:'creditCode', placeholder:'91110000XXXXXXXXXX', default:''},
-         {label:'药店名称(pharmacyName)', name:'pharmacyName', placeholder:'XX大药房', default:''},
-         {label:'法定代表人(legalPerson,可选)', name:'legalPerson', placeholder:'张三', default:''}
-        ], true, 'businessLicenseInfo', '上传营业执照参与资质审核。审核通过后可获认证标识、解锁邀请有礼奖励、获取比价抽奖资格')}
+         {label:'店铺名称(storeName) *', name:'storeName', placeholder:'XX大药房', default:''},
+         {label:'终端类型(terminalType) *', name:'terminalType', placeholder:'SINGLE/CHAIN/CLINIC/COMMUNITY_HEALTH', default:'SINGLE'},
+         {label:'省(province) *', name:'province', placeholder:'广东省', default:''},
+         {label:'市(city) *', name:'city', placeholder:'深圳市', default:''},
+         {label:'区(district) *', name:'district', placeholder:'南山区', default:''},
+         {label:'店铺地址(storeAddress) *', name:'storeAddress', placeholder:'科技园路1号', default:''},
+         {label:'联系人(contactPerson) *', name:'contactPerson', placeholder:'张三', default:''},
+         {label:'电话(contactPhone) *', name:'contactPhone', placeholder:'13800138000', default:''},
+         {label:'营业执照URL(businessLicenseUrl) *', name:'businessLicenseUrl', placeholder:'https://example.com/yyzz.jpg', default:''},
+         {label:'药品经营许可证URL(drugLicenseUrl) *', name:'drugLicenseUrl', placeholder:'https://example.com/ypjy.jpg', default:''},
+         {label:'信用代码(creditCode)', name:'creditCode', placeholder:'91440300XXXXXXXXXX', default:''},
+         {label:'二类医疗器械备案(medicalDeviceClass2Url)', name:'medicalDeviceClass2Url', placeholder:'https://example.com/elqx.jpg', default:''},
+         {label:'三类医疗器械备案(medicalDeviceClass3Url)', name:'medicalDeviceClass3Url', placeholder:'https://example.com/slqx.jpg', default:''}
+        ], true, 'businessLicenseInfo', '提交入驻信息参与审核,10个必填字段:店铺名称、终端类型、省市区、地址、联系人、电话、营业执照、药品经营许可证;3个选填:信用代码、二类/三类医疗器械备案')}
     <div class="test-section">
     <div class="test-section">
         <div class="section-title">
         <div class="section-title">
             <span class="method method-post">POST</span>
             <span class="method method-post">POST</span>
@@ -934,29 +954,18 @@ function renderBusinessLicenseModule() {
             <span class="side-tag side-user">用户侧</span>
             <span class="side-tag side-user">用户侧</span>
             <span class="auth-required auth-tag">需认证</span>
             <span class="auth-required auth-tag">需认证</span>
         </div>
         </div>
-        <div class="section-desc">选择营业执照图片文件上传至阿里云OSS,返回可公开访问的URL。仅需选择文件即可上传成功,信用代码和药店名称为可选填项。支持 JPG/PNG 格式</div>
+        <div class="section-desc">选择资质文件上传至阿里云OSS,返回可公开访问的URL。每会话最多10次。仅需选择文件即可上传,返回URL后填入上方资质URL字段提交</div>
         <details class="field-desc-details">
         <details class="field-desc-details">
             <summary class="field-desc-summary">📞 cURL 调用示例</summary>
             <summary class="field-desc-summary">📞 cURL 调用示例</summary>
-            <pre class="curl-example"># 最简上传(仅图片):
-curl -X POST "\${BASE_URL}/api/business-license/upload-file" \\
-  -H "Authorization: Bearer \${TOKEN}" \\
-  -F "file=@/path/to/license.jpg"
-
-# 完整上传(含执照信息):
-curl -X POST "\${BASE_URL}/api/business-license/upload-file" \\
-  -H "Authorization: Bearer \${TOKEN}" \\
-  -F "file=@/path/to/license.jpg" \\
-  -F "creditCode=91110000XXXXXXXXXX" \\
-  -F "pharmacyName=XX大药房" \\
-  -F "legalPerson=张三"</pre>
+            <pre class="curl-example"># 上传资质文件:
+curl -X POST "${BASE_URL}/api/business-license/upload-file" \\
+  -H "Authorization: Bearer ${TOKEN}" \\
+  -F "file=@/path/to/license.jpg"</pre>
         </details>
         </details>
         <div class="form-grid">
         <div class="form-grid">
-            <div class="form-group"><label>执照图片文件(file) *</label><input type="file" id="business-license-upload-file-file" accept="image/*"></div>
-            <div class="form-group"><label>统一社会信用代码(creditCode,可选)</label><input type="text" id="business-license-upload-file-creditCode" placeholder="91110000XXXXXXXXXX"></div>
-            <div class="form-group"><label>药店名称(pharmacyName,可选)</label><input type="text" id="business-license-upload-file-pharmacyName" placeholder="XX大药房"></div>
-            <div class="form-group"><label>法定代表人(legalPerson,可选)</label><input type="text" id="business-license-upload-file-legalPerson" placeholder="张三"></div>
+            <div class="form-group"><label>资质文件(file) *</label><input type="file" id="business-license-upload-file-file" accept="image/*"></div>
         </div>
         </div>
-        <button class="btn btn-primary" onclick="runBusinessLicenseFileUpload('business-license-upload-file')">执行测试</button>
+        <button class="btn btn-primary" onclick="runBusinessLicenseFileUpload('business-license-upload-file')">上传文件</button>
         <div class="response-box" id="business-license-upload-file-box" style="display:none;">
         <div class="response-box" id="business-license-upload-file-box" style="display:none;">
             <div class="response-header">
             <div class="response-header">
                 <span class="response-title">响应 <span id="business-license-upload-file-duration" style="color:#999;font-weight:400;"></span></span>
                 <span class="response-title">响应 <span id="business-license-upload-file-duration" style="color:#999;font-weight:400;"></span></span>
@@ -1067,8 +1076,8 @@ function renderAdminActivityInviteModule() {
          {label:'邀请人最多奖励次数(maxInviterReward)', name:'maxInviterReward', placeholder:'30', default:'30'},
          {label:'邀请人最多奖励次数(maxInviterReward)', name:'maxInviterReward', placeholder:'30', default:'30'},
          {label:'奖励触发模式(rewardTrigger)', name:'rewardTrigger', placeholder:'REGISTRATION/LICENSE_APPROVED', default:'LICENSE_APPROVED'},
          {label:'奖励触发模式(rewardTrigger)', name:'rewardTrigger', placeholder:'REGISTRATION/LICENSE_APPROVED', default:'LICENSE_APPROVED'},
          {label:'要求小程序用户(requireMiniappInviter)', name:'requireMiniappInviter', placeholder:'0不要求/1要求', default:'1'},
          {label:'要求小程序用户(requireMiniappInviter)', name:'requireMiniappInviter', placeholder:'0不要求/1要求', default:'1'},
-         {label:'备注(remark)', name:'remark', placeholder:'邀请有礼活动', default:'邀请有礼:被邀请人资质审核通过后双方各得30天高级会员'}
-        ], true, 'adminActivityInviteConfig', '更新邀请有礼活动规则。rewardTrigger=REGISTRATION注册即发,LICENSE_APPROVED资质审核通过后发')}
+         {label:'备注(remark)', name:'remark', placeholder:'邀请有礼活动', default:'邀请有礼:被邀请人入驻审核通过后双方各得30天高级会员'}
+        ], true, 'adminActivityInviteConfig', '更新邀请有礼活动规则。rewardTrigger=REGISTRATION注册即发,LICENSE_APPROVED入驻审核通过后发')}
     ${testSection('admin-activity-invite-revoke', '撤销邀请奖励(反作弊)', 'POST', 'post', '/api/admin/activity/invite/revoke', '需认证', 'auth-required',
     ${testSection('admin-activity-invite-revoke', '撤销邀请奖励(反作弊)', 'POST', 'post', '/api/admin/activity/invite/revoke', '需认证', 'auth-required',
         [
         [
          {label:'被邀请人ID(inviteeId)', name:'inviteeId', placeholder:'作弊者用户ID', default:''},
          {label:'被邀请人ID(inviteeId)', name:'inviteeId', placeholder:'作弊者用户ID', default:''},
@@ -1153,6 +1162,28 @@ function renderAdminActivityLotteryModule() {
         ], true, 'adminActivityRewards', '按活动类型统一查询三种活动的中奖/奖励记录。type: INVITE-邀请有礼 / CHECKIN-签到有礼 / LOTTERY-比价抽奖')}`;
         ], true, 'adminActivityRewards', '按活动类型统一查询三种活动的中奖/奖励记录。type: INVITE-邀请有礼 / CHECKIN-签到有礼 / LOTTERY-比价抽奖')}`;
 }
 }
 
 
+// ==================== 运营端:入驻信息审核模块 ====================
+function renderAdminLicenseModule() {
+    return `
+    <div class="batch-buttons">
+        <button class="btn btn-warning btn-sm" onclick="runAdminLicenseTests()">运行入驻信息审核测试</button>
+    </div>
+    ${testSection('admin-license-list', '待审核列表', 'GET', 'get', '/api/admin/license/review?reviewStatus=PENDING', '需认证', 'auth-required',
+        [{label:'审核状态(reviewStatus,可选)', name:'reviewStatus', placeholder:'PENDING/APPROVED/REJECTED', default:'PENDING'}],
+        true, null, '查询入驻信息审核列表。status: PENDING-待审核, APPROVED-已通过, REJECTED-已拒绝')}
+    ${testSection('admin-license-approve', '审核通过', 'POST', 'post', '/api/admin/license/review', '需认证', 'auth-required',
+        [
+         {label:'资质ID(licenseId) *', name:'licenseId', placeholder:'1', default:'1'},
+         {label:'审核动作(action)', name:'action', placeholder:'APPROVED', default:'APPROVED'}
+        ], true, null, '审核通过入驻信息。通过后设置认证标识、首次通过赠送30天高级会员')}
+    ${testSection('admin-license-reject', '审核拒绝', 'POST', 'post', '/api/admin/license/review', '需认证', 'auth-required',
+        [
+         {label:'资质ID(licenseId) *', name:'licenseId', placeholder:'1', default:'1'},
+         {label:'审核动作(action)', name:'action', placeholder:'REJECTED', default:'REJECTED'},
+         {label:'拒绝原因(rejectReason)', name:'rejectReason', placeholder:'图片不清晰', default:''}
+        ], true, null, '拒绝入驻信息申请,需填写拒绝原因供申请人查看')}`;
+}
+
 // ===================== 批量测试 ====================
 // ===================== 批量测试 ====================
 function getSectionsInModule(modulePrefix) {
 function getSectionsInModule(modulePrefix) {
     return Array.from(document.querySelectorAll('.test-section')).filter(s => {
     return Array.from(document.querySelectorAll('.test-section')).filter(s => {
@@ -1261,17 +1292,10 @@ function runActivityAvailableTests() { runBatchTests('activity-available'); }
 async function runBusinessLicenseFileUpload(sectionId) {
 async function runBusinessLicenseFileUpload(sectionId) {
     const fileInput = document.getElementById(sectionId + '-file');
     const fileInput = document.getElementById(sectionId + '-file');
     const file = fileInput?.files[0];
     const file = fileInput?.files[0];
-    if (!file) { alert('请先选择营业执照图片文件'); return; }
-
-    const creditCode = document.getElementById(sectionId + '-creditCode')?.value?.trim() || '';
-    const pharmacyName = document.getElementById(sectionId + '-pharmacyName')?.value?.trim() || '';
-    const legalPerson = document.getElementById(sectionId + '-legalPerson')?.value?.trim() || '';
+    if (!file) { alert('请先选择资质文件'); return; }
 
 
     const formData = new FormData();
     const formData = new FormData();
     formData.append('file', file);
     formData.append('file', file);
-    formData.append('creditCode', creditCode);
-    formData.append('pharmacyName', pharmacyName);
-    if (legalPerson) formData.append('legalPerson', legalPerson);
 
 
     const box = document.getElementById(sectionId + '-box');
     const box = document.getElementById(sectionId + '-box');
     const statusEl = document.getElementById(sectionId + '-status');
     const statusEl = document.getElementById(sectionId + '-status');
@@ -1324,6 +1348,7 @@ function runAdminCheckinTests() { runBatchTests('admin-checkin'); }
 function runAdminActivityInviteTests() { runBatchTests('admin-activity-invite'); }
 function runAdminActivityInviteTests() { runBatchTests('admin-activity-invite'); }
 function runAdminActivityCheckinTests() { runBatchTests('admin-activity-checkin'); }
 function runAdminActivityCheckinTests() { runBatchTests('admin-activity-checkin'); }
 function runAdminActivityLotteryTests() { runBatchTests('admin-activity-lottery'); }
 function runAdminActivityLotteryTests() { runBatchTests('admin-activity-lottery'); }
+function runAdminLicenseTests() { runBatchTests('admin-license'); }
 
 
 // ===================== 验证器 =====================
 // ===================== 验证器 =====================
 function validate_checkinStatus(result) { return result.data?.code === 200 && result.data?.data?.checkedToday !== undefined; }
 function validate_checkinStatus(result) { return result.data?.code === 200 && result.data?.data?.checkedToday !== undefined; }
@@ -1364,6 +1389,9 @@ function validate_adminActivityRewards(result) { return result.data?.code === 20
 function validate_businessLicenseInfo(result) { return result.data?.code === 200 && result.data?.data !== undefined; }
 function validate_businessLicenseInfo(result) { return result.data?.code === 200 && result.data?.data !== undefined; }
 function validate_businessLicenseUpload(result) { return result.data?.code === 200; }
 function validate_businessLicenseUpload(result) { return result.data?.code === 200; }
 function validate_businessLicenseUploadFile(result) { return result.data?.code === 200 && typeof result.data?.data === 'string'; }
 function validate_businessLicenseUploadFile(result) { return result.data?.code === 200 && typeof result.data?.data === 'string'; }
+function validate_adminLicenseList(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
+function validate_adminLicenseApprove(result) { return result.data?.code === 200; }
+function validate_adminLicenseReject(result) { return result.data?.code === 200; }
 
 
 // ===================== 初始化 =====================
 // ===================== 初始化 =====================
 window.addEventListener('DOMContentLoaded', () => {
 window.addEventListener('DOMContentLoaded', () => {

+ 22 - 13
zhijiayun-test/src/main/resources/static/invite-test.html

@@ -230,7 +230,7 @@
         <div class="module-tab" data-module="upload">7.文件上传</div>
         <div class="module-tab" data-module="upload">7.文件上传</div>
         <div class="module-tab" data-module="search">8.搜索查询</div>
         <div class="module-tab" data-module="search">8.搜索查询</div>
         <div class="module-tab" data-module="platform-account">9.平台账号</div>
         <div class="module-tab" data-module="platform-account">9.平台账号</div>
-        <div class="module-tab" data-module="business-license">10.营业执照</div>
+        <div class="module-tab" data-module="business-license">10.入驻信息</div>
         <div class="module-tab" data-module="platform-config">11.平台配置</div>
         <div class="module-tab" data-module="platform-config">11.平台配置</div>
         <div class="module-tab" data-module="search-record">12.搜索记录</div>
         <div class="module-tab" data-module="search-record">12.搜索记录</div>
         <div class="module-tab" data-module="platform-session">13.平台会话</div>
         <div class="module-tab" data-module="platform-session">13.平台会话</div>
@@ -242,7 +242,7 @@
         <div class="module-tab" data-module="admin-invite">2.邀请管理</div>
         <div class="module-tab" data-module="admin-invite">2.邀请管理</div>
         <div class="module-tab" data-module="admin-crawler">3.爬虫管理</div>
         <div class="module-tab" data-module="admin-crawler">3.爬虫管理</div>
         <div class="module-tab" data-module="admin-coupon">4.优惠券发放</div>
         <div class="module-tab" data-module="admin-coupon">4.优惠券发放</div>
-        <div class="module-tab" data-module="admin-license">5.执照审核</div>
+        <div class="module-tab" data-module="admin-license">5.入驻信息审核</div>
         <div class="module-tab" data-module="admin-payment">6.支付方案</div>
         <div class="module-tab" data-module="admin-payment">6.支付方案</div>
         <div class="module-tab" data-module="admin-anxin">7.安心付管理</div>
         <div class="module-tab" data-module="admin-anxin">7.安心付管理</div>
         <div class="module-tab" data-module="admin-trial-quota">8.体验配额</div>
         <div class="module-tab" data-module="admin-trial-quota">8.体验配额</div>
@@ -1550,14 +1550,23 @@ function renderPlatformAccountModule() {
 function renderBusinessLicenseModule() {
 function renderBusinessLicenseModule() {
     return `
     return `
     <div class="batch-buttons">
     <div class="batch-buttons">
-        <button class="btn btn-warning btn-sm" onclick="runBusinessLicenseTests()">运行全部营业执照测试</button>
+        <button class="btn btn-warning btn-sm" onclick="runBusinessLicenseTests()">运行全部入驻信息提交测试</button>
     </div>
     </div>
-    ${testSection('bl-upload', '上传执照(JSON)', 'POST', 'post', '/api/business-license/upload', '需认证', 'auth-required',
-        [{label:'图片URL(licenseImageUrl)', name:'licenseImageUrl', placeholder:'https://oss.example.com/license/xxx.jpg', default:'https://oss.example.com/license/test.jpg'},
+    ${testSection('bl-upload', '提交入驻信息(JSON)', 'POST', 'post', '/api/business-license/upload', '需认证', 'auth-required',
+        [{label:'店铺名称(storeName)', name:'storeName', placeholder:'XX大药房', default:'XX大药房'},
+         {label:'终端类型(terminalType)', name:'terminalType', placeholder:'SINGLE/CHAIN/CLINIC/COMMUNITY_HEALTH', default:'SINGLE'},
+         {label:'省(province)', name:'province', placeholder:'广东省', default:'广东省'},
+         {label:'市(city)', name:'city', placeholder:'深圳市', default:'深圳市'},
+         {label:'区(district)', name:'district', placeholder:'南山区', default:'南山区'},
+         {label:'店铺地址(storeAddress)', name:'storeAddress', placeholder:'科技园路1号', default:'科技园路1号'},
+         {label:'联系人(contactPerson)', name:'contactPerson', placeholder:'张三', default:'张三'},
+         {label:'电话(contactPhone)', name:'contactPhone', placeholder:'13800138000', default:'13800138000'},
+         {label:'营业执照URL(businessLicenseUrl)', name:'businessLicenseUrl', placeholder:'https://oss.example.com/license/xxx.jpg', default:'https://oss.example.com/license/test.jpg'},
+         {label:'药品经营许可证(drugLicenseUrl)', name:'drugLicenseUrl', placeholder:'https://oss.example.com/drug/xxx.jpg', default:'https://oss.example.com/drug/test.jpg'},
          {label:'信用代码(creditCode)', name:'creditCode', placeholder:'91110000XXXXXXXXXX', default:'91110000TEST000001'},
          {label:'信用代码(creditCode)', name:'creditCode', placeholder:'91110000XXXXXXXXXX', default:'91110000TEST000001'},
-         {label:'药店名称(pharmacyName)', name:'pharmacyName', placeholder:'XX大药房', default:'测试大药房'},
-         {label:'法人(legalPerson,可选)', name:'legalPerson', placeholder:'张三', default:'张三'}], false, null, '通过JSON方式上传营业执照(传图片URL)')}
-    ${testSection('bl-info', '查询执照信息', 'GET', 'get', '/api/business-license/info', '需认证', 'auth-required', null, true, 'businessLicense', '查询当前用户已上传的营业执照信息')}`;
+         {label:'二类医疗器械(medicalDeviceClass2Url)', name:'medicalDeviceClass2Url', placeholder:'https://oss.example.com/med2/xxx.jpg', default:''},
+         {label:'三类医疗器械(medicalDeviceClass3Url)', name:'medicalDeviceClass3Url', placeholder:'https://oss.example.com/med3/xxx.jpg', default:''}], false, null, '通过JSON方式提交入驻信息(10必填+3选填)')}
+    ${testSection('bl-info', '查询入驻信息', 'GET', 'get', '/api/business-license/info', '需认证', 'auth-required', null, true, 'businessLicense', '查询当前用户已提交的入驻信息')}`;
 }
 }
 
 
 function renderPlatformConfigModule() {
 function renderPlatformConfigModule() {
@@ -1680,12 +1689,12 @@ function renderAdminCouponModule() {
 
 
 function renderAdminLicenseModule() {
 function renderAdminLicenseModule() {
     return `
     return `
-    <div class="batch-buttons"><button class="btn btn-warning btn-sm" onclick="runAdminLicenseTests()">运行全部执照审核测试</button></div>
-    ${testSection('admin-license-list', '审核列表', 'GET', 'get', '/api/admin/license/review?reviewStatus=PENDING', '需认证', 'auth-required', null, true, 'businessLicense', '查询待审核/已审核的营业执照列表')}
-    ${testSection('admin-license-review', '审核执照', 'POST', 'post', '/api/admin/license/review', '需认证', 'auth-required',
-        [{label:'执照ID(licenseId)', name:'licenseId', placeholder:'1', default:'1'},
+    <div class="batch-buttons"><button class="btn btn-warning btn-sm" onclick="runAdminLicenseTests()">运行全部入驻信息审核测试</button></div>
+    ${testSection('admin-license-list', '审核列表', 'GET', 'get', '/api/admin/license/review?reviewStatus=PENDING', '需认证', 'auth-required', null, true, 'businessLicense', '查询待审核/已审核的入驻信息列表')}
+    ${testSection('admin-license-review', '审核资质', 'POST', 'post', '/api/admin/license/review', '需认证', 'auth-required',
+        [{label:'资质ID(licenseId)', name:'licenseId', placeholder:'1', default:'1'},
          {label:'动作(action)', name:'action', placeholder:'APPROVED/REJECTED', default:'APPROVED'},
          {label:'动作(action)', name:'action', placeholder:'APPROVED/REJECTED', default:'APPROVED'},
-         {label:'驳回原因(rejectReason)', name:'rejectReason', placeholder:'驳回时填写', default:''}], false, null, '审核营业执照,通过或驳回')}`;
+         {label:'驳回原因(rejectReason)', name:'rejectReason', placeholder:'驳回时填写', default:''}], false, null, '审核入驻信息,通过或驳回')}`;
 }
 }
 
 
 function renderAdminPaymentModule() {
 function renderAdminPaymentModule() {

+ 10 - 10
zhijiayun-user/src/main/java/com/xuekairui/user/controller/AdminLicenseController.java

@@ -30,7 +30,7 @@ import java.time.LocalDateTime;
 import java.util.List;
 import java.util.List;
 
 
 /**
 /**
- * 营业执照审核管理接口(运营端)
+ * 入驻信息审核管理接口(运营端)
  */
  */
 @Slf4j
 @Slf4j
 @RestController
 @RestController
@@ -63,17 +63,17 @@ public class AdminLicenseController {
 
 
         BusinessLicense license = businessLicenseMapper.selectById(request.getLicenseId());
         BusinessLicense license = businessLicenseMapper.selectById(request.getLicenseId());
         if (license == null) {
         if (license == null) {
-            return Result.error(404, "营业执照记录不存在");
+            return Result.error(404, "入驻信息记录不存在");
         }
         }
         if (!"PENDING".equals(license.getReviewStatus())) {
         if (!"PENDING".equals(license.getReviewStatus())) {
-            return Result.error(400, "该营业执照已审核,无法重复操作");
+            return Result.error(400, "该入驻信息已审核,无法重复操作");
         }
         }
 
 
         if ("APPROVED".equals(request.getAction())) {
         if ("APPROVED".equals(request.getAction())) {
             license.setReviewStatus("APPROVED");
             license.setReviewStatus("APPROVED");
             license.setShowVerifiedBadge(true);
             license.setShowVerifiedBadge(true);
 
 
-            // 营业执照首次审核通过,赠送 30 天高级会员
+            // 入驻信息首次审核通过,赠送 30 天高级会员
             Long userId = license.getUserId();
             Long userId = license.getUserId();
             if (userId != null && !hasApprovedLicense(userId, license.getId())) {
             if (userId != null && !hasApprovedLicense(userId, license.getId())) {
                 membershipService.grantOrExtendMembership(
                 membershipService.grantOrExtendMembership(
@@ -81,8 +81,8 @@ public class AdminLicenseController {
                         MembershipLevel.PRO,
                         MembershipLevel.PRO,
                         30,
                         30,
                         "LICENSE",
                         "LICENSE",
-                        "营业执照审核通过赠送30天高级会员");
-                log.info("营业执照审核通过,赠送用户{} 30天高级会员", userId);
+                        "入驻信息审核通过赠送30天高级会员");
+                log.info("入驻信息审核通过,赠送用户{} 30天高级会员", userId);
 
 
                 // 存入 Redis 事件队列,事务提交后由活动模块消费(邀请有礼:被邀请人资质通过后发邀请人奖励)
                 // 存入 Redis 事件队列,事务提交后由活动模块消费(邀请有礼:被邀请人资质通过后发邀请人奖励)
                 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@@ -91,7 +91,7 @@ public class AdminLicenseController {
                         try {
                         try {
                             activityEventRedisService.pushLicenseApproved(userId, license.getId(), "BUSINESS_LICENSE");
                             activityEventRedisService.pushLicenseApproved(userId, license.getId(), "BUSINESS_LICENSE");
                         } catch (Exception e) {
                         } catch (Exception e) {
-                            log.warn("存入资质审核通过事件失败,不影响主流程: userId={}", userId, e);
+                            log.warn("存入入驻审核通过事件失败,不影响主流程: userId={}", userId, e);
                         }
                         }
                     }
                     }
                 });
                 });
@@ -108,7 +108,7 @@ public class AdminLicenseController {
         license.setReviewTime(LocalDateTime.now());
         license.setReviewTime(LocalDateTime.now());
         businessLicenseMapper.updateById(license);
         businessLicenseMapper.updateById(license);
 
 
-        log.info("营业执照审核完成: licenseId={}, action={}, reviewerId={}",
+        log.info("入驻信息审核完成: licenseId={}, action={}, reviewerId={}",
                 request.getLicenseId(), request.getAction(), reviewerId);
                 request.getLicenseId(), request.getAction(), reviewerId);
 
 
         // 记录审计日志
         // 记录审计日志
@@ -120,7 +120,7 @@ public class AdminLicenseController {
     }
     }
 
 
     /**
     /**
-     * 检查用户是否已有其他审核通过的营业执照(避免重复赠送会员)
+     * 检查用户是否已有其他审核通过的入驻信息(避免重复赠送会员)
      */
      */
     private boolean hasApprovedLicense(Long userId, Long excludeLicenseId) {
     private boolean hasApprovedLicense(Long userId, Long excludeLicenseId) {
         Long count = businessLicenseMapper.selectCount(
         Long count = businessLicenseMapper.selectCount(
@@ -133,7 +133,7 @@ public class AdminLicenseController {
 
 
     @Data
     @Data
     public static class ReviewRequest {
     public static class ReviewRequest {
-        @NotNull(message = "营业执照ID不能为空")
+        @NotNull(message = "入驻信息ID不能为空")
         private Long licenseId;
         private Long licenseId;
 
 
         @NotBlank(message = "审核动作不能为空")
         @NotBlank(message = "审核动作不能为空")

+ 106 - 30
zhijiayun-user/src/main/java/com/xuekairui/user/controller/BusinessLicenseController.java

@@ -10,11 +10,14 @@ import jakarta.validation.constraints.NotBlank;
 import lombok.Data;
 import lombok.Data;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartFile;
 
 
+import java.util.concurrent.TimeUnit;
+
 /**
 /**
- * 营业执照控制器
+ * 入驻信息控制器
  */
  */
 @Slf4j
 @Slf4j
 @RestController
 @RestController
@@ -24,34 +27,30 @@ public class BusinessLicenseController {
 
 
     private final BusinessLicenseMapper businessLicenseMapper;
     private final BusinessLicenseMapper businessLicenseMapper;
     private final OssService ossService;
     private final OssService ossService;
+    private final RedisTemplate<String, Object> redisTemplate;
 
 
     /**
     /**
-     * 上传/更新营业执照(JSON 方式,传图片URL
+     * 提交入驻信息(JSON 方式
      * POST /api/business-license/upload
      * POST /api/business-license/upload
-     * Content-Type: application/json
      */
      */
     @PostMapping("/upload")
     @PostMapping("/upload")
     public Result<Void> upload(
     public Result<Void> upload(
             @Valid @RequestBody UploadRequest request,
             @Valid @RequestBody UploadRequest request,
             HttpServletRequest httpRequest) {
             HttpServletRequest httpRequest) {
         Long userId = (Long) httpRequest.getAttribute("userId");
         Long userId = (Long) httpRequest.getAttribute("userId");
-        saveLicense(userId, request.getLicenseImageUrl(), request.getCreditCode(),
-                request.getPharmacyName(), request.getLegalPerson());
+        saveLicense(userId, request);
         return Result.success();
         return Result.success();
     }
     }
 
 
     /**
     /**
-     * 上传/更新营业执照(文件方式,multipart → OSS)
+     * 上传资质文件(multipart → OSS),仅返回URL不保存业务信息
      * POST /api/business-license/upload-file
      * POST /api/business-license/upload-file
-     * Content-Type: multipart/form-data
-     * Fields: file(required), creditCode(optional), pharmacyName(optional), legalPerson(optional)
+     * <p>
+     * 每个会话最多上传10次。
      */
      */
     @PostMapping("/upload-file")
     @PostMapping("/upload-file")
     public Result<String> uploadFile(
     public Result<String> uploadFile(
             @RequestParam("file") MultipartFile file,
             @RequestParam("file") MultipartFile file,
-            @RequestParam(value = "creditCode", required = false) String creditCode,
-            @RequestParam(value = "pharmacyName", required = false) String pharmacyName,
-            @RequestParam(value = "legalPerson", required = false) String legalPerson,
             HttpServletRequest httpRequest) {
             HttpServletRequest httpRequest) {
 
 
         if (file.isEmpty()) {
         if (file.isEmpty()) {
@@ -59,15 +58,23 @@ public class BusinessLicenseController {
         }
         }
         Long userId = (Long) httpRequest.getAttribute("userId");
         Long userId = (Long) httpRequest.getAttribute("userId");
 
 
-        String fileUrl = ossService.upload(file);
-        log.info("营业执照文件上传至OSS成功: {}", fileUrl);
+        // 会话内上传次数限制(最多10次,2小时过期)
+        String rateKey = "bl:upload:count:" + userId;
+        Long count = redisTemplate.opsForValue().increment(rateKey);
+        if (count == 1) {
+            redisTemplate.expire(rateKey, 2, TimeUnit.HOURS);
+        }
+        if (count > 10) {
+            return Result.error(429, "每个会话最多上传10次资质文件");
+        }
 
 
-        saveLicense(userId, fileUrl, creditCode, pharmacyName, legalPerson);
+        String fileUrl = ossService.upload(file);
+        log.info("资质文件上传至OSS成功: {}", fileUrl);
         return Result.success(fileUrl);
         return Result.success(fileUrl);
     }
     }
 
 
     /**
     /**
-     * 查询营业执照信息
+     * 查询入驻信息
      * GET /api/business-license/info
      * GET /api/business-license/info
      */
      */
     @GetMapping("/info")
     @GetMapping("/info")
@@ -79,43 +86,112 @@ public class BusinessLicenseController {
 
 
     // ---- 私有辅助方法 ----
     // ---- 私有辅助方法 ----
 
 
-    private void saveLicense(Long userId, String imageUrl, String creditCode,
-                              String pharmacyName, String legalPerson) {
+    private void saveLicense(Long userId, UploadRequest req) {
         BusinessLicense existing = businessLicenseMapper.selectByUserId(userId);
         BusinessLicense existing = businessLicenseMapper.selectByUserId(userId);
         if (existing == null) {
         if (existing == null) {
             BusinessLicense license = BusinessLicense.builder()
             BusinessLicense license = BusinessLicense.builder()
                     .userId(userId)
                     .userId(userId)
-                    .licenseImageUrl(imageUrl)
-                    .creditCode(creditCode)
-                    .pharmacyName(pharmacyName)
-                    .legalPerson(legalPerson)
+                    // 必填字段
+                    .storeName(req.getStoreName())
+                    .terminalType(req.getTerminalType())
+                    .province(req.getProvince())
+                    .city(req.getCity())
+                    .district(req.getDistrict())
+                    .storeAddress(req.getStoreAddress())
+                    .contactPerson(req.getContactPerson())
+                    .contactPhone(req.getContactPhone())
+                    // 资质图片
+                    .licenseImageUrl(req.getBusinessLicenseUrl())
+                    .drugLicenseUrl(req.getDrugLicenseUrl())
+                    // 可选字段
+                    .creditCode(req.getCreditCode())
+                    .medicalDeviceClass2Url(req.getMedicalDeviceClass2Url())
+                    .medicalDeviceClass3Url(req.getMedicalDeviceClass3Url())
+                    // 状态
                     .reviewStatus("PENDING")
                     .reviewStatus("PENDING")
                     .showVerifiedBadge(false)
                     .showVerifiedBadge(false)
                     .build();
                     .build();
             businessLicenseMapper.insert(license);
             businessLicenseMapper.insert(license);
         } else {
         } else {
-            existing.setLicenseImageUrl(imageUrl);
-            existing.setCreditCode(creditCode);
-            existing.setPharmacyName(pharmacyName);
-            existing.setLegalPerson(legalPerson);
+            // 必填字段
+            existing.setStoreName(req.getStoreName());
+            existing.setTerminalType(req.getTerminalType());
+            existing.setProvince(req.getProvince());
+            existing.setCity(req.getCity());
+            existing.setDistrict(req.getDistrict());
+            existing.setStoreAddress(req.getStoreAddress());
+            existing.setContactPerson(req.getContactPerson());
+            existing.setContactPhone(req.getContactPhone());
+            // 资质图片
+            existing.setLicenseImageUrl(req.getBusinessLicenseUrl());
+            existing.setDrugLicenseUrl(req.getDrugLicenseUrl());
+            // 可选字段(非空才覆盖)
+            if (req.getCreditCode() != null) existing.setCreditCode(req.getCreditCode());
+            if (req.getMedicalDeviceClass2Url() != null) existing.setMedicalDeviceClass2Url(req.getMedicalDeviceClass2Url());
+            if (req.getMedicalDeviceClass3Url() != null) existing.setMedicalDeviceClass3Url(req.getMedicalDeviceClass3Url());
+            // 状态重置
             existing.setReviewStatus("PENDING");
             existing.setReviewStatus("PENDING");
             businessLicenseMapper.updateById(existing);
             businessLicenseMapper.updateById(existing);
         }
         }
     }
     }
 
 
     /**
     /**
-     * 上传请求体(JSON 方式)
+     * 入驻信息提交请求
      */
      */
     @Data
     @Data
     public static class UploadRequest {
     public static class UploadRequest {
 
 
-        @NotBlank(message = "执照图片URL不能为空")
-        private String licenseImageUrl;
+        // ===== 必填字段 =====
+
+        /** 店铺名称 */
+        @NotBlank(message = "店铺名称不能为空")
+        private String storeName;
+
+        /** 终端类型:SINGLE-单店, CHAIN-连锁, CLINIC-诊所, COMMUNITY_HEALTH-社康 */
+        @NotBlank(message = "终端类型不能为空")
+        private String terminalType;
+
+        /** 省 */
+        @NotBlank(message = "所在省不能为空")
+        private String province;
+
+        /** 市 */
+        @NotBlank(message = "所在市不能为空")
+        private String city;
+
+        /** 区 */
+        @NotBlank(message = "所在区不能为空")
+        private String district;
+
+        /** 店铺详细地址 */
+        @NotBlank(message = "店铺地址不能为空")
+        private String storeAddress;
+
+        /** 联系人姓名 */
+        @NotBlank(message = "联系人姓名不能为空")
+        private String contactPerson;
+
+        /** 联系电话 */
+        @NotBlank(message = "联系电话不能为空")
+        private String contactPhone;
+
+        /** 营业执照图片URL */
+        @NotBlank(message = "营业执照图片不能为空")
+        private String businessLicenseUrl;
+
+        /** 药品经营许可证图片URL */
+        @NotBlank(message = "药品经营许可证不能为空")
+        private String drugLicenseUrl;
+
+        // ===== 非必填字段 =====
 
 
+        /** 统一社会信用代码 */
         private String creditCode;
         private String creditCode;
 
 
-        private String pharmacyName;
+        /** 二类医疗器械备案图片URL */
+        private String medicalDeviceClass2Url;
 
 
-        private String legalPerson;
+        /** 三类医疗器械备案图片URL */
+        private String medicalDeviceClass3Url;
     }
     }
 }
 }

+ 3 - 3
zhijiayun-user/src/main/java/com/xuekairui/user/controller/ContentController.java

@@ -38,10 +38,10 @@ public class ContentController {
         List<Map<String, String>> faqs = List.of(
         List<Map<String, String>> faqs = List.of(
                 Map.of("q", "小程序和桌面版有什么区别?",
                 Map.of("q", "小程序和桌面版有什么区别?",
                        "a", "桌面版用于实时聚合比价和采购;小程序用于查看查询历史、管理执照和会员。查价功能请使用桌面版。"),
                        "a", "桌面版用于实时聚合比价和采购;小程序用于查看查询历史、管理执照和会员。查价功能请使用桌面版。"),
-                Map.of("q", "如何上传营业执照?",
-                       "a", "进入「我的 → 营业执照」,拍照或从相册选择上传。提交后运营会在1个工作日内审核。"),
+                Map.of("q", "如何提交入驻信息?",
+                       "a", "进入「我的 → 入驻信息」,填写店铺信息和上传资质图片。提交后运营会在1个工作日内审核。"),
                 Map.of("q", "为什么无法在桌面端查价?",
                 Map.of("q", "为什么无法在桌面端查价?",
-                       "a", "请确保:1)营业执照已通过审核;2)至少绑定了1个B2B平台账号。"),
+                       "a", "请确保:1)入驻信息已通过审核;2)至少绑定了1个B2B平台账号。"),
                 Map.of("q", "会员如何升级?",
                 Map.of("q", "会员如何升级?",
                        "a", "进入「我的 → 会员中心」,选择套餐后通过微信支付完成购买。")
                        "a", "进入「我的 → 会员中心」,选择套餐后通过微信支付完成购买。")
         );
         );

+ 49 - 5
zhijiayun-user/src/main/java/com/xuekairui/user/entity/BusinessLicense.java

@@ -9,12 +9,12 @@ import lombok.NoArgsConstructor;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
 
 
 /**
 /**
- * 营业执照信息实体(可选填写,非强制要求
+ * 入驻信息实体(综合审核
  * 
  * 
  * 功能说明:
  * 功能说明:
- * 1. 用户可以自愿上传营业执照,提升信任度
- * 2. 运营可以审核营业执照,给予认证标识
- * 3. 不强制要求上传,不影响基本使用
+ * 1. 用户可以自愿提交入驻信息(含营业执照、药品经营许可证、医疗器械备案),提升信任度
+ * 2. 运营可以审核入驻信息,给予认证标识
+ * 3. 不强制要求提交,不影响基本使用
  * 4. 审核后显示"已认证"标识,增强用户信任
  * 4. 审核后显示"已认证"标识,增强用户信任
  * 
  * 
  * @author ProPrice Team
  * @author ProPrice Team
@@ -43,7 +43,51 @@ public class BusinessLicense {
     @TableField("credit_code")
     @TableField("credit_code")
     private String creditCode;
     private String creditCode;
     
     
-    /** 药店名称(与执照一致) */
+    /** 店铺名称 */
+    @TableField("store_name")
+    private String storeName;
+
+    /** 终端类型:SINGLE-单店, CHAIN-连锁, CLINIC-诊所, COMMUNITY_HEALTH-社康 */
+    @TableField("terminal_type")
+    private String terminalType;
+
+    /** 店铺地址 */
+    @TableField("store_address")
+    private String storeAddress;
+
+    /** 省 */
+    @TableField("province")
+    private String province;
+
+    /** 市 */
+    @TableField("city")
+    private String city;
+
+    /** 区 */
+    @TableField("district")
+    private String district;
+
+    /** 联系人姓名 */
+    @TableField("contact_person")
+    private String contactPerson;
+
+    /** 联系电话 */
+    @TableField("contact_phone")
+    private String contactPhone;
+
+    /** 药品经营许可证图片URL */
+    @TableField("drug_license_url")
+    private String drugLicenseUrl;
+
+    /** 二类医疗器械备案图片URL */
+    @TableField("medical_device_class2_url")
+    private String medicalDeviceClass2Url;
+
+    /** 三类医疗器械备案图片URL */
+    @TableField("medical_device_class3_url")
+    private String medicalDeviceClass3Url;
+
+    /** 药店名称(与执照一致,旧字段保留兼容) */
     @TableField("pharmacy_name")
     @TableField("pharmacy_name")
     private String pharmacyName;
     private String pharmacyName;
     
     

+ 1 - 1
zhijiayun-user/src/main/java/com/xuekairui/user/entity/OperationAuditLog.java

@@ -43,7 +43,7 @@ public class OperationAuditLog {
     /** 操作设备信息(User-Agent) */
     /** 操作设备信息(User-Agent) */
     private String userAgent;
     private String userAgent;
 
 
-    /** 操作模块:USER-用户管理 LEVEL-等级配置 PLATFORM-平台配置 LICENSE-营业执照 INVITE-邀请系统 COUPON-优惠券 SYSTEM-系统管理 */
+    /** 操作模块:USER-用户管理 LEVEL-等级配置 PLATFORM-平台配置 LICENSE-入驻信息 INVITE-邀请系统 COUPON-优惠券 SYSTEM-系统管理 */
     private String module;
     private String module;
 
 
     /** 操作类型(见OperationType枚举) */
     /** 操作类型(见OperationType枚举) */

+ 1 - 1
zhijiayun-user/src/main/java/com/xuekairui/user/entity/UserMembership.java

@@ -29,7 +29,7 @@ public class UserMembership {
     /** 会员等级:PLUS/PRO/ULTRA */
     /** 会员等级:PLUS/PRO/ULTRA */
     private MembershipLevel level;
     private MembershipLevel level;
 
 
-    /** 来源:SYSTEM-系统默认 RECHARGE-充值购买 INVITE-邀请奖励 ADMIN_GRANT-运营发放 LICENSE-营业执照 */
+    /** 来源:SYSTEM-系统默认 RECHARGE-充值购买 INVITE-邀请奖励 ADMIN_GRANT-运营发放 LICENSE-入驻信息 */
     @Builder.Default
     @Builder.Default
     private String source = "SYSTEM";
     private String source = "SYSTEM";
 
 

+ 9 - 9
zhijiayun-user/src/main/java/com/xuekairui/user/enums/OperationType.java

@@ -46,15 +46,15 @@ public enum OperationType {
     /** 验证B2B平台账号 */
     /** 验证B2B平台账号 */
     ACCOUNT_VERIFY("ACCOUNT", "VERIFY", "验证B2B平台账号", false),
     ACCOUNT_VERIFY("ACCOUNT", "VERIFY", "验证B2B平台账号", false),
 
 
-    // ==================== 营业执照模块 ====================
-    /** 上传营业执照 */
-    LICENSE_UPLOAD("LICENSE", "UPLOAD", "上传营业执照", false),
-    /** 审核营业执照(通过) */
-    LICENSE_APPROVE("LICENSE", "APPROVE", "审核营业执照(通过)", true),
-    /** 审核营业执照(驳回) */
-    LICENSE_REJECT("LICENSE", "REJECT", "审核营业执照(驳回)", true),
-    /** 删除营业执照 */
-    LICENSE_DELETE("LICENSE", "DELETE", "删除营业执照", true),
+    // ==================== 入驻信息模块 ====================
+    /** 提交入驻信息 */
+    LICENSE_UPLOAD("LICENSE", "UPLOAD", "提交入驻信息", false),
+    /** 审核入驻信息(通过) */
+    LICENSE_APPROVE("LICENSE", "APPROVE", "审核入驻信息(通过)", true),
+    /** 审核入驻信息(驳回) */
+    LICENSE_REJECT("LICENSE", "REJECT", "审核入驻信息(驳回)", true),
+    /** 删除入驻信息 */
+    LICENSE_DELETE("LICENSE", "DELETE", "删除入驻信息", true),
 
 
     // ==================== 邀请系统模块 ====================
     // ==================== 邀请系统模块 ====================
     /** 生成邀请码 */
     /** 生成邀请码 */

+ 2 - 2
zhijiayun-user/src/main/java/com/xuekairui/user/mapper/BusinessLicenseMapper.java

@@ -6,13 +6,13 @@ import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Select;
 
 
 /**
 /**
- * 营业执照Mapper
+ * 入驻信息Mapper
  */
  */
 @Mapper
 @Mapper
 public interface BusinessLicenseMapper extends BaseMapper<BusinessLicense> {
 public interface BusinessLicenseMapper extends BaseMapper<BusinessLicense> {
 
 
     /**
     /**
-     * 查询用户的营业执照
+     * 查询用户的入驻信息
      */
      */
     @Select("SELECT * FROM t_business_license WHERE user_id = #{userId} LIMIT 1")
     @Select("SELECT * FROM t_business_license WHERE user_id = #{userId} LIMIT 1")
     BusinessLicense selectByUserId(Long userId);
     BusinessLicense selectByUserId(Long userId);

+ 1 - 1
zhijiayun-user/src/main/java/com/xuekairui/user/service/AuthService.java

@@ -501,7 +501,7 @@ public class AuthService {
 
 
         java.util.List<String> messages = new java.util.ArrayList<>();
         java.util.List<String> messages = new java.util.ArrayList<>();
         if (!"APPROVED".equals(licenseStatus)) {
         if (!"APPROVED".equals(licenseStatus)) {
-            messages.add("请先上传营业执照并通过审核");
+            messages.add("请先提交入驻信息并通过审核");
         }
         }
         if (boundCount == 0) {
         if (boundCount == 0) {
             messages.add("请至少绑定1个B2B平台账号");
             messages.add("请至少绑定1个B2B平台账号");