ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

金融类小程序三层架构设计与合规实现

金融类小程序三层架构设计与合规实现 简介本资源是一套完整的微信小程序毕业设计项目源码面向计算机专业本科生及Java后端开发者聚焦金融类小程序开发全流程实践。项目名为“茉莉炒股”涵盖小程序前端、Spring Boot服务端及Python爬虫端三大模块完整呈现行情数据采集、后端业务处理与小程序交互的闭环逻辑适用于课程设计、大作业或求职项目复现。压缩包共248个文件含130个Java核心业务类、27个XML配置与Mapper文件、13个Python爬虫脚本基于Scrapy框架、14个WXSS样式与9个WXML页面结构文件整体仅201KB轻量易部署。已有190人学习下载资源结构清晰包含SpiderExchangeMessageHandler等典型消息处理器及UserServerController等关键服务层代码便于理解多端协同架构与金融数据抓取规范。1. “茉莉炒股”不是开源项目但它的三层架构拆解能帮你快速复刻同类金融类小程序“茉莉炒股”在公开渠道并无官方 GitHub 仓库、未上架微信小程序商店、也无备案主体信息可查。网络检索中出现的“.zip”源码包多为学习者基于公开接口模拟的练手工程不包含真实券商交易通道、无合法行情授权、不具备实盘交易能力。但它意外成为理解「轻量级金融工具类小程序」典型分层结构的优质样本前端展示小程序端负责交互与可视化后端服务server 端做数据聚合与权限控制爬虫端则承担非官方行情/公告的补充采集任务。这类结构常见于雪球、同花顺 Lite、东方财富股吧等第三方工具的 MVP 版本中。如果你正要开发一款面向个人投资者的行情提醒、研报摘要或自选股跟踪小程序且受限于合规接口获取难度“茉莉炒股”的三层分工逻辑——尤其是如何用最小成本绕过付费行情 SDK、如何让爬虫结果安全透传至小程序、如何避免被微信静态审核拦截——比代码本身更有复用价值。本文不提供任何 ZIP 包下载链接只还原一个合规、可上线、经得起微信基础库 2.30 版本校验的完整技术路径。2. 小程序端用 uni-app 实现跨端兼容重点解决加载页定制与微信原生能力调用2.1 为什么选 uni-app 而非原生小程序框架微信原生开发虽性能最优但“茉莉炒股”类工具需快速适配 App 端如后续打包成 iOS/Android 客户端、H5 端分享到浏览器查看uni-app 的vue语法 条件编译能统一 80% 以上业务逻辑。关键证据是热词中高频出现的uniapp微信小程序和hbuilderx开发微信小程序——HBuilderX 对 uni-app 的深度集成如一键生成sitemap.json、自动注入wx.login兼容层大幅降低多端发布门槛。若强行用原生写法后续扩展 App 端时需重写 60% 以上 UI 层而 uni-app 仅需调整#ifdef APP-PLUS块内代码。2.2 修改刚进入的加载页面从白屏到品牌化首屏的三步落地微信小程序默认启动白屏iOS 更明显用户等待超 1.2 秒即产生流失。修改刚进入的加载页面是热词中实操性最强的需求uni-app 下需同步操作三处2.2.1 配置pages.json的窗口层级{ path: pages/index/index, style: { navigationBarTitleText: 茉莉炒股, navigationStyle: custom, // 关键隐藏默认导航栏自定义顶部 backgroundColor: #f8f9fa, loadingImage: /static/logo-loading.png, // 启动图尺寸 750×1334PNG 透明底 enablePullDownRefresh: true, onReachBottomDistance: 50 } }注意loadingImage仅在冷启动生效热启动从后台切回由App.vue的onLaunch控制图片必须放在static目录assets下资源无法被微信启动器读取。2.2.2 在App.vue中接管启动流程// App.vue export default { onLaunch() { // 1. 检查本地缓存是否有有效 token const token uni.getStorageSync(user_token) if (token) { this.$store.commit(SET_TOKEN, token) uni.switchTab({ url: /pages/home/home }) // 直接跳首页跳过 splash } else { uni.navigateTo({ url: /pages/auth/login }) // 引导登录 } }, onShow() { // 热启动时检查网络状态避免白屏卡顿 uni.getNetworkType({ success: res { if (res.networkType none) { uni.showToast({ title: 网络不可用, icon: none }) } } }) } }提示onLaunch中禁止调用uni.request微信限制冷启动阶段网络请求所有初始化数据应在首页onLoad中触发用uni.showLoading显示自定义加载动画。2.2.3 首页home.vue的骨架屏实现template view classcontainer !-- 骨架屏灰色块模拟 K 线图、列表、指标 -- view v-if!loaded classskeleton view classskeleton-header/view view classskeleton-chart/view view classskeleton-list view classskeleton-item v-fori in 5 :keyi/view /view /view !-- 真实内容 -- view v-else stock-chart :datachartData / stock-list :stocksstockList / /view /view /template script export default { data() { return { loaded: false, chartData: [], stockList: [] } }, onLoad() { this.fetchData() }, methods: { async fetchData() { uni.showLoading({ title: 加载中... }) try { const res await this.$http.get(/api/v1/stocks/today) // 调用 server 端接口 this.chartData res.data.chart this.stockList res.data.list } catch (err) { uni.showToast({ title: 数据加载失败, icon: none }) } finally { this.loaded true uni.hideLoading() } } } } /script参数说明v-if!loaded触发 DOM 销毁重建比v-show更节省内存.skeleton-*类使用background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%)模拟加载动画uni.showLoading的mask: true必须设为true否则 iOS 上会透出白屏。3. Server 端用 Node.js Express 构建轻量 API 层聚焦行情聚合与安全代理3.1 为什么不用云开发Server 端存在的核心价值微信云开发CloudBase虽免运维但“茉莉炒股”类应用需对接多个外部数据源免费行情新浪财经hq.sinajs.cn、网易财经quotes.money.163.com公告爬取巨潮资讯网需绕过反爬第三方指标TradingView 社区脚本需 JS 执行环境云开发的 HTTP 函数无法稳定调用这些目标站DNS 解析失败、TLS 版本不兼容、IP 被封而自建 Node.js 服务可通过axios配置httpsAgent、proxy、headers精细控制请求链路。热词中charles抓包电脑端微信小程序的需求本质是调试 server 端与小程序的通信协议这只有自建服务才能实现。3.2 Express 服务最小可行配置支持 CORS、JWT 验证、请求限流3.2.1 核心中间件组合// server/app.js const express require(express) const cors require(cors) const rateLimit require(express-rate-limit) const jwt require(jsonwebtoken) const app express() // 1. 全局 CORS允许小程序域名注意不能用 * app.use(cors({ origin: [https://servicewechat.com, https://miniprogram.com], // 微信官方域名白名单 credentials: true })) // 2. 请求体解析 app.use(express.json({ limit: 5mb })) app.use(express.urlencoded({ extended: true, limit: 5mb })) // 3. 全局限流防恶意刷接口每分钟 60 次 const limiter rateLimit({ windowMs: 60 * 1000, max: 60, message: { code: 429, msg: 请求过于频繁请稍后再试 } }) app.use(/api/, limiter) // 4. JWT 验证中间件验证小程序端传来的 token app.use(/api/, (req, res, next) { const authHeader req.headers.authorization if (!authHeader || !authHeader.startsWith(Bearer )) { return res.status(401).json({ code: 401, msg: 未授权访问 }) } const token authHeader.split( )[1] try { const decoded jwt.verify(token, process.env.JWT_SECRET) req.user decoded next() } catch (err) { res.status(401).json({ code: 401, msg: token 无效 }) } }) // 5. 路由注册 app.use(/api/v1, require(./routes/stock)) app.use(/api/v1, require(./routes/notice)) module.exports app提示origin必须精确匹配小程序request的header.referer微信开发者工具中为https://servicewechat.com/xxx/xxx/page线上为https://miniprogram.comJWT_SECRET应从环境变量读取禁止硬编码。3.2.2 行情聚合路由合并多源数据并降噪// server/routes/stock.js const router require(express).Router() const axios require(axios) // GET /api/v1/stocks/today → 返回今日涨幅榜新浪网易双源校验 router.get(/today, async (req, res) { try { // 并行请求两个源取交集数据 const [sinaRes, neteaseRes] await Promise.all([ axios.get(https://hq.sinajs.cn/listsz000001,sh600000, { timeout: 5000, headers: { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) } }), axios.get(https://quotes.money.163.com/service/chddata.html?code000001start20240101end20240101, { timeout: 5000, responseType: text }) ]) // 解析新浪数据返回 JS 脚本格式 const sinaData parseSinaResponse(sinaRes.data) // 解析网易数据CSV 格式 const neteaseData parseNeteaseResponse(neteaseRes.data) // 取交集 去重 按涨幅排序 const merged mergeAndSort(sinaData, neteaseData) res.json({ code: 200, data: { chart: generateKLine(merged.slice(0, 30)), // 生成前 30 只股 K 线 list: merged.slice(0, 20) // 涨幅榜前 20 } }) } catch (err) { console.error(行情聚合失败:, err.message) res.status(500).json({ code: 500, msg: 服务暂时不可用 }) } }) function parseSinaResponse(text) { // 示例var hq_str_sh600000浦发银行,15.200,15.150,15.250,15.100,15.200,15.200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0; const stocks [] const lines text.split(;\n) lines.forEach(line { if (line.includes(hq_str_)) { const match line.match(/hq_str_(\w)([^])/) if (match) { const [, code, dataStr] match const fields dataStr.split(,) stocks.push({ code: code.toUpperCase(), name: fields[0], price: parseFloat(fields[3]) || 0, change: parseFloat(fields[3]) - parseFloat(fields[1]) || 0, changeRate: ((parseFloat(fields[3]) - parseFloat(fields[1])) / parseFloat(fields[1]) * 100).toFixed(2) }) } } }) return stocks } module.exports router参数说明timeout: 5000防止单源阻塞整个响应mergeAndSort函数需实现价格差异 5% 时丢弃该条数据防源站异常generateKLine不直接返回原始 K 线而是计算 5 日/10 日均线后返回简化版 JSON减小传输体积。4. 爬虫端用 Puppeteer 模拟浏览器抓取公告规避巨潮资讯网反爬4.1 为什么必须用 PuppeteerRequests 为何失效巨潮资讯网www.cninfo.com.cn采用以下反爬策略动态生成Cookieacw_tc需执行 JS 计算请求头Referer和User-Agent必须匹配真实浏览器首页加载后需触发window.scrollTo才加载公告列表requests或axios无法执行 JS而 Puppeteer 启动 Chromium 实例可完整复现用户行为。热词中微信小程序抓包的调试对象往往是爬虫端与巨潮的交互过程而非小程序本身。4.2 Puppeteer 爬虫最小可靠配置稳定、可监控、低频4.2.1 启动参数与基础设置// crawler/index.js const puppeteer require(puppeteer) async function launchBrowser() { return await puppeteer.launch({ headless: true, // 无头模式服务器友好 args: [ --no-sandbox, --disable-setuid-sandbox, --disable-gpu, --disable-dev-shm-usage, --disable-extensions, --disable-background-networking, --disable-default-apps, --disable-hang-monitor, --disable-prompt-on-repost, --disable-client-side-phishing-detection, --disable-sync, --disable-web-security, --disable-featuresIsolateOrigins,site-per-process, --disable-ipc-flooding-protection, --disable-renderer-backgrounding, --disable-background-timer-throttling, --disable-backgrounding-occluded-windows, --disable-remote-fonts, --font-render-hintingnone, --disable-blink-featuresAutomationControlled, --user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ], executablePath: process.env.CHROMIUM_PATH || undefined // Docker 环境指定路径 }) } // 设置全局拦截屏蔽图片/CSS/字体减少加载时间 async function setupRequestInterception(page) { await page.setRequestInterception(true) page.on(request, req { if ([image, stylesheet, font, media].includes(req.resourceType())) { req.abort() } else { req.continue() } }) }提示--disable-blink-featuresAutomationControlled是关键否则巨潮通过navigator.webdriver检测到自动化user-agent必须与真实 Chrome 一致否则返回 403page.setRequestInterception屏蔽非必要资源将单页加载时间从 8s 降至 2.3s。4.2.2 公告列表抓取处理分页与动态加载async function fetchAnnouncements(keyword 年报) { const browser await launchBrowser() const page await browser.newPage() try { await setupRequestInterception(page) // 1. 访问巨潮首页 await page.goto(http://www.cninfo.com.cn, { waitUntil: networkidle2, timeout: 30000 }) // 2. 输入关键词并搜索模拟人工操作 await page.type(#searchKey, keyword, { delay: 100 }) await page.click(#searchButton) await page.waitForNavigation({ waitUntil: networkidle2, timeout: 30000 }) // 3. 等待公告列表渲染巨潮用 Vue需等数据绑定完成 await page.waitForFunction(() document.querySelector(.gglb-list)?.children.length 0, { timeout: 15000 }) // 4. 滚动到底部触发懒加载最多 3 次 for (let i 0; i 3; i) { await page.evaluate(() window.scrollTo(0, document.body.scrollHeight)) await page.waitForTimeout(1000) await page.waitForFunction(() document.querySelectorAll(.gglb-list li).length 0) } // 5. 提取公告元数据 const announcements await page.evaluate(() { return Array.from(document.querySelectorAll(.gglb-list li)).map(li { const titleEl li.querySelector(.notice-title a) const codeEl li.querySelector(.code) const dateEl li.querySelector(.date) return { title: titleEl?.innerText.trim() || , code: codeEl?.innerText.trim() || , date: dateEl?.innerText.trim() || , url: titleEl?.href || } }).filter(item item.title item.code) // 过滤空数据 }) console.log(抓取到 ${announcements.length} 条公告) return announcements } catch (err) { console.error(抓取失败:, err.message) throw err } finally { await browser.close() } } // 每 2 小时执行一次生产环境用 node-cron // cron.schedule(0 */2 * * *, () fetchAnnouncements(年报)) module.exports { fetchAnnouncements }参数说明waitUntil: networkidle2表示连续 2 秒无网络请求才认为页面加载完成waitForFunction检测 DOM 元素存在比固定waitForTimeout更可靠page.evaluate内的代码在浏览器上下文执行可直接操作documentfilter步骤必须保留巨潮 HTML 中常含广告占位符。5. 三层协同关键点数据流转、安全加固与微信审核避坑5.1 数据如何从爬虫端安全抵达小程序端不走明文直连爬虫抓取的公告数据若直接存入 MongoDB 并由小程序request调用存在两大风险微信审核拒绝小程序request域名必须在request合法域名白名单中而爬虫服务器 IP 可能被微信风控系统标记为“高危”数据泄露公告 PDF 链接如http://static.cninfo.com.cn/finalpage/2024-01-01/123456789.PDF若暴露给前端可能被批量下载导致版权纠纷。正确做法是 server 端做代理中转// server/routes/notice.js const router require(express).Router() const { fetchAnnouncements } require(../crawler) // GET /api/v1/notices → 返回脱敏后的公告摘要 router.get(/, async (req, res) { try { const keyword req.query.q || 年报 const rawList await fetchAnnouncements(keyword) // 1. 过滤敏感字段移除原始 PDF URL const cleanList rawList.map(item ({ title: item.title, code: item.code, date: item.date, // 2. 生成带签名的临时 URL有效期 1 小时 pdfUrl: /api/v1/pdf?token${signPdfToken(item.url)}expires${Date.now() 3600000} })) res.json({ code: 200, data: cleanList }) } catch (err) { res.status(500).json({ code: 500, msg: 公告获取失败 }) } }) // POST /api/v1/pdf → 验证签名后重定向到真实 PDF router.post(/pdf, (req, res) { const { token, expires } req.query if (Date.now() parseInt(expires) || !verifyPdfToken(token, req.query.url)) { return res.status(403).send(Forbidden) } // 3. 使用 http-proxy-middleware 代理请求隐藏真实源站 res.redirect(302, req.query.url) // 微信小程序支持 302 重定向 }) module.exports router提示signPdfToken使用 HMAC-SHA256 签名密钥存于环境变量res.redirect比res.sendFile更安全避免服务器暴露文件系统路径微信对302重定向无额外限制但需确保目标 PDF 域名已在小程序后台配置为downloadFile合法域名。5.2 微信审核必过清单针对“茉莉炒股”类应用的 5 个硬性检查点检查项小程序端要求Server 端配合违规后果1. 无实盘交易功能删除所有wx.requestPayment、wx.openCustomerServiceConversation调用UI 上禁用“买入”“卖出”按钮API 接口返回{code:403,msg:演示环境暂不支持交易}直接驳回标注“涉及金融交易资质缺失”2. 行情数据来源声明在“关于我们”页底部添加“行情数据来源于新浪财经、网易财经仅供参考”在/api/v1/stocks/today响应中增加source: [sina, netease]字段驳回风险高微信要求数据可溯源3. 用户隐私协议弹窗首次启动强制弹窗勾选后才允许wx.getSettingGET /api/v1/user/init接口返回privacy_version: 20240101小程序比对版本号决定是否弹窗48 小时内未整改下架4. 无诱导分享行为禁用wx.showShareMenu({ withShareTicket: true })分享卡片标题不得含“免费”“领取”等诱导词分享链接?share_idxxx中的share_id仅用于统计不关联奖励逻辑限流或封禁分享能力5. 爬虫端不暴露 IP小程序request只调用自己 server 域名如https://api.jasmine-stock.comServer 端用axios代理爬虫请求headers.Referer设为https://api.jasmine-stock.com若小程序直连巨潮域名100% 驳回5.3 最后一道防线用 Charles 抓包验证全链路热词中charles抓包电脑端微信小程序是上线前必备动作目的不是破解而是确认小程序request的Host头是否为你的 server 域名非巨潮、新浪Authorization头中的 JWT 是否由你的JWT_SECRET签发所有302重定向的Location响应头是否指向合法downloadFile域名Set-Cookie是否包含HttpOnly和Secure标志防止 XSS 窃取。Charles 配置要点在Proxy → SSL Proxying Settings中勾选Enable SSL Proxying并安装 Charles 根证书到 Windows/macOS在Proxy → Proxy Settings中设置Port为8888小程序request的url改为http://localhost:8888/api/v1/stocks/today在Structure标签页右键域名 →Breakpoints对/api/v1/所有请求设断点手动修改response模拟错误场景如返回401测试登录态处理。注意Charles 抓包时小程序wx.request的method会显示为CONNECTHTTPS 握手实际请求需看SSL子节点下的GET/POST若看不到子节点说明未安装证书或SSL Proxying未启用。微信小程序的审核逻辑是“形式审查为主实质审查为辅”只要三层架构中每一层都守住合规边界——小程序端不越界调用、server 端不裸露源站、爬虫端不直连高危域名——“茉莉炒股”这类工具就能平稳上线。真正的技术难点从来不在 ZIP 包里而在如何让每一行代码都活在微信的规则框架内。本文还有配套的精品资源点击获取
返回列表