ARTICLE · 1271970
【程序源代码】知识付费小程序
关键字:知识付费,源码资源,微信支付,授权下载,Spring Boot,uni-app
项目名称:知识付费小程序
项目介绍
知识付费源码资源小程序,采用前后端分离架构,主要功能:资源发布;资源文件版本;后台订单与退款;微信小程序浏览与支付;订单支付回调;资源授权;短时签名下载;下载次数与有效期;退款撤权;租户与权限隔离;全链路审计。
项目工程详细设计内容
1 文档目标与范围
本文档是知识付费源码资源小程序的单一实现规格,用于指导研发人员或 AI 按统一边界完成数据库、后端、管理后台和小程序开发。目标业务是:运营人员上传源码压缩包并配置价格与授权规则,用户在小程序内完成微信支付后获得受控下载权限,系统完整记录订单、授权、下载和退款链路。
必须实现:资源发布;资源文件版本;后台订单与退款;微信小程序浏览与支付;订单支付回调;资源授权;短时签名下载;下载次数与有效期;退款撤权;租户与权限隔离;全链路审计。
本期不包含:直播课程、视频点播、分销、评论社区、版权交易撮合、复杂优惠叠加。数据结构应预留扩展,但不得在本期引入无关模块。
范围 | 端 | 必须交付 | 验收口径 |
资源管理 | 管理后台 | 分类、资源、SKU、文件版本、预览、上下架 | 管理员可完成从草稿到上架 |
交易支付 | 小程序 + 后端 | 下单、微信支付、回调、关单、退款 | 重复回调不重复授权 |
授权访问 | 小程序 + 后端 | 购买授权、有效期、下载次数、签名 URL | 未购买与过期用户无法下载 |
运营审计 | 管理后台 | 订单、退款、授权、下载日志、操作日志 | 可按会员/资源/订单追溯 |
AI执行 | 代码仓库 | 模块、DDL、接口、页面与测试 | 按本规格可独立实现并验收 |
2 总体架构与技术约束
后端继续使用 master-jdk17 分支的模块化单体结构,新增 yudao-module-resource-api 与 yudao-module-resource-server。支付复用 yudao-module-pay,文件存储复用 yudao-module-infra,小程序登录与消息复用 yudao-module-mp,会员身份复用 yudao-module-member。资源模块不得跨模块直接访问其他模块的 Mapper。
层次 | 技术 | 职责 | 关键约束 |
接入层 | Nginx | HTTPS、静态资源、API 反向代理 | 后台与 App API 分域名 |
应用层 | Spring Boot 3 / JDK 17+ | 管理 API、App API、任务、事件监听 | 统一响应与异常码 |
领域层 | Module | 资源、订单、授权、下载、退款 | 资源模块独立,不复制 pay 逻辑 |
数据层 | MySQL 8 / MyBatis-Plus | 资源、订单、授权与日志持久化 | 金额使用分,时间区一 |
缓存层 | Redis / Redisson | 短时限流、下载锁、幂等辅助 | 数据库唯一键仍为最终保障 |
文件层 | MinIO / S3 / OSS | 源码包与预览文件 | 禁止公开永久 URL |
后台 | Vue 3 + Element Plus | 运营、审核、订单、授权管理 | RBAC 与数据权限 |
小程序 | uni-app + 微信小程序 | 浏览、支付、下载、售后 | 登录态与支付状态可恢复 |
2.1 代码模块与目录
yudao-module-resource-api/└─ src/main/java/cn/iocoder/yudao/module/resource/├─ api/对外 API 与 DTO├─ enums/订单、授权、审核枚举└─ errors/资源业务错误码yudao-module-resource-server/└─ src/main/java/cn/iocoder/yudao/module/resource/├─ controller/admin/管理后台接口├─ controller/app/小程序接口├─ convert/对象转换├─ dal/dataobject/DO├─ dal/mysql/Mapper├─ service/领域服务├─ job/过期与对账任务├─ mq/支付事件消费者└─ framework/配置、拦截器、错误码 |
新增模块必须在 yudao-server 的 Maven 依赖中装配。resource-api 不得依赖 resource-server;resource-server 可依赖 system-api、infra-api、member-api、pay-api。后台页面放在 yudao-ui-admin-vue3 的 src/views/resource,小程序页面放在 yudao-mall-uniapp 的 pages/resource。
3 角色、页面与权限边界
角色 | 可用端 | 核心操作 | 权限标识 |
平台管理员 | 管理后台 | 全部资源、订单、授权、配置 | resource:* |
内容运营 | 管理后台 | 创建资源、上传文件、提交审核 | resource:info:create/update |
内容审核员 | 管理后台 | 审核、通过、驳回、下架 | resource:info:audit |
财务/客服 | 管理后台 | 订单查询、退款审批、授权查询 | resource:order:query/refund |
注册会员 | 微信小程序 | 浏览、购买、下载、申请退款 | member token |
访客 | 微信小程序 | 浏览公开资源与试读 | 匿名只读 |
对象级权限必须由后端校验:会员只能查看自己的订单和授权;运营只能操作所属租户资源;客服退款前必须校验订单归属;资源下架后不允许新增下载,但已产生授权仍可由管理员决定是否保留。不得仅依赖按钮隐藏。
4 UI设计原则与管理后台
4.1 参考主流知识付费产品的设计原则
UI 应参考小鹅通、得到、知识星球等主流知识付费产品的信息架构,但不得复制其品牌、图标、插画或页面素材。参考重点是“内容店铺化、详情页建立信任、支付路径短、已购资源易找到”,而不是照搬某个产品的配色。
参考产品 | 可借鉴的结构 | 源码资源场景的落地 |
小鹅通类 | 品牌首页、内容卡片、详情转化、支付、已购中心 | 首页呈现源码店铺;详情页强化目录、文件清单与购买权益 |
得到类 | 内容目录、试读、价值说明、学习入口 | 提供源码目录、运行截图、技术栈和示例试读 |
知识星球类 | 会员权益、内容沉淀、持续更新 | 展示版本更新、作者支持、购买协议和售后边界 |
• 内容优先:首页先表达“这个店铺提供什么源码”,再展示价格与活动。
• 信任优先:详情页必须展示文件清单、技术栈、运行环境、更新时间和版权说明。
• 转化优先:详情页仅保留一个主行动按钮,支付页不出现无关导航与促销干扰。
• 复访优先:支付完成后主动引导到“我的资源”,形成可重复使用的资源中心。
4.2 管理后台页面与布局

图 1 管理后台资源管理页 UI 线框图
后台保持现有布局:顶部栏、左侧菜单、白色内容区。资源列表使用 Element Plus Table,筛选区与表格之间 16px,表格行高不小于 48px。右侧资源编辑采用抽屉或独立页,避免长表单挤压列表。所有危险操作必须二次确认。
页面 | 路由建议 | 主要组件 | 关键交互 |
资源列表 | /resource/info | 筛选栏、表格、状态标签、分页 | 新建、编辑、审核、上下架、复制 |
资源编辑 | /resource/info/edit/:id | 步骤条、表单、上传、预览 | 基础信息→文件→SKU→发布 |
分类标签 | /resource/category | 树表、标签管理 | 拖拽排序、启停 |
订单列表 | /resource/order | 筛选、详情抽屉 | 查看支付、退款、下载 |
退款处理 | /resource/refund | 待办表、审批弹窗 | 同意退款后撤销授权 |
授权管理 | /resource/grant | 会员/资源筛选、状态标签 | 查看到期、剩余次数、人工撤权 |
下载日志 | /resource/download-log | 日志表、IP/设备详情 | 按失败码筛选 |
统计看板 | /resource/dashboard | 指标卡、趋势图、榜单 | 按日期、租户筛选 |
资源编辑步骤:基础信息(标题/分类/封面/简介/详情);文件版本(上传、校验、预览);价格授权(SKU、授权天数、下载次数、限购);提交审核与发布。任一步保存草稿,提交审核后锁定文件版本。
后台视觉规范:页面背景 #F5F7FA,卡片 #FFFFFF,主色 #2F6FED,成功 #1F8B4C,警告 #E6A23C,危险 #E85D3F,正文 #1F2937,次要文字 #6C7686,边框 #DDE3EC。
5 UI设计:微信小程序
5.1 参考小鹅通类产品的小程序信息架构
小程序采用“知识店铺 + 内容详情 + 已购资源”的结构。首页以店铺品牌、推荐资源和分类筛选建立内容感;详情页承担价值说明、试读和支付转化;已购资源页承担复访和下载。不要做成普通商城的三列商品瀑布流,也不要堆叠秒杀倒计时等与源码购买无关的元素。
区域 | 参考信息架构 | 本产品要求 |
首页 | 品牌头图、分类、精选内容、内容卡片 | 技术栈标签、资源类型、价格、已购标记 |
详情 | 标题、卖点、目录、试读、老师/作者、评价 | 文件清单、运行环境、版本日志、版权与售后 |
支付 | 订单摘要、优惠、支付方式 | 资源快照、授权期限、下载次数、购买协议 |
已购中心 | 已购课程、学习进度、继续学习 | 有效/过期、剩余下载次数、重新获取链接 |
个人中心 | 订单、客服、协议、帮助 | 会员信息、购买协议、发票/退款入口 |
• 首页卡片统一 16:9 封面,标题最多两行,技术标签最多三个。
• 详情页首屏必须同时出现资源名称、核心价值、价格和购买按钮。
• 已购用户进入详情页时,主按钮直接切换为“获取下载”,不重复展示支付。
• 页面不使用“课程”字样误导用户,统一使用“资源、源码包、文件清单、授权”。
5.2 页面流与交互规范

图 2 微信小程序页面流 UI 线框图
小程序采用 uni-app 页面结构,底部 TabBar 为“首页、分类、我的资源、我的”。资源详情页采用固定底部购买栏,适配 iPhone 安全区;登录后若已有有效授权,底部按钮切换为“获取下载”。支付按钮避免与返回手势冲突。
页面 | 关键内容 | 状态与交互 |
首页 | 搜索、分类、推荐资源、价格 | 下拉刷新、上拉分页、骨架屏 |
分类 | 一级/二级分类、技术标签 | 筛选后回到稳定滚动位置 |
资源详情 | 封面、标题、价格、文件清单、试读 | 已购显示获取下载;未购显示购买 |
确认订单 | 资源快照、授权天数、下载次数、协议 | 金额只读,后端计算 |
支付结果 | 支付成功/处理中/失败 | 轮询订单,禁止仅凭页面参数发资源 |
我的资源 | 有效/过期/全部 | 每次进入实时刷新授权 |
下载页 | 文件名、大小、剩余次数、错误原因 | 短时 URL;失败可重试 |
订单列表 | 待支付、已支付、已退款 | 支持继续支付和申请退款 |
个人中心 | 登录、客服、购买协议、帮助 | 手机号授权可选 |
小程序视觉规范:页面背景 #F4F6FA,主按钮 #2F6FED,购买金额 #E85D3F,正文 #1F2937,辅助文字 #6C7686。正文 14px,卡片标题 16px,页面标题 20px,主按钮高度 44px;所有可点击区域不小于 44×44px。
必须具备的空态/异常态:加载中、网络失败、无资源、无授权、授权过期、下载次数用尽、文件不存在、支付处理中、支付失败、退款处理中。错误提示必须说明下一步动作,不能只显示“系统繁忙”。
6 数据库详细设计
所有业务表必须包含 tenant_id、creator、create_time、updater、update_time、deleted。金额统一使用 BIGINT 分,禁止浮点数;时间使用 DATETIME,应用时区固定 Asia/Shanghai;主键使用 BIGINT。表前缀统一 resource_,新增表全部使用 utf8mb4。
表名 | 职责 | 关键唯一键/索引 |
resource_category | 资源分类 | tenant_id + parent_id |
resource_info | 资源主体与详情 | tenant_id + status + category_id |
resource_sku | 价格与授权规格 | resource_id + status |
resource_file | 源码文件版本 | resource_id + version_no 唯一 |
resource_preview | 试读/目录/截图 | resource_id + sort |
resource_order | 资源订单 | order_no 唯一、pay_order_id 唯一 |
resource_order_item | 订单资源快照 | order_id + sku_id 唯一 |
resource_grant | 购买后的访问权 | order_item_id 唯一 |
resource_download_log | 下载结果审计 | member_id + create_time |
resource_audit_log | 后台审核与操作记录 | resource_id + create_time |
6.1 资源与文件表
CREATE TABLE resource_category (id BIGINT NOT NULL AUTO_INCREMENT,tenant_id BIGINT NOT NULL DEFAULT 0,parent_id BIGINT NOT NULL DEFAULT 0,name VARCHAR(64) NOT NULL,sort INT NOT NULL DEFAULT 0,status TINYINT NOT NULL DEFAULT 0,deleted BIT NOT NULL DEFAULT b'0',creator VARCHAR(64) DEFAULT '',create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,updater VARCHAR(64) DEFAULT '',update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMPON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id),KEY idx_tenant_parent (tenant_id, parent_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE resource_info (id BIGINT NOT NULL AUTO_INCREMENT,tenant_id BIGINT NOT NULL DEFAULT 0,category_id BIGINT NOT NULL,title VARCHAR(120) NOT NULL,subtitle VARCHAR(255) DEFAULT '',cover_url VARCHAR(512) DEFAULT '',summary VARCHAR(500) DEFAULT '',detail_html MEDIUMTEXT,resource_type VARCHAR(32) NOT NULL DEFAULT 'SOURCE_CODE',status TINYINT NOT NULL DEFAULT 0,audit_status TINYINT NOT NULL DEFAULT 0,current_version INT NOT NULL DEFAULT 1,view_count BIGINT NOT NULL DEFAULT 0,sales_count BIGINT NOT NULL DEFAULT 0,sort INT NOT NULL DEFAULT 0,deleted BIT NOT NULL DEFAULT b'0',creator VARCHAR(64) DEFAULT '',create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,updater VARCHAR(64) DEFAULT '',update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMPON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id),KEY idx_tenant_status (tenant_id, status, audit_status),KEY idx_category (category_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
CREATE TABLE resource_sku (id BIGINT NOT NULL AUTO_INCREMENT,tenant_id BIGINT NOT NULL DEFAULT 0,resource_id BIGINT NOT NULL,sku_name VARCHAR(80) NOT NULL,price_fen BIGINT NOT NULL,original_price_fen BIGINT NOT NULL DEFAULT 0,access_days INT NOT NULL DEFAULT 0,download_limit INT NOT NULL DEFAULT 0,status TINYINT NOT NULL DEFAULT 0,sort INT NOT NULL DEFAULT 0,deleted BIT NOT NULL DEFAULT b'0',creator VARCHAR(64) DEFAULT '',create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,updater VARCHAR(64) DEFAULT '',update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMPON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id),KEY idx_resource_status (resource_id, status)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE resource_file (id BIGINT NOT NULL AUTO_INCREMENT,tenant_id BIGINT NOT NULL DEFAULT 0,resource_id BIGINT NOT NULL,version_no INT NOT NULL,storage_provider VARCHAR(32) NOT NULL,bucket_name VARCHAR(128) NOT NULL,object_key VARCHAR(512) NOT NULL,original_name VARCHAR(255) NOT NULL,mime_type VARCHAR(128) DEFAULT '',file_size BIGINT NOT NULL DEFAULT 0,sha256 CHAR(64) NOT NULL,scan_status TINYINT NOT NULL DEFAULT 0,encrypt_flag BIT NOT NULL DEFAULT b'0',status TINYINT NOT NULL DEFAULT 0,deleted BIT NOT NULL DEFAULT b'0',creator VARCHAR(64) DEFAULT '',create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,updater VARCHAR(64) DEFAULT '',update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMPON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id),UNIQUE KEY uk_resource_version (resource_id, version_no),KEY idx_sha256 (sha256)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
access_days=0 表示永久授权,download_limit=0 表示不限次数。每次替换源码包必须新增 version_no,不能覆盖原记录。文件状态只有“已上传、校验中、可用、隔离”四种;scan_status 未通过的文件不得发布。
6.2 订单、授权与日志表
CREATE TABLE resource_order (id BIGINT NOT NULL AUTO_INCREMENT,tenant_id BIGINT NOT NULL DEFAULT 0,order_no VARCHAR(32) NOT NULL,member_id BIGINT NOT NULL,total_amount_fen BIGINT NOT NULL,discount_amount_fen BIGINT NOT NULL DEFAULT 0,pay_amount_fen BIGINT NOT NULL,status TINYINT NOT NULL DEFAULT 0,pay_order_id BIGINT DEFAULT NULL,pay_channel VARCHAR(32) DEFAULT '',paid_time DATETIME DEFAULT NULL,expire_time DATETIME DEFAULT NULL,close_time DATETIME DEFAULT NULL,remark VARCHAR(255) DEFAULT '',deleted BIT NOT NULL DEFAULT b'0',creator VARCHAR(64) DEFAULT '',create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,updater VARCHAR(64) DEFAULT '',update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMPON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id),UNIQUE KEY uk_order_no (order_no),UNIQUE KEY uk_pay_order_id (pay_order_id),KEY idx_member_status (member_id, status, create_time)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE resource_order_item (id BIGINT NOT NULL AUTO_INCREMENT,tenant_id BIGINT NOT NULL DEFAULT 0,order_id BIGINT NOT NULL,resource_id BIGINT NOT NULL,sku_id BIGINT NOT NULL,resource_title VARCHAR(120) NOT NULL,sku_name VARCHAR(80) NOT NULL,price_fen BIGINT NOT NULL,discount_fen BIGINT NOT NULL DEFAULT 0,pay_fen BIGINT NOT NULL,access_days INT NOT NULL DEFAULT 0,download_limit INT NOT NULL DEFAULT 0,grant_id BIGINT DEFAULT NULL,deleted BIT NOT NULL DEFAULT b'0',creator VARCHAR(64) DEFAULT '',create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,updater VARCHAR(64) DEFAULT '',update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMPON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id),UNIQUE KEY uk_order_sku (order_id, sku_id),KEY idx_resource (resource_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
CREATE TABLE resource_grant (id BIGINT NOT NULL AUTO_INCREMENT,tenant_id BIGINT NOT NULL DEFAULT 0,member_id BIGINT NOT NULL,resource_id BIGINT NOT NULL,sku_id BIGINT NOT NULL,order_id BIGINT NOT NULL,order_item_id BIGINT NOT NULL,status TINYINT NOT NULL DEFAULT 0,effective_time DATETIME NOT NULL,expire_time DATETIME DEFAULT NULL,download_limit INT NOT NULL DEFAULT 0,download_count INT NOT NULL DEFAULT 0,revoke_time DATETIME DEFAULT NULL,revoke_reason VARCHAR(255) DEFAULT '',deleted BIT NOT NULL DEFAULT b'0',creator VARCHAR(64) DEFAULT '',create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,updater VARCHAR(64) DEFAULT '',update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMPON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id),UNIQUE KEY uk_order_item (order_item_id),KEY idx_member_resource (member_id, resource_id, status),KEY idx_expire (status, expire_time)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE resource_download_log (id BIGINT NOT NULL AUTO_INCREMENT,tenant_id BIGINT NOT NULL DEFAULT 0,grant_id BIGINT NOT NULL,order_id BIGINT NOT NULL,member_id BIGINT NOT NULL,resource_id BIGINT NOT NULL,file_id BIGINT NOT NULL,version_no INT NOT NULL,result_code VARCHAR(32) NOT NULL,request_ip VARCHAR(64) DEFAULT '',user_agent VARCHAR(512) DEFAULT '',file_size BIGINT NOT NULL DEFAULT 0,create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id),KEY idx_member_time (member_id, create_time),KEY idx_resource_time (resource_id, create_time)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
resource_grant.order_item_id 的唯一索引是支付回调幂等的最终保障;resource_order.order_no 与 pay_order_id 分别防止重复建单和重复绑定支付单。不要只依赖 Redis 锁,数据库唯一键必须保留。
7 状态机与核心业务逻辑
7.1 订单状态
WAIT_PAY --支付成功--> PAID --授权完成--> COMPLETED||+--超时/用户取消--> CLOSED|REFUNDING --退款成功--> REFUNDED状态值:0 WAIT_PAY10 PAID20 COMPLETED30 CLOSED40 REFUNDING50 REFUNDED |
• WAIT_PAY:允许继续支付和取消,不允许下载。
• PAID:支付已确认,授权写入中;收到重复回调直接返回成功。
• COMPLETED:授权已建立,允许下载;退款任务可继续进入 REFUNDING。
• CLOSED:只能由超时任务或用户取消产生,不能再支付。
• REFUNDING / REFUNDED:停止新增下载,退款完成后撤销授权。
7.2 资源与授权状态
领域 | 状态 | 允许动作 |
资源 | DRAFT | 编辑、上传文件、提交审核、删除 |
资源 | PENDING_AUDIT | 审核员通过/驳回,其他人只读 |
资源 | ONLINE | 小程序展示、购买;可下架 |
资源 | REJECTED | 运营修改后重新提交 |
资源 | OFFLINE | 停止新增购买;已有授权按规则下载 |
授权 | ACTIVE | 在有效期与次数内下载 |
授权 | EXPIRED | 拒绝下载,提示续费或重购 |
授权 | REVOKED | 退款或人工撤销后拒绝下载 |
授权 | EXHAUSTED | 下载次数用尽,拒绝下载 |
7.3 资源发布流程
运营创建 DRAFT → 上传文件并完成 SHA256/病毒扫描 → 配置 SKU 与预览 → 提交审核 → 审核员校验版权来源和内容 → ONLINE。审核驳回必须填写原因;上架后修改文件必须创建新版本,不能直接覆盖在线版本。
• 上架前置条件:至少一个有效 SKU、一个可用文件、封面与详情完整。
• 版权前置条件:来源说明、授权凭证或原创声明至少一项。
• 文件前置条件:扫描通过、大小与哈希已写入 resource_file。
7.4 创建订单与支付
下单接口只接收 skuId 与客户端幂等键,不接受前端价格。后端校验 SKU 在线、资源在线、会员未持有相同有效授权,重新读取 price_fen 生成订单快照,再调用 pay-api 创建支付单。返回小程序调起支付所需参数,不能返回后台密钥。
createOrder(memberId, skuId, requestId):key = "resource_order:" + memberId + ":" + skuId + ":" + requestIdexists = orderCache.get(key)if exists: return existssku = selectSku(skuId)resource = selectOnlineResource(sku.resourceId)if hasActiveGrant(memberId, resource.id): throw ALREADY_OWNEDorder = insertOrder(snapshot(sku, resource))payOrder = payApi.createPayOrder(orderNo, sku.priceFen)bindPayOrder(order.id, payOrder.id)cache.put(key, order, 30 minutes)return paymentParams(payOrder) |
支付回调必须验签、核对商户号/AppID/金额,并以 pay_order_id 查询本地订单。状态已是 PAID 或 COMPLETED 时直接返回成功;金额不一致、订单不存在或订单已关闭时记录异常并告警,不得发放授权。
7.5 支付成功后的授权事务
@Transactional(rollbackFor = Exception.class)public void handlePaySuccess(Long payOrderId, Long paidFen) {ResourceOrderDO order = orderMapper.selectForUpdateByPayOrderId(payOrderId);if (order == null) throw new ResourceOrderNotFound();if (order.getStatus() >= COMPLETED) return;if (!order.getPayAmountFen().equals(paidFen)) throw new AmountMismatch();orderMapper.updateStatus(order.getId(), PAID);for (ResourceOrderItemDO item : itemMapper.selectByOrderId(order.getId())) {ResourceGrantDO grant = createGrant(order, item);grantMapper.insertIgnoreDuplicate(grant);itemMapper.bindGrant(item.getId(), grant.getId());}orderMapper.updateStatus(order.getId(), COMPLETED);publisher.publishAfterCommit(new ResourcePurchasedEvent(order.getId()));} |
事务内不调用微信、短信或对象存储等外部服务。授权写入必须有唯一键,外部通知与统计事件放到事务提交后执行。支付通知重复到达时,以订单状态和唯一索引共同保证幂等。
7.6 下载鉴权逻辑
下载接口必须先查询授权,再原子占用一次下载额度,最后生成短时签名 URL。占用额度使用条件更新,不能先查 count 再写回,否则并发请求会突破次数限制。签名 URL 有效期 5 分钟,过期后重新申请。
int affected = grantMapper.consumeDownload(grantId,memberId,now);if (affected == 0) {throw resolveGrantError(grantId);}FileDO file = requireAvailableFile(resourceId);String url = storage.createSignedUrl(file.objectKey, 300);downloadLogMapper.insert(successLog(...));return new DownloadUrl(url, file.originalName, file.fileSize); |
条件更新的 SQL 必须同时校验 status=ACTIVE、expire_time 为空或大于当前时间、download_limit=0 或 download_count <download_limit。失败时返回明确错误码:NO_GRANT、EXPIRED、LIMIT_EXCEEDED、FILE_UNAVAILABLE。< span>
7.7 退款与撤权
用户申请退款后,订单进入 REFUNDING,授权立即冻结,不再允许新增下载。退款成功回调后,订单置为 REFUNDED,授权置为 REVOKED;退款失败则恢复原授权。已经签发的下载 URL 无法主动撤回,因此有效期必须足够短并在退款后依赖对象级签名过期。
操作 | 订单变化 | 授权变化 | 通知 |
用户申请退款 | COMPLETED→REFUNDING | ACTIVE→SUSPENDED | 通知客服 |
客服拒绝 | REFUNDING→COMPLETED | SUSPENDED→ACTIVE | 通知会员 |
退款成功 | REFUNDING→REFUNDED | SUSPENDED→REVOKED | 通知会员 |
退款失败 | REFUNDING→COMPLETED | SUSPENDED→ACTIVE | 告警财务 |
7.8 幂等、并发与事务边界
场景 | 幂等键 | 最终保障 | 失败处理 |
创建订单 | memberId+skuId+requestId | requestId 唯一记录/缓存 | 返回同一订单 |
支付回调 | payOrderId+第三方流水号 | pay_order_id 唯一 | 重复直接成功 |
授权发放 | orderItemId | resource_grant 唯一键 | 忽略重复插入 |
下载占额 | grantId+条件更新 | SQL 原子更新 | 返回次数不足 |
退款回调 | refundId | 退款记录唯一键 | 重复直接成功 |
上传文件、对象存储签名、短信、微信模板消息均不得放在数据库事务中。数据库事务只承载订单状态、授权状态和日志状态;外部副作用使用事务后事件或可靠消息处理。
8 接口设计
8.1 管理后台 API
方法 | 路径 | 用途 | 权限 |
POST | /admin-api/resource/category/create | 创建分类 | resource:category:create |
PUT | /admin-api/resource/category/update | 修改分类 | resource:category:update |
POST | /admin-api/resource/info/create | 创建资源草稿 | resource:info:create |
PUT | /admin-api/resource/info/update | 修改资源 | resource:info:update |
POST | /admin-api/resource/info/submit-audit | 提交审核 | resource:info:update |
POST | /admin-api/resource/info/audit | 审核通过/驳回 | resource:info:audit |
POST | /admin-api/resource/info/publish | 上架 | resource:info:publish |
POST | /admin-api/resource/file/presign-upload | 获取上传凭证 | resource:file:upload |
POST | /admin-api/resource/file/complete-upload | 确认文件哈希 | resource:file:upload |
GET | /admin-api/resource/order/page | 订单分页 | resource:order:query |
POST | /admin-api/resource/order/refund/approve | 退款审批 | resource:order:refund |
GET | /admin-api/resource/grant/page | 授权分页 | resource:grant:query |
POST | /admin-api/resource/grant/revoke | 人工撤权 | resource:grant:revoke |
GET | /admin-api/resource/download-log/page | 下载日志 | resource:download:query |
8.2 小程序 API
方法 | 路径 | 用途 | 鉴权 |
GET | /app-api/resource/info/page | 资源列表 | 可匿名 |
GET | /app-api/resource/info/get | 资源详情与试读 | 可匿名 |
GET | /app-api/resource/category/list | 分类树 | 可匿名 |
POST | /app-api/resource/order/create | 创建订单 | 会员 |
GET | /app-api/resource/order/get | 查询订单 | 会员本人 |
POST | /app-api/resource/order/cancel | 取消未支付订单 | 会员本人 |
POST | /app-api/resource/order/refund/apply | 申请退款 | 会员本人 |
GET | /app-api/resource/grant/page | 我的资源 | 会员 |
POST | /app-api/resource/grant/download | 获取签名下载地址 | 会员 |
GET | /app-api/resource/download-log/page | 我的下载记录 | 会员 |
8.3 统一响应与错误码
{"code": 0,"data": {"url": "https://storage.example.com/...","fileName": "project.zip","expireSeconds": 300},"msg": ""}RESOURCE_NOT_ONLINE资源已下架RESOURCE_ALREADY_OWNED已拥有有效授权RESOURCE_PRICE_CHANGED价格或SKU已变化,请刷新RESOURCE_ORDER_CLOSED订单已关闭RESOURCE_GRANT_NOT_FOUND无下载授权RESOURCE_GRANT_EXPIRED授权已过期RESOURCE_DOWNLOAD_LIMIT下载次数用尽RESOURCE_FILE_UNAVAILABLE 文件暂不可用 |
9 文件存储、安全与可观测性
9.1 文件对象规范
对象键格式固定为 resource/{tenantId}/{resourceId}/v{version}/{sha256}.zip。源码压缩包默认私有读,禁止 CDN 公开缓存。上传使用预签名或多段上传;完成后后端复核大小、MIME、SHA256 与病毒扫描状态。下载时只对单个 object key 生成签名,不允许接收任意 objectKey 参数。
防泄漏措施 | 实现要求 |
短时签名 | 有效期 5 分钟,绑定对象 key 与 HTTP 方法 |
身份绑定 | 下载前使用会员 token 校验 grant.member_id |
次数控制 | 数据库条件更新,禁止先查后写 |
目录隔离 | 对象桶禁止 List 权限,只允许 GetObject |
动态水印 | 高价值资源可选 PDF 水印/用户标识 |
频率限制 | 同一会员每分钟最多 10 次申请 |
审计留痕 | 记录成功/失败、IP、UA、文件版本 |
9.2 支付安全
• 必须验签并核对商户号、AppID、订单号、金额与币种。
• 支付密钥、APIv3 私钥与商户证书只能放密钥配置/环境变量。
• 回调原始报文与处理结果至少保留 180 天,便于对账。
• 每日执行支付单与资源订单对账,异常自动告警。
10 AI执行规格
后续交给 AI 执行时,应以本规格为唯一业务来源,以现有代码规范为技术来源。AI 不得自行增加直播、分销、会员等级折扣等未定义功能;遇到未明确事项时,优先保持最小闭环并在执行记录中列出假设。
10.1 交付目录
backend/├─ yudao-module-resource-api/├─ yudao-module-resource-server/├─ yudao-server/pom.xml增加模块依赖└─ sql/mysql/resource.sql新增DDL与菜单SQLadmin/├─ src/api/resource/接口封装├─ src/views/resource/info/资源管理├─ src/views/resource/order/订单退款├─ src/views/resource/grant/授权日志└─ src/views/resource/dashboard/运营看板miniapp/├─ pages/resource/list.vue├─ pages/resource/detail.vue├─ pages/resource/checkout.vue├─ pages/resource/result.vue├─ pages/resource/mine.vue└─ pages/resource/order-list.vue |
10.2 实施阶段
阶段 | 任务 | 完成标志 |
P1 | 创建模块、枚举、错误码、DDL | 应用可编译,表可初始化 |
P2 | 资源分类、资源、SKU、文件版本 CRUD | 后台可创建草稿并上传文件 |
P3 | 审核、上架、预览与资源详情 | 小程序可浏览在线资源 |
P4 | 订单创建、支付单绑定、关单 | 可完成下单并拉起支付 |
P5 | 支付回调、授权发放、我的资源 | 支付后出现有效授权 |
P6 | 签名下载、下载次数、日志 | 并发情况不超次数 |
P7 | 退款、撤权、任务与报表 | 退款后无法继续下载 |
P8 | UI完善、权限、测试、渲染验收 | 全部验收用例通过 |
10.3 验收用例
编号 | 场景 | 预期结果 |
T01 | 未支付用户调用下载 | 返回 NO_GRANT,不生成 URL |
T02 | 支付回调重复 5 次 | 仅一条授权,订单最终 COMPLETED |
T03 | 两个请求并发下载且仅剩 1 次 | 仅一个请求成功,另一个 LIMIT_EXCEEDED |
T04 | 授权过期后下载 | 返回 EXPIRED,不生成 URL |
T05 | 退款成功后再次下载 | 返回 REVOKED,不生成 URL |
T06 | 资源下架后已购用户下载 | 按产品开关允许/拒绝,并记录日志 |
T07 | 管理员跨租户查询订单 | 无权限,返回 403 |
T08 | 对象 key 被篡改 | 签名失败,不能下载其他资源 |
10.4 AI禁止事项
• 不得把资源文件 URL 直接写进数据库并在接口中原样返回。
• 不得在支付回调中调用外部通知;外部副作用必须在事务提交后执行。
• 不得跨模块访问 pay、member、infra 的 Mapper,只能使用 API。
• 不得把订单金额、授权天数或下载次数改成前端可覆盖字段。
• 不得为了赶进度关闭租户隔离、权限或审计日志。
11 部署与上线检查
项目 | 要求 |
Java | JDK 17 或 21 |
数据库 | MySQL 8,执行 resource.sql |
缓存 | Redis,生产建议哨兵/集群 |
对象存储 | 私有桶,禁止 List 与公开读 |
小程序 | HTTPS、request/download/upload 域名备案 |
微信支付 | 商户号、AppID、证书、回调域名完成联调 |
监控 | 支付失败、授权失败、文件签名失败、下载失败告警 |
备份 | 订单/授权表每日备份,文件版本不可覆盖 |
上线前必须完成:生产配置检查;支付 1 分钱真机测试;退款测试;签名 URL 越权测试;跨租户访问测试;资源下架测试;数据库唯一键与索引检查;日志脱敏检查;小程序隐私协议与付费协议检查。
12 结论与交付标准
本设计将知识付费拆成“资源发布—订单支付—授权生成—安全下载—退款撤权—审计运营”六个闭环。只要严格按本文档实现,AI 可以在不改动支付核心的前提下完成一个可运营的源码资源收费小程序。交付时应同时提交数据库脚本、接口文档、后台页面、小程序页面、自动化测试结果和上线检查清单。
这个源码适合的学习人群
该项目代码结构清晰、注释规范、无过度封装、轻量化易上手,极其适合四类人群学习:Java后端初学者、微信小程序开发学习者、计算机专业在校学生、个人开源项目爱好者。
1.UniApp 小程序开发者,学习线下小程序开发;
2. 计算机专业学生,课程设计、毕业设计;
3. 二次开发人员,基于项目拓展类小程序。
这个项目源码获取方式
关注公众号,回复:知识付费小程序合作















































































