ARTICLE DETAIL

资讯详情

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

微信小程序记账项目实战:.ashx后端接口与联调解析

微信小程序记账项目实战:.ashx后端接口与联调解析 简介这是一套带后端服务的简易记账微信小程序源码适合正在学习小程序前后端交互、或想快速搭建个人记账工具的开发者。资源不仅包含前端页面wxml/wxss/js与逻辑还提供了基于C#的ashx处理程序、cs类库等后端代码以及管理后台相关配置可清晰理解记账数据从接口请求到存储的完整链路。压缩包共126个文件主要类型有cs、ashx、js、wxss、json、png等并含Visual Studio工程文件总大小仅312KB结构紧凑便于对照分析。当前已有46人学习下载作为小程序入门与实战的参考资料相当合适。通过阅读和改造这套代码可以掌握小程序登录、流水记录与历史账单查询等功能的实现思路也能体会页面渲染、数据绑定、接口调用等核心概念对搭建带管理后台的小程序项目有直接帮助。1. .ashx 后端的小程序记账项目越老的技术越能看到本质一份带后端的微信小程序记账源码压缩包里是三个 .ashx 处理程序加一个 csproj 工程文件。技术栈看起来有些年头——ASP.NET 一般处理程序不是现在的主流选择——但恰恰因为简单它把「小程序前端 HTTP 接口 数据库」这条链路的每一个节点都暴露得很清楚。如果你写过只调云开发数据库的小程序再看这个项目会很容易理解后端接口到底在做什么以及为什么登录、记账、查历史账单这些动作必须有服务端参与。这份源码适合刚接触前后端分离的开发者也适合想快速搭一套可演示的带后台小程序的人。老技术不一定过时它的边界恰好是学习价值所在。2. 小程序端登录态、记账表单与历史列表的实现2.1 页面结构与路由设计先看前端部分的整体组织方式。典型的微信小程序目录是 pages 下按功能拆页面这套源码对应的页面结构一般会这样安排pages/login/账号密码登录页登录成功后跳转记账主页pages/index/记账主页含收入/支出切换、分类选择、金额输入pages/history/历史账单列表按时间倒序展示pages/mine/个人中心展示用户信息与退出登录页面之间通过wx.navigateTo或wx.switchTab跳转。要注意的是如果把 index 设为 tabBar 页面wx.navigateTo是无法跳转到 tabBar 页面的必须使用wx.switchTab这一点在改造源码时经常踩坑。记账主页通常承载最核心的交互金额输入框、分类 picker、备注输入以及保存按钮。2.2 登录态管理从 wx.request 到 Storage这个项目的登录走的是账号密码校验后端拿到请求后核对数据库里的用户表成功则返回一个标志位和用户信息。小程序端收到成功响应后把用户 ID 和用户名写入本地缓存wx.request({ url: https://your-domain.com/UserLogin.ashx, method: POST, data: { username: this.data.username, password: this.data.password }, success(res) { if (res.data.code 0) { wx.setStorageSync(userId, res.data.data.userId); wx.setStorageSync(userName, res.data.data.userName); wx.switchTab({ url: /pages/index/index }); } else { wx.showToast({ title: res.data.msg, icon: none }); } } });这里有几个值得注意的设计点。wx.setStorageSync是同步写入本地缓存适合登录这种需要立刻读取状态的场景如果是频繁写入的数据建议用异步版本wx.setStorage避免阻塞渲染线程。登录成功后我没有用全局变量globalData存用户信息而是直接写 Storage因为globalData在小程序冷启动后会丢失Storage 可以持久化。2.3 记账页的数据绑定与提交逻辑记账页的 WXML 结构通常是一组表单控件绑定到 data 中的字段。以支出记账为例核心代码长这样view classform-item text金额/text input typedigit bindinputonAmountInput placeholder0.00 / /view view classform-item text分类/text picker range{{categories}} bindchangeonCategoryChange view{{categories[categoryIndex]}}/view /picker /view button bindtaponSubmit保存/buttonbindinput会在每次输入时触发onAmountInput把输入值同步到data.amounttypedigit会调起带小数点的数字键盘这是记账应用必须注意的细节如果写成typetext用户还得手动切键盘。提交时组装数据调用AddRecordBill.ashx这里有一个容易忽略的问题金额字段传给后端时要确认是字符串还是数字类型。有的开发者直接把this.data.amount传出去后端拿到的可能是带前导零的字符串排序和汇总统计会出现诡异问题。我一般会在提交前做一次parseFloat再传给后端。3. .ashx 接口层UserLogin 与 Bill 的 C# 实现3.1 为什么用一般处理程序而不是 WebAPI.ashx是 ASP.NET 的一般处理程序本质是一个实现了IHttpHandler接口的类只有ProcessRequest和IsReusable两个成员。相比完整的 WebAPI 或 MVC Controller它没有模型绑定、没有过滤器管道、没有依赖注入所有逻辑都在一个方法里完成。不夸张地说它就是「裸」的 HTTP 处理入口。但这对学习反而是优点。用 WebAPI 时参数绑定、JSON 序列化、路由映射这些框架行为会掩盖 HTTP 请求处理的本质。.ashx里context.Request和context.Response是直接暴露的你能清楚地看到请求参数怎么读、响应字节怎么写出。对于「理解接口在做什么」这个小目标没有比这更合适的教学载体了。生产环境上如果追求开发效率可以换成 ASP.NET Core Minimal API代码量差不多但生态更好这个项目保持 .ashx 不改也完全能跑。3.2 UserLogin.ashx密码校验与会话返回登录接口的逻辑顺序是读取请求参数 → 查询数据库 → 校验密码 → 返回 JSON。密码校验部分我不会在数据库里存明文而是存哈希值。下面是这个接口的典型实现public void ProcessRequest(HttpContext context) { context.Response.ContentType application/json; context.Response.ContentEncoding Encoding.UTF8; string username context.Request[username]; string password context.Request[password]; if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) { WriteJson(context, -1, 用户名和密码不能为空, null); return; } string hash GetMd5Hash(password); string connStr ConfigurationManager.ConnectionStrings[SqlServer].ConnectionString; DataTable dt new DataTable(); using (SqlConnection conn new SqlConnection(connStr)) { string sql SELECT UserId, UserName, Role FROM T_User WHERE UserNameun AND Passwordpw; using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(un, username); cmd.Parameters.AddWithValue(pw, hash); conn.Open(); SqlDataAdapter da new SqlDataAdapter(cmd); da.Fill(dt); } } if (dt.Rows.Count 1) { var data new { userId dt.Rows[0][UserId].ToString(), userName dt.Rows[0][UserName].ToString(), role dt.Rows[0][Role].ToString() }; WriteJson(context, 0, 登录成功, data); } else { WriteJson(context, -2, 用户名或密码错误, null); } }这段代码里有三个细节值得展开。第一密码用 MD5 哈希后存储和比对而不是明文但 MD5 已经不够安全生产环境建议换成 SHA256 或 BCrypt。第二SQL 查询用了参数化查询AddWithValue避免了字符串拼接注入风险。第三响应统一走WriteJson方法序列化保证 code/msg/data 三段式结构一致小程序端解析时不需要处理多种格式。3.3 AddRecordBill.ashx参数化写入与事务记账接口比登录复杂一点因为要同时校验用户身份和账单数据合法性。支出、收入、分类、备注这些字段都需要逐项检查。账单写入时我建议加上事务特别是后续扩展为「写账单同时更新用户总余额」这类场景时任何一个步骤失败都应该回滚public void ProcessRequest(HttpContext context) { context.Response.ContentType application/json; context.Response.ContentEncoding Encoding.UTF8; string userId context.Request[userId]; string type context.Request[type]; // 0 支出, 1 收入 string amount context.Request[amount]; // 单位: 元 string category context.Request[category]; string remark context.Request[remark]; decimal amountDec; if (!decimal.TryParse(amount, out amountDec) || amountDec 0) { WriteJson(context, -1, 金额格式不正确, null); return; } string connStr ConfigurationManager.ConnectionStrings[SqlServer].ConnectionString; using (SqlConnection conn new SqlConnection(connStr)) { conn.Open(); SqlTransaction tran conn.BeginTransaction(); try { string sql INSERT INTO T_Bill(UserId, Type, Amount, Category, Remark, CreateTime) VALUES(userId, type, amount, category, remark, GETDATE()); using (SqlCommand cmd new SqlCommand(sql, conn, tran)) { cmd.Parameters.AddWithValue(userId, userId); cmd.Parameters.AddWithValue(type, type); cmd.Parameters.AddWithValue(amount, amountDec); cmd.Parameters.AddWithValue(category, category); cmd.Parameters.AddWithValue(remark, remark); cmd.ExecuteNonQuery(); } tran.Commit(); WriteJson(context, 0, 记账成功, null); } catch { tran.Rollback(); WriteJson(context, -3, 数据写入失败, null); } } }金额的校验是这里的核心逻辑。decimal.TryParse比Convert.ToDecimal安全后者在遇到非法输入时会抛异常前者只会返回 false。另外注意我用了GETDATE()让数据库生成账单时间而不是依赖前端传时间——前端时钟可能不准或被修改服务端时间才是可信的记账基准。3.4 GetHistroyBillList.ashx条件查询与分页历史账单接口需要按用户 ID 过滤按时间倒序排列并且支持分页。这个接口很容易写出性能问题比如一次性把所有账单全查出来。账单一多小程序端渲染就会出现明显卡顿。标准做法是分页查询string userId context.Request[userId]; string pageIndex context.Request[pageIndex] ?? 1; string pageSize context.Request[pageSize] ?? 20; int pageIndexInt int.Parse(pageIndex); int pageSizeInt int.Parse(pageSize); string sql SELECT * FROM ( SELECT ROW_NUMBER() OVER (ORDER BY CreateTime DESC) AS RowNo, Amount, Type, Category, Remark, CreateTime FROM T_Bill WHERE UserId userId ) AS T WHERE RowNo BETWEEN start AND end; cmd.Parameters.AddWithValue(userId, userId); cmd.Parameters.AddWithValue(start, (pageIndexInt - 1) * pageSizeInt 1); cmd.Parameters.AddWithValue(end, pageIndexInt * pageSizeInt);ROW_NUMBER()是 SQL Server 里分页的经典写法在 2012 之前的版本里是标准方案之后的版本可以改用OFFSET ... FETCH NEXT更直观。分页参数从前端传入后必须转成整数再参与计算否则字符串拼接很容易被注入利用。Category 字段建议做一次Server.UrlDecode处理中文分类名在 URL 传递时会被编码。3.5 三个接口的参数与返回约定把三个接口放在一起看参数约定前端联调时更容易对齐接口方法必填参数返回结构UserLogin.ashxPOSTusername, passwordcode, msg, data(userId, userName)AddRecordBill.ashxPOSTuserId, type, amount, categorycode, msg, data(null)GetHistroyBillList.ashxGET/POSTuserId, pageIndex, pageSizecode, msg, data(list, total)返回格式统一是关键。code 为 0 表示成功负数各不相同-1 参数错误-2 登录失败-3 数据库异常。这样小程序端只需要判断一次res.data.code 0就能走成功分支错误信息全部由msg字段承载展示给用户。很多新手写的接口每个返回格式都不一样联调时前端要写大量 if-else 兼容这是管理混乱的根源。4. 从点击到入库记账链路的联调与排错4.1 wx.request 与 .ashx 的请求接送小程序端发请求的完整链路是wxml 的 bindtap 触发 → js 里的提交函数 → wx.request → 服务器上的 .ashx → ProcessRequest 被调用 → HttpResponse 返回 → 小程序 success 回调里解析数据。这中间最容易断的点在 URL 拼接和后端路由。.ashx文件在 IIS 下的 URL 就是文件名本身不涉及路由表配置。如果部署后访问返回 404先确认两个位置IIS 是否安装了 ASP.NET 功能模块以及应用程序池是否选择了正确的 .NET CLR 版本。这个项目用的是 .NET Framework不是 .NET Core应用程序池版本必须匹配不然会直接报Handler PageHandlerFactory-Integrated 的模块列表中错误。小程序端的wx.request域名必须是 HTTPS 并且在微信公众平台后台配置过 request 合法域名。开发阶段可以勾选「不校验合法域名」但体验版和正式版不会生效。调试时我习惯先用浏览器直接访问AddRecordBill.ashx?userId1amount10...如果浏览器能正确返回 JSON说明后端没问题问题只可能出在小程序配置或参数拼写上。4.2 状态码与错误提示设计接口返回的 code 设计参考了 HTTP 状态码的思路但是没有完全照搬。HTTP 200 一定会返回代表请求网络层面成功业务层的成败全部放到 code 里。这种设计的好处是网络错误HTTP 非 200和业务错误HTTP 200 code ! 0能清晰区分。小程序端对应处理时我会做一个统一的封装方法function doRequest(url, data, onSuccess, onFail) { wx.request({ url: url, data: data, method: POST, header: { Content-Type: application/x-www-form-urlencoded }, success(res) { if (res.data.code 0) { onSuccess(res.data.data); } else { wx.showToast({ title: res.data.msg, icon: none }); } }, fail() { wx.showToast({ title: 网络异常请检查服务器, icon: none }); } }); }注意header里的Content-Type。.ashx里通过context.Request[username]读取参数时兼容application/x-www-form-urlencoded和 query string 两种传递方式但不解析application/json的请求体。如果你在小程序端设置了header: {Content-Type: application/json}然后把数据 JSON.stringify 传过去后端通过索引器是读不到值的。4.3 高频踩坑编码、日期与域名白名单我从这类项目里看到的联调问题十有八九集中在下面几个地方场景现象原因与解法中文字符乱码记账备注和分类在页面上显示为乱码响应编码没设置 UTF-8ProcessRequest里Response.ContentEncoding Encoding.UTF8是必须的日期格式不一致前端显示/Date(1728000000000)/格式ASP.NET 的 JavaScriptSerializer 序列化 DateTime 会输出这种格式前端需要截取毫秒数再 new Date() 转换域名配置缺失工具正常手机上请求失败微信公众平台的 request 合法域名没配置或没同步到小程序版本金额精度丢失100.10 记成 100.1统计对不上数据库字段用了 float 而不是 decimal金额字段必须用 decimal(18,2)分页失效切页后数据重复或遗漏排序字段有重复值导致 ROW_NUMBER 不稳定ORDER BY 要追加主键作为第二排序键日期序列化这块单独说一下。如果你在后端把CreateTime转成字符串返回前端拿到的就是格式化好的文本但时区问题需要处理。我一般建议前端统一接收时间戳或 ISO 字符串展示时统一用dayjs或Date原生对象格式化避免每个页面各写一套转换逻辑。5. 进阶把简易记账改成能上线的记账服务5.1 把账号密码换成微信 OpenID当前登录方案是账号密码这在小程序里其实不友好因为用户不想在小程序里注册新账号。常见的改造方向是接wx.login()换取code后端拿code调微信接口获取openid用openid作为用户的唯一标识。改造接口时UserLogin.ashx的参数从 username/password 变为 code后端多一次 HTTP 调用微信服务端换取 openid。第一次登录的 openid 不在用户表里就自动创建一条新记录已存在则直接返回用户信息。密码字段也随之取消数据库表的 Password 列可以保留但不再校验。5.2 用 GROUP BY 补一个消费统计接口记账应用除了录入和查看用户最需要的是月度统计。给这套源码加一个统计接口GetBillSummary.ashx按分类聚合支出金额string sql SELECT Category, SUM(Amount) AS TotalAmount, COUNT(*) AS Cnt FROM T_Bill WHERE UserId userId AND Type 0 AND CreateTime startDate AND CreateTime endDate GROUP BY Category ORDER BY TotalAmount DESC;一个 SQL 就能完成「这个月三餐花了多少」这类分析需求不用把所有明细捞到前端再算。这个接口也让「后端不仅是增删改查」这件事变得具体——增删改查之外聚合统计、趋势预测、分类占比这些都是后端真正的价值。5.3 部署与安全基线上线部署前把这几项检查过一遍数据库连接字符串从代码里挪到 Web.config 并加密IIS 应用程序池设为经典模式或集成模式取决于服务端环境.ashx文件目录禁止列出文件内容用户输入的超长字段在入库前截断。用 Charles 等网络调试工具观察一次完整请求和响应确认没有泄露 SQL 错误堆栈、密码哈希等敏感信息这套记账小程序就可以从学习案例变成一个正常运行的业务服务了。本文还有配套的精品资源点击获取
返回列表