From 767534df4865f4e9345d5ca3a12f517f25cb735f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=B9=8F?= Date: Tue, 14 Jul 2026 16:04:51 +0800 Subject: [PATCH] feat: harden phase one workflows --- .gitignore | 1 + admin-web/src/api/groupBuyApi.test.ts | 13 + admin-web/src/api/groupBuyApi.ts | 20 +- admin-web/src/api/orderApi.test.ts | 8 +- admin-web/src/api/orderApi.ts | 32 ++- admin-web/src/api/productApi.test.ts | 20 ++ admin-web/src/api/productApi.ts | 26 +- admin-web/src/styles.css | 6 + admin-web/src/types.ts | 7 + admin-web/src/views/ExpressOrderListView.vue | 35 ++- admin-web/src/views/GoodsOrderListView.vue | 37 ++- admin-web/src/views/GroupBuyListView.vue | 28 ++- admin-web/src/views/ProductListView.vue | 17 +- .../com/linhelp/common/api/PageUtils.java | 22 ++ .../common/security/AuthController.java | 5 + .../common/security/AuthProperties.java | 24 ++ .../linhelp/common/security/AuthService.java | 105 +++++++- .../common/security/LoginSessionIssuer.java | 5 + .../common/security/MiniappLoginRequest.java | 16 ++ .../common/security/MiniappSessionClient.java | 5 + .../security/SaTokenLoginSessionIssuer.java | 16 ++ .../security/WechatMiniappSessionClient.java | 47 ++++ .../common/security/account/AdminAccount.java | 55 ++++ .../account/AdminAccountRepository.java | 7 + .../account/JdbcAdminAccountRepository.java | 46 ++++ .../security/account/PasswordHasher.java | 63 +++++ .../delivery/DeliveryAdminController.java | 9 +- .../delivery/DeliveryAssignmentService.java | 39 ++- .../express/ExpressOrderController.java | 34 ++- .../linhelp/express/ExpressOrderService.java | 188 ++++++++++++-- .../linhelp/express/ExpressOrderStore.java | 9 + .../express/JdbcExpressOrderStore.java | 132 ++++++++++ .../linhelp/groupbuy/GroupBuyController.java | 54 +++- .../groupbuy/GroupBuyOrderService.java | 234 ++++++++++++++++-- .../linhelp/groupbuy/GroupBuyOrderStore.java | 9 + .../com/linhelp/groupbuy/GroupBuyService.java | 46 ++++ .../groupbuy/JdbcGroupBuyOrderStore.java | 113 +++++++++ .../com/linhelp/notice/NoticeController.java | 6 +- .../com/linhelp/notice/NoticeService.java | 8 + .../linhelp/order/GoodsOrderController.java | 41 ++- .../com/linhelp/order/GoodsOrderService.java | 195 +++++++++++++-- .../com/linhelp/order/GoodsOrderStore.java | 9 + .../linhelp/order/JdbcGoodsOrderStore.java | 185 ++++++++++++++ .../linhelp/orderlog/JdbcOrderLogStore.java | 66 +++++ .../com/linhelp/orderlog/OrderLogRecord.java | 105 ++++++++ .../com/linhelp/orderlog/OrderLogService.java | 99 ++++++++ .../com/linhelp/orderlog/OrderLogStore.java | 9 + .../linhelp/product/ProductController.java | 24 +- .../com/linhelp/product/ProductService.java | 46 ++++ .../secondhand/SecondGoodsController.java | 13 +- .../secondhand/SecondGoodsService.java | 48 ++++ .../linhelp/storage/FileStorageService.java | 55 +++- .../com/linhelp/storage/StorageWebConfig.java | 24 ++ .../src/main/resources/application-demo.yml | 10 + .../src/main/resources/application-dev.yml | 10 + .../V3__align_runtime_order_fields.sql | 32 +++ ...__admin_accounts_and_order_persistence.sql | 23 ++ .../common/security/AuthServiceTests.java | 148 +++++++++++ .../DeliveryAssignmentServiceTests.java | 106 ++++++-- .../express/ExpressOrderServiceTests.java | 106 +++++++- .../groupbuy/GroupBuyServiceTests.java | 103 +++++++- .../linhelp/order/GoodsOrderServiceTests.java | 96 ++++++- .../product/ProductInventoryTests.java | 38 +++ .../secondhand/SecondGoodsReviewTests.java | 27 +- .../storage/FileStorageServiceTests.java | 23 +- miniapp/miniprogram/api/productApi.js | 19 +- miniapp/miniprogram/api/productApi.test.js | 12 + miniapp/miniprogram/api/secondGoodsApi.js | 19 +- .../miniprogram/api/secondGoodsApi.test.js | 12 + miniapp/miniprogram/app.js | 1 + miniapp/miniprogram/app.wxss | 187 ++++++++++++-- miniapp/miniprogram/pages/address/index.wxml | 48 +++- miniapp/miniprogram/pages/address/index.wxss | 42 +++- miniapp/miniprogram/pages/express/create.wxml | 33 ++- miniapp/miniprogram/pages/express/create.wxss | 47 +++- .../miniprogram/pages/group-buy/detail.wxml | 41 ++- .../miniprogram/pages/group-buy/detail.wxss | 94 ++++++- .../miniprogram/pages/group-buy/index.wxml | 37 ++- .../miniprogram/pages/group-buy/index.wxss | 47 ++++ miniapp/miniprogram/pages/home/index.wxml | 135 +++++++--- miniapp/miniprogram/pages/home/index.wxss | 217 +++++++++++++++- miniapp/miniprogram/pages/notices/index.wxml | 29 ++- miniapp/miniprogram/pages/notices/index.wxss | 39 ++- miniapp/miniprogram/pages/orders/detail.wxml | 35 +-- miniapp/miniprogram/pages/orders/detail.wxss | 55 ++++ miniapp/miniprogram/pages/orders/index.wxml | 32 ++- miniapp/miniprogram/pages/orders/index.wxss | 39 +++ miniapp/miniprogram/pages/pageStyles.test.js | 68 +++++ .../miniprogram/pages/products/detail.wxml | 42 ++-- .../miniprogram/pages/products/detail.wxss | 66 ++++- miniapp/miniprogram/pages/products/index.js | 17 +- miniapp/miniprogram/pages/products/index.wxml | 36 ++- miniapp/miniprogram/pages/products/index.wxss | 73 +++++- miniapp/miniprogram/pages/profile/index.wxml | 42 +++- miniapp/miniprogram/pages/profile/index.wxss | 100 +++++++- .../miniprogram/pages/second-hand/create.js | 43 +++- .../miniprogram/pages/second-hand/create.wxml | 34 ++- .../miniprogram/pages/second-hand/create.wxss | 81 +++++- .../miniprogram/pages/second-hand/index.js | 11 +- .../miniprogram/pages/second-hand/index.wxml | 46 ++-- .../miniprogram/pages/second-hand/index.wxss | 69 +++++- miniapp/miniprogram/project.config.json | 28 +++ miniapp/miniprogram/utils/request.js | 73 ++++++ 103 files changed, 4849 insertions(+), 388 deletions(-) create mode 100644 admin-web/src/api/productApi.test.ts create mode 100644 backend/src/main/java/com/linhelp/common/api/PageUtils.java create mode 100644 backend/src/main/java/com/linhelp/common/security/AuthProperties.java create mode 100644 backend/src/main/java/com/linhelp/common/security/LoginSessionIssuer.java create mode 100644 backend/src/main/java/com/linhelp/common/security/MiniappLoginRequest.java create mode 100644 backend/src/main/java/com/linhelp/common/security/MiniappSessionClient.java create mode 100644 backend/src/main/java/com/linhelp/common/security/SaTokenLoginSessionIssuer.java create mode 100644 backend/src/main/java/com/linhelp/common/security/WechatMiniappSessionClient.java create mode 100644 backend/src/main/java/com/linhelp/common/security/account/AdminAccount.java create mode 100644 backend/src/main/java/com/linhelp/common/security/account/AdminAccountRepository.java create mode 100644 backend/src/main/java/com/linhelp/common/security/account/JdbcAdminAccountRepository.java create mode 100644 backend/src/main/java/com/linhelp/common/security/account/PasswordHasher.java create mode 100644 backend/src/main/java/com/linhelp/express/ExpressOrderStore.java create mode 100644 backend/src/main/java/com/linhelp/express/JdbcExpressOrderStore.java create mode 100644 backend/src/main/java/com/linhelp/groupbuy/GroupBuyOrderStore.java create mode 100644 backend/src/main/java/com/linhelp/groupbuy/JdbcGroupBuyOrderStore.java create mode 100644 backend/src/main/java/com/linhelp/order/GoodsOrderStore.java create mode 100644 backend/src/main/java/com/linhelp/order/JdbcGoodsOrderStore.java create mode 100644 backend/src/main/java/com/linhelp/orderlog/JdbcOrderLogStore.java create mode 100644 backend/src/main/java/com/linhelp/orderlog/OrderLogRecord.java create mode 100644 backend/src/main/java/com/linhelp/orderlog/OrderLogService.java create mode 100644 backend/src/main/java/com/linhelp/orderlog/OrderLogStore.java create mode 100644 backend/src/main/java/com/linhelp/storage/StorageWebConfig.java create mode 100644 backend/src/main/resources/db/migration/V3__align_runtime_order_fields.sql create mode 100644 backend/src/main/resources/db/migration/V4__admin_accounts_and_order_persistence.sql create mode 100644 backend/src/test/java/com/linhelp/common/security/AuthServiceTests.java create mode 100644 miniapp/miniprogram/api/productApi.test.js create mode 100644 miniapp/miniprogram/api/secondGoodsApi.test.js create mode 100644 miniapp/miniprogram/pages/pageStyles.test.js create mode 100644 miniapp/miniprogram/project.config.json diff --git a/.gitignore b/.gitignore index 6195f51..7d113fa 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ backend/logs/ deploy/.data/ miniapp/miniprogram_npm/ miniapp/private.* +**/project.private.config.json diff --git a/admin-web/src/api/groupBuyApi.test.ts b/admin-web/src/api/groupBuyApi.test.ts index 3e1efa5..7e57c2b 100644 --- a/admin-web/src/api/groupBuyApi.test.ts +++ b/admin-web/src/api/groupBuyApi.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from 'vitest' import { buildGroupBuyOrderActionPath, + buildGroupBuyOrderQuery, filterPendingDeliveryGroupBuyOrders, groupBuyOrderDispatchLabel } from './groupBuyApi' @@ -30,6 +31,18 @@ describe('group buy dispatch helpers', () => { expect(buildGroupBuyOrderActionPath(1, 'confirm')).toBe('/api/admin/group-buy-orders/1/confirm') expect(buildGroupBuyOrderActionPath(1, 'ready')).toBe('/api/admin/group-buy-orders/1/ready') }) + + it('builds group buy order page query params', () => { + expect(buildGroupBuyOrderQuery({ + status: 'PENDING_CONFIRM', + pageNo: 2, + pageSize: 10 + })).toEqual({ + status: 'PENDING_CONFIRM', + pageNo: 2, + pageSize: 10 + }) + }) }) function groupBuyOrder(overrides: Partial): GroupBuyOrder { diff --git a/admin-web/src/api/groupBuyApi.ts b/admin-web/src/api/groupBuyApi.ts index b6ecaad..ee55d03 100644 --- a/admin-web/src/api/groupBuyApi.ts +++ b/admin-web/src/api/groupBuyApi.ts @@ -1,4 +1,4 @@ -import type { GroupBuy, GroupBuyOrder } from '../types' +import type { GroupBuy, GroupBuyOrder, PageResponse } from '../types' import { apiGet, apiPost, apiPut } from './http' export function listGroupBuys() { @@ -9,6 +9,24 @@ export function listGroupBuyOrders(groupBuyId: number) { return apiGet(`/api/admin/group-buys/${groupBuyId}/orders`) } +export interface GroupBuyOrderQuery { + status?: string + pageNo?: number + pageSize?: number +} + +export function buildGroupBuyOrderQuery(query: GroupBuyOrderQuery = {}) { + const params: Record = {} + if (query.status) params.status = query.status + if (query.pageNo) params.pageNo = query.pageNo + if (query.pageSize) params.pageSize = query.pageSize + return params +} + +export function pageGroupBuyOrders(groupBuyId: number, query: GroupBuyOrderQuery = {}) { + return apiGet>(`/api/admin/group-buys/${groupBuyId}/orders/page`, buildGroupBuyOrderQuery(query)) +} + export function filterPendingDeliveryGroupBuyOrders(orders: GroupBuyOrder[]) { return orders.filter((order) => order.status === 'PENDING_DELIVERY') } diff --git a/admin-web/src/api/orderApi.test.ts b/admin-web/src/api/orderApi.test.ts index 4734466..cae55e1 100644 --- a/admin-web/src/api/orderApi.test.ts +++ b/admin-web/src/api/orderApi.test.ts @@ -6,11 +6,15 @@ describe('buildGoodsOrderQuery', () => { expect(buildGoodsOrderQuery({ status: 'PENDING_DELIVERY', startDate: '2026-07-01', - endDate: '2026-07-07' + endDate: '2026-07-07', + pageNo: 2, + pageSize: 20 })).toEqual({ status: 'PENDING_DELIVERY', startDate: '2026-07-01', - endDate: '2026-07-07' + endDate: '2026-07-07', + pageNo: 2, + pageSize: 20 }) }) }) diff --git a/admin-web/src/api/orderApi.ts b/admin-web/src/api/orderApi.ts index b47820e..2a4098a 100644 --- a/admin-web/src/api/orderApi.ts +++ b/admin-web/src/api/orderApi.ts @@ -1,17 +1,21 @@ -import type { ExpressOrder, GoodsOrder } from '../types' +import type { ExpressOrder, GoodsOrder, PageResponse } from '../types' import { apiGet, apiPut } from './http' export interface GoodsOrderQuery { status?: string startDate?: string endDate?: string + pageNo?: number + pageSize?: number } export function buildGoodsOrderQuery(query: GoodsOrderQuery) { - const params: Record = {} + const params: Record = {} if (query.status) params.status = query.status if (query.startDate) params.startDate = query.startDate if (query.endDate) params.endDate = query.endDate + if (query.pageNo) params.pageNo = query.pageNo + if (query.pageSize) params.pageSize = query.pageSize return params } @@ -19,6 +23,10 @@ export function listGoodsOrders(query: GoodsOrderQuery = {}) { return apiGet('/api/admin/goods-orders', buildGoodsOrderQuery(query)) } +export function pageGoodsOrders(query: GoodsOrderQuery = {}) { + return apiGet>('/api/admin/goods-orders/page', buildGoodsOrderQuery(query)) +} + export function confirmGoodsOrder(id: number) { return apiPut(`/api/admin/goods-orders/${id}/confirm`) } @@ -28,17 +36,35 @@ export function markGoodsOrderPrepared(id: number) { } export function cancelGoodsOrder(id: number) { - return apiPut(`/api/mini/goods-orders/${id}/cancel`) + return apiPut(`/api/admin/goods-orders/${id}/cancel`) } export function markGoodsOrderPaid(id: number) { return apiPut(`/api/admin/goods-orders/${id}/paid`) } +export interface ExpressOrderQuery { + status?: string + pageNo?: number + pageSize?: number +} + +export function buildExpressOrderQuery(query: ExpressOrderQuery = {}) { + const params: Record = {} + if (query.status) params.status = query.status + if (query.pageNo) params.pageNo = query.pageNo + if (query.pageSize) params.pageSize = query.pageSize + return params +} + export function listExpressOrders(status?: string) { return apiGet('/api/admin/express-orders', status ? { status } : undefined) } +export function pageExpressOrders(query: ExpressOrderQuery = {}) { + return apiGet>('/api/admin/express-orders/page', buildExpressOrderQuery(query)) +} + export function confirmExpressOrder(id: number) { return apiPut(`/api/admin/express-orders/${id}/confirm`) } diff --git a/admin-web/src/api/productApi.test.ts b/admin-web/src/api/productApi.test.ts new file mode 100644 index 0000000..3211a9a --- /dev/null +++ b/admin-web/src/api/productApi.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest' +import { buildProductQuery } from './productApi' + +describe('buildProductQuery', () => { + it('keeps pagination and optional product filters stable', () => { + expect(buildProductQuery({ + keyword: '苹果', + categoryId: 2, + merchantId: 1, + pageNo: 3, + pageSize: 10 + })).toEqual({ + keyword: '苹果', + categoryId: 2, + merchantId: 1, + pageNo: 3, + pageSize: 10 + }) + }) +}) diff --git a/admin-web/src/api/productApi.ts b/admin-web/src/api/productApi.ts index ef453bf..b4f2044 100644 --- a/admin-web/src/api/productApi.ts +++ b/admin-web/src/api/productApi.ts @@ -1,8 +1,28 @@ -import type { Product, ProductCategory, ProductSku } from '../types' +import type { PageResponse, Product, ProductCategory, ProductSku } from '../types' import { apiGet, apiPost, apiPut } from './http' -export function listProducts() { - return apiGet('/api/admin/products') +export interface ProductQuery { + keyword?: string + categoryId?: number + merchantId?: number + pageNo?: number + pageSize?: number +} + +export function buildProductQuery(query: ProductQuery = {}) { + const params: Record = { + pageNo: query.pageNo || 1, + pageSize: query.pageSize || 20 + } + if (query.keyword) params.keyword = query.keyword + if (query.categoryId) params.categoryId = query.categoryId + if (query.merchantId) params.merchantId = query.merchantId + return params +} + +export async function listProducts(query: ProductQuery = {}) { + const page = await apiGet>('/api/admin/products/page', buildProductQuery(query)) + return page.records } export function createProduct(data: Partial) { diff --git a/admin-web/src/styles.css b/admin-web/src/styles.css index 41a8c8c..fae7628 100644 --- a/admin-web/src/styles.css +++ b/admin-web/src/styles.css @@ -140,3 +140,9 @@ body { display: inline-flex; gap: 8px; } + +.pagination-bar { + display: flex; + justify-content: flex-end; + padding-top: 14px; +} diff --git a/admin-web/src/types.ts b/admin-web/src/types.ts index d494be5..2b00679 100644 --- a/admin-web/src/types.ts +++ b/admin-web/src/types.ts @@ -6,6 +6,13 @@ export interface CurrentUser { roleCode: string } +export interface PageResponse { + total: number + pageNo: number + pageSize: number + records: T[] +} + export interface ProductSku { id: number productId: number diff --git a/admin-web/src/views/ExpressOrderListView.vue b/admin-web/src/views/ExpressOrderListView.vue index f24bd60..27554b6 100644 --- a/admin-web/src/views/ExpressOrderListView.vue +++ b/admin-web/src/views/ExpressOrderListView.vue @@ -6,7 +6,7 @@ import { adjustExpressFee, cancelExpressOrder, confirmExpressOrder, - listExpressOrders, + pageExpressOrders, updateExpressPayStatus } from '../api/orderApi' import type { ExpressOrder } from '../types' @@ -15,11 +15,18 @@ import { statusText, yuan } from '../utils/format' const loading = ref(false) const rows = ref([]) const filters = reactive({ status: '' }) +const pagination = reactive({ pageNo: 1, pageSize: 10, total: 0 }) async function load() { loading.value = true try { - rows.value = await listExpressOrders(filters.status || undefined) + const page = await pageExpressOrders({ + status: filters.status || undefined, + pageNo: pagination.pageNo, + pageSize: pagination.pageSize + }) + rows.value = page.records + pagination.total = page.total } catch (error) { ElMessage.error(error instanceof Error ? error.message : '加载失败') } finally { @@ -27,6 +34,17 @@ async function load() { } } +function search() { + pagination.pageNo = 1 + load() +} + +function changeSize(size: number) { + pagination.pageSize = size + pagination.pageNo = 1 + load() +} + async function run(action: () => Promise) { try { await action() @@ -63,7 +81,7 @@ onMounted(load) - 查询 + 查询 刷新 @@ -88,5 +106,16 @@ onMounted(load) +
+ +
diff --git a/admin-web/src/views/GoodsOrderListView.vue b/admin-web/src/views/GoodsOrderListView.vue index 7d28e8e..6232309 100644 --- a/admin-web/src/views/GoodsOrderListView.vue +++ b/admin-web/src/views/GoodsOrderListView.vue @@ -5,9 +5,9 @@ import { ElMessage } from 'element-plus' import { cancelGoodsOrder, confirmGoodsOrder, - listGoodsOrders, markGoodsOrderPaid, - markGoodsOrderPrepared + markGoodsOrderPrepared, + pageGoodsOrders } from '../api/orderApi' import type { GoodsOrder } from '../types' import { statusText, yuan } from '../utils/format' @@ -15,11 +15,18 @@ import { statusText, yuan } from '../utils/format' const loading = ref(false) const rows = ref([]) const filters = reactive({ status: '' }) +const pagination = reactive({ pageNo: 1, pageSize: 10, total: 0 }) async function load() { loading.value = true try { - rows.value = await listGoodsOrders({ status: filters.status || undefined }) + const page = await pageGoodsOrders({ + status: filters.status || undefined, + pageNo: pagination.pageNo, + pageSize: pagination.pageSize + }) + rows.value = page.records + pagination.total = page.total } catch (error) { ElMessage.error(error instanceof Error ? error.message : '加载失败') } finally { @@ -27,6 +34,17 @@ async function load() { } } +function search() { + pagination.pageNo = 1 + load() +} + +function changeSize(size: number) { + pagination.pageSize = size + pagination.pageNo = 1 + load() +} + async function run(action: () => Promise) { try { await action() @@ -51,7 +69,7 @@ onMounted(load) - 查询 + 查询 刷新 @@ -79,5 +97,16 @@ onMounted(load) +
+ +
diff --git a/admin-web/src/views/GroupBuyListView.vue b/admin-web/src/views/GroupBuyListView.vue index 5294255..637904b 100644 --- a/admin-web/src/views/GroupBuyListView.vue +++ b/admin-web/src/views/GroupBuyListView.vue @@ -7,9 +7,9 @@ import { completeGroupBuy, confirmGroupBuyOrder, createGroupBuy, - listGroupBuyOrders, listGroupBuys, markGroupBuyOrderReady, + pageGroupBuyOrders, startGroupBuy } from '../api/groupBuyApi' import type { GroupBuy, GroupBuyOrder } from '../types' @@ -22,6 +22,7 @@ const orders = ref([]) const activeGroupBuy = ref(null) const dialog = ref(false) const orderDialog = ref(false) +const orderPagination = reactive({ pageNo: 1, pageSize: 10, total: 0 }) const form = reactive({ title: '', coverUrl: '', @@ -46,6 +47,7 @@ async function load() { async function openOrders(row: GroupBuy) { activeGroupBuy.value = row + orderPagination.pageNo = 1 orderDialog.value = true await loadOrders(row.id) } @@ -54,7 +56,12 @@ async function loadOrders(groupBuyId = activeGroupBuy.value?.id) { if (!groupBuyId) return orderLoading.value = true try { - orders.value = await listGroupBuyOrders(groupBuyId) + const page = await pageGroupBuyOrders(groupBuyId, { + pageNo: orderPagination.pageNo, + pageSize: orderPagination.pageSize + }) + orders.value = page.records + orderPagination.total = page.total } catch (error) { ElMessage.error(error instanceof Error ? error.message : '加载失败') } finally { @@ -62,6 +69,12 @@ async function loadOrders(groupBuyId = activeGroupBuy.value?.id) { } } +function changeOrderSize(size: number) { + orderPagination.pageSize = size + orderPagination.pageNo = 1 + loadOrders() +} + async function save() { try { await createGroupBuy(form) @@ -153,6 +166,17 @@ onMounted(load) +
+ +
diff --git a/admin-web/src/views/ProductListView.vue b/admin-web/src/views/ProductListView.vue index 11c7bdf..9a198ed 100644 --- a/admin-web/src/views/ProductListView.vue +++ b/admin-web/src/views/ProductListView.vue @@ -1,6 +1,6 @@