From 72b4e9b846c5b8201714ba1bc6ee0f230c5fad1b Mon Sep 17 00:00:00 2001 From: WorkBuddy Date: Thu, 17 Sep 2026 22:21:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(ops-backend/service-station):=20close=20Pha?= =?UTF-8?q?se=2019=20KEEP-VERIFY=20=E2=80=94=2025=20defects=20(unreachable?= =?UTF-8?q?=20announcements=20+=20station=20write=20privilege=20escalation?= =?UTF-8?q?;=20banner=20500=20+=20soft-delete=20leak;=20content=20publish?= =?UTF-8?q?=20dead-end;=20program=20approval=20dead-end/draft=20leak/zero?= =?UTF-8?q?=20counts/missing=20withdraw;=20zero-coverage=20test=20schema)?= =?UTF-8?q?=20;=20add=20V042=20announcement=20chain=20+=2034=20guard=20tes?= =?UTF-8?q?ts;=20full=20regression=20602=20green?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend - Service Station: expose the 「临时公告」 chain end-to-end (V042 table/service existed but had ZERO endpoints -> both admin and miniapp could never reach it): 5 operator endpoints (list/create/update/publish/archive) + 1 user-facing read endpoint. - Fix privilege escalation: create/update were guarded only by the class-level service_location.view, making V015's service_location.manage a never-used dead permission -> read-only roles could edit stations. Permissions moved to method level. - Add ServiceLocationOperatorController to OperatorEndpointPermissionCoverageTest (it was NOT scanned, which is why the escalation stayed invisible). - Add create/update validation (address NOT NULL + status CHECK only blew up on PG). - Banner: validation failures threw bare RuntimeException -> 500; now VALIDATION_ERROR -> 400. Add deleted_at IS NULL to both home queries (soft-deleted banners stayed live). - Content: operator list/detail split (admin «publish» was a dead end because it reused the published-only public API); category deactivate/reactivate; article body on edit. - Service Program: operator member list now includes pending (approval reachable); active-only public visibility (draft leak); memberCount/pendingCount aggregation; withdraw/removeMember; reuse row on re-apply (UNIQUE(program_id,user_id) crash). - MeController: add the missing GET /me/service-history route. Schema / guards - schema-h2.sql: add service_location_announcements (+ chk_announcement_status) and content_categories (+ uk_content_category_code, chk_category_status); move both into REQUIRED_IN_TEST_SCHEMA. MigrationValidationTest baseline 41 -> 42. - V11PermissionMatrixContractTest now scans ALL migrations instead of hardcoding V015+V029. - New: ServiceLocationAnnouncementPhase19IntegrationTest (24), ServiceLocationControllerPermissionContractTest (5), ServiceLocationAnnouncementConstraintParityTest (5, prod DDL <-> test schema <-> backend whitelist <-> admin labels). Frontend - admin-web: ServiceStationView gains announcement management (statuses, create/edit, publish/archive with confirm, expiry); write buttons gated by service_location.manage. - miniapp: U50 renders the 临时公告 block via new serviceStationService; page no longer talks to utils/api directly; announcement failure degrades to [] instead of erroring. - ui-tests: U37 four-group + U50 announcement contracts; /home assertion retargeted to the service layer. Verification - Negative verification: 3 injected defects (drop soft-delete filter / drop test-schema CHECK / downgrade write endpoint to view) -> 4 named tests fail precisely, no collateral failures; restored, NEG-VAL residue = 0. - Backend full regression: 602 tests, 0 failures, 0 errors, 3 skipped (baseline 420). - admin-web: vue-tsc --noEmit EXIT=0, vite build EXIT=0. Miniapp: tsc --noEmit EXIT=0. - ui-tests 14/14 PASS. --- admin-web/src/layouts/MainLayout.vue | 26 +- .../modules/banner/BannerManagementView.vue | 216 ++++++++- .../modules/content/ContentManagementView.vue | 199 +++++++-- .../service-program/ServiceProgramView.vue | 236 ++++++++-- .../service-station/ServiceStationView.vue | 311 ++++++++++++- .../HomeBannerOperatorController.java | 146 +++++- .../banner/mapper/HomeBannerMapper.java | 12 +- .../content/controller/ContentController.java | 42 ++ .../content/dto/ContentArticleDTO.java | 22 +- .../content/service/ContentService.java | 323 ++++++++++++-- .../platform/me/controller/MeController.java | 17 + .../me/service/ServiceHistoryService.java | 112 +++++ .../ServiceAppointmentController.java | 19 + .../controller/ServiceLocationController.java | 33 ++ .../ServiceLocationOperatorController.java | 153 ++++++- .../entity/ServiceLocationAnnouncement.java | 31 ++ .../ServiceLocationAnnouncementMapper.java | 31 ++ .../service/ServiceAppointmentService.java | 40 +- .../ServiceLocationAnnouncementService.java | 221 +++++++++ .../controller/ServiceProgramController.java | 62 ++- .../dto/ServiceProgramDTO.java | 8 + .../mapper/ServiceProgramMemberMapper.java | 36 +- .../service/ServiceProgramService.java | 420 +++++++++++++++--- .../platform/shared/constant/Constants.java | 11 + ...42__add_service_location_announcements.sql | 39 ++ .../BannerOperatorPhase19IntegrationTest.java | 287 ++++++++++++ ...ntentControllerPermissionContractTest.java | 5 +- ...ContentOperatorPhase19IntegrationTest.java | 293 ++++++++++++ .../contract/MeRouteContractTest.java | 3 + .../BannerConstraintParityTest.java | 166 +++++++ .../deployment/EntityMigrationParityTest.java | 19 +- .../deployment/MigrationValidationTest.java | 4 +- ...ationAnnouncementConstraintParityTest.java | 145 ++++++ ...peratorEndpointPermissionCoverageTest.java | 1 + .../V11PermissionMatrixContractTest.java | 30 +- .../ServiceAppointmentOwnerGuardTest.java | 333 ++++++++++++++ ...ionAnnouncementPhase19IntegrationTest.java | 401 +++++++++++++++++ ...ationControllerPermissionContractTest.java | 107 +++++ ...ogramControllerPermissionContractTest.java | 76 ++++ ...ProgramOperatorPhase19IntegrationTest.java | 336 ++++++++++++++ backend/src/test/resources/schema-h2.sql | 88 +++- docs/JIRA_BACKLOG.md | 5 +- ...1-Recovery-Completion-Report-2026-09-17.md | 217 +++++++++ docs/STATUS.md | 36 +- ...阳 V1.1 客户需求基线增量开发与修复计划.md | 30 +- .../pages/content-detail/content-detail.js | 50 ++- .../pages/content-detail/content-detail.wxml | 2 +- miniapp/pages/mine/mine.ts | 2 + miniapp/pages/mine/mine.wxml | 37 +- .../pages/my-appointments/my-appointments.js | 65 ++- .../my-appointments/my-appointments.wxml | 2 +- .../service-appointment.js | 93 +++- .../service-appointment.wxml | 2 +- .../pages/service-history/service-history.js | 51 ++- .../service-history/service-history.wxml | 2 +- .../pages/service-program/service-program.js | 65 ++- .../service-program/service-program.wxml | 2 +- .../pages/service-station/service-station.ts | 36 +- .../service-station/service-station.wxml | 16 + .../service-station/service-station.wxss | 7 + miniapp/project.config.json | 4 +- miniapp/services/contentService.js | 28 ++ miniapp/services/index.js | 12 + miniapp/services/serviceAppointmentService.js | 80 ++++ miniapp/services/serviceHistoryService.js | 15 + miniapp/services/serviceProgramService.js | 34 ++ miniapp/services/serviceStationService.js | 52 +++ miniapp/styles/service-page.wxss | 2 +- ui-tests/admin-role-navigation-contracts.mjs | 98 ++++ ui-tests/miniapp-reference-contracts.mjs | 50 ++- 70 files changed, 5847 insertions(+), 308 deletions(-) create mode 100644 backend/src/main/java/com/danyang/match/platform/me/service/ServiceHistoryService.java create mode 100644 backend/src/main/java/com/danyang/match/platform/service_appointment/controller/ServiceLocationController.java create mode 100644 backend/src/main/java/com/danyang/match/platform/service_appointment/entity/ServiceLocationAnnouncement.java create mode 100644 backend/src/main/java/com/danyang/match/platform/service_appointment/mapper/ServiceLocationAnnouncementMapper.java create mode 100644 backend/src/main/java/com/danyang/match/platform/service_appointment/service/ServiceLocationAnnouncementService.java create mode 100644 backend/src/main/resources/db/migration/V042__add_service_location_announcements.sql create mode 100644 backend/src/test/java/com/danyang/match/platform/banner/BannerOperatorPhase19IntegrationTest.java create mode 100644 backend/src/test/java/com/danyang/match/platform/content/ContentOperatorPhase19IntegrationTest.java create mode 100644 backend/src/test/java/com/danyang/match/platform/deployment/BannerConstraintParityTest.java create mode 100644 backend/src/test/java/com/danyang/match/platform/deployment/ServiceLocationAnnouncementConstraintParityTest.java create mode 100644 backend/src/test/java/com/danyang/match/platform/service_appointment/ServiceAppointmentOwnerGuardTest.java create mode 100644 backend/src/test/java/com/danyang/match/platform/service_appointment/ServiceLocationAnnouncementPhase19IntegrationTest.java create mode 100644 backend/src/test/java/com/danyang/match/platform/service_appointment/ServiceLocationControllerPermissionContractTest.java create mode 100644 backend/src/test/java/com/danyang/match/platform/service_program/ServiceProgramControllerPermissionContractTest.java create mode 100644 backend/src/test/java/com/danyang/match/platform/service_program/ServiceProgramOperatorPhase19IntegrationTest.java create mode 100644 docs/Phase-19-V11-Recovery-Completion-Report-2026-09-17.md create mode 100644 miniapp/services/contentService.js create mode 100644 miniapp/services/serviceAppointmentService.js create mode 100644 miniapp/services/serviceHistoryService.js create mode 100644 miniapp/services/serviceProgramService.js create mode 100644 miniapp/services/serviceStationService.js create mode 100644 ui-tests/admin-role-navigation-contracts.mjs diff --git a/admin-web/src/layouts/MainLayout.vue b/admin-web/src/layouts/MainLayout.vue index 6c6c294..9ceb563 100644 --- a/admin-web/src/layouts/MainLayout.vue +++ b/admin-web/src/layouts/MainLayout.vue @@ -136,20 +136,25 @@ import { Bell, Calendar, ChatDotRound, + Clock, Connection, DataAnalysis, DataBoard, DocumentChecked, Expand, Fold, + House, Location, LocationFilled, Lock, + Memo, Opportunity, + Picture, Reading, Search, Setting, StarFilled, + Tickets, User, UserFilled, Warning, @@ -169,14 +174,20 @@ const navigation: NavigationItem[] = [ { label: '资料审核', path: '/review', permissions: ['review.view'], icon: DocumentChecked }, { label: '匹配管理', path: '/activities/interests', permissions: ['activity.review_registration'], icon: StarFilled }, { label: '公益红娘', path: '/referral', permissions: ['referral.manage'], icon: Connection }, + { label: '红娘服务', path: '/matchmaker', permissions: ['matchmaker.service.view'], icon: Connection }, { label: '公益咨询', path: '/counseling', permissions: ['counseling.service.view'], icon: ChatDotRound }, { label: '活动管理', path: '/activities', permissions: ['activity.view'], icon: Calendar }, { label: '活动签到', path: '/activities/checkin', permissions: ['activity.checkin'], icon: Calendar }, + { label: 'Banner管理', path: '/banner', permissions: ['banner.view'], icon: Picture }, { label: '内容管理', path: '/content', permissions: ['content.view'], icon: Reading }, { label: '善缘服务站', path: '/service-station', permissions: ['service_location.view'], icon: Location }, + { label: '服务预约', path: '/service-appointment', permissions: ['service_appointment.view'], icon: House }, + { label: '专项服务', path: '/service-program', permissions: ['service_program.manage'], icon: Tickets }, { label: '举报风控', path: '/risk/reports', permissions: ['risk.report.view'], icon: Warning }, + { label: '申诉管理', path: '/appeals', permissions: ['risk.report.process'], icon: Memo }, { label: '数据统计', path: '/analytics', permissions: ['analytics.view'], icon: DataAnalysis }, { label: '组织人员', path: '/organizations', permissions: ['organization.manage'], icon: UserFilled }, + { label: '操作日志', path: '/audit', permissions: ['user.view'], icon: Clock }, { label: '系统设置', path: '/operators', permissions: ['permission.manage'], icon: Setting }, ] @@ -195,10 +206,17 @@ const currentTitle = computed(() => (route.meta?.title as string) || '页面') const operator = computed(() => authStore.operator) const permissions = computed(() => authStore.permissions || []) const currentRoleName = computed(() => { - if (permissions.value.includes('permission.manage')) return '平台管理员' - if (permissions.value.includes('counseling.service.view')) return '公益咨询人员' - if (permissions.value.includes('service_appointment.view')) return '服务站运营人员' - if (permissions.value.includes('review.view')) return '资料审核人员' + // 按权限从高到低判定,覆盖七类后台角色(平台管理员 / 咨询管理员 / 内容编辑员 / + // 风控运营 / 公益咨询 / 服务站运营 / 资料审核 / 公益红娘)。 + const perms = permissions.value + if (perms.includes('permission.manage')) return '平台管理员' + if (perms.includes('counseling.service.assign') || perms.includes('counseling.note.view')) return '咨询管理员' + if (perms.includes('content.publish')) return '内容编辑员' + if (perms.includes('risk.report.process')) return '风控运营人员' + if (perms.includes('counseling.service.view')) return '公益咨询人员' + if (perms.includes('service_appointment.view')) return '服务站运营人员' + if (perms.includes('review.view')) return '资料审核人员' + if (perms.includes('matchmaker.service.view')) return '公益红娘' return '运营人员' }) const visibleNavigation = computed(() => navigation.filter((item) => item.permissions.every(hasPerm))) diff --git a/admin-web/src/modules/banner/BannerManagementView.vue b/admin-web/src/modules/banner/BannerManagementView.vue index 5d01b45..c45725a 100644 --- a/admin-web/src/modules/banner/BannerManagementView.vue +++ b/admin-web/src/modules/banner/BannerManagementView.vue @@ -7,31 +7,42 @@ 新建 Banner + - + + + + - + - + - + - + - + + diff --git a/admin-web/src/modules/service-program/ServiceProgramView.vue b/admin-web/src/modules/service-program/ServiceProgramView.vue index cf1ec3f..c2cba41 100644 --- a/admin-web/src/modules/service-program/ServiceProgramView.vue +++ b/admin-web/src/modules/service-program/ServiceProgramView.vue @@ -1,41 +1,96 @@