ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Android财务系统毕业设计:登录、账单与统计全模块解析

Android财务系统毕业设计:登录、账单与统计全模块解析 简介面向毕业设计场景的Android财务系统完整项目资源包适合计算机相关专业学生与需要快速搭建财务类App的开发者。资源共1226个文件压缩包仅10.86MB以Java源码、XML布局、PNG图片、JAR依赖库等类型为主包含完整的界面布局、业务逻辑、图标资源与第三方库配置可直接导入Android Studio运行。系统覆盖登录验证、公告管理、账单管理、账单统计、个人中心、用户管理六大模块支持管理员与注册用户不同角色的操作闭环其中收入支出统计与密码修改等功能可满足毕业设计常见功能要求。全部代码与配置按Android工程标准目录组织便于阅读、调试和二次扩展。目前已有167人学习下载适合作为毕业设计参考或项目实战训练素材。1. 为什么一个“财务系统”毕业设计要先想清楚身份认证很多同学拿到这个题目第一反应是先把账单增删改查写出来结果做到一半发现登录、权限、公告、统计全缠在一起改一处崩三处。这个基于 Android 的财务系统表面上是六个功能模块实际上是一条完整的数据链路认证决定“你是谁”用户管理决定“你能看到谁”账单和统计决定“你看到什么”公告和个人中心则是系统运转的辅助闭环。如果一上来就闷头写界面后面对接后端接口时一定会反复返工。这篇文按“认证 → 用户 → 公告 → 账单 → 统计 → 设置”的顺序拆开讲每部分都给出可落地的代码片段和参数说明适合正在做 Android 课程设计、或者想补全一个完整业务闭环的开发者参考。2. 登录模块与用户管理的权限基线2.1 身份验证的常见实现方式登录模块在这个项目里承担的是“门禁”角色它不只要验证账号密码还要为后续的公告管理、用户管理提供权限判断依据。我见过不少毕业设计把登录做成“本地比对用户名密码”这种做法演示起来没问题但换到真实场景就有隐患客户端拿到的用户列表是完整的攻击者反编译 APK 就能看到全部账号。常见做法是把登录校验放到服务端Android 端只负责提交账号密码并接收 token。客户端代码可以这样写// LoginActivity.java private void doLogin(String account, String password) { // 先做本地非空校验避免无谓的网络请求 if (TextUtils.isEmpty(account) || TextUtils.isEmpty(password)) { showToast(账号和密码不能为空); return; } // 构建请求体这里用 JSON 格式传递 JSONObject body new JSONObject(); try { body.put(account, account.trim()); body.put(password, MD5Util.md5(password)); // 密码先做 MD5 再传输 } catch (JSONException e) { e.printStackTrace(); return; } // 发起异步请求回调中处理登录结果 HttpHelper.post(/api/login, body.toString(), new HttpCallback() { Override public void onSuccess(String response) { // 解析 token 并保存到 SharedPreferences String token JSONParser.parseToken(response); SharedPreferencesUtil.putString(token, token); navigateToMain(); } Override public void onFailure(int code, String msg) { showToast(登录失败: msg); } }); }这段代码有两个关键参数需要说明MD5Util.md5(password)是对密码做摘要处理防止明文出现在请求日志里但要注意 MD5 本身不够安全生产环境建议换成 BCryptHttpHelper.post是封装好的网络请求工具实际项目里可以用 OkHttp 或 Retrofit 替代。每次登录成功后保存的 token是后续所有业务接口的通行凭证。2.2 用户管理模块的权限控制逻辑用户管理模块在这个系统里是由管理员操作的它的核心不是界面能做多少按钮而是服务端怎么保证“只有管理员能调用这些接口”。常见做法是在服务端拦截器里校验 token 关联的 role 字段Android 端根据登录时返回的角色值来决定是否显示“用户管理”入口。// MainActivity.java // 登录成功后获取用户角色1 表示管理员2 表示普通用户 int role SharedPreferencesUtil.getInt(role, 2); if (role 1) { // 只有管理员能看到用户管理入口 findViewById(R.id.btn_user_manage).setVisibility(View.VISIBLE); findViewById(R.id.btn_announcement_manage).setVisibility(View.VISIBLE); } else { findViewById(R.id.btn_user_manage).setVisibility(View.GONE); findViewById(R.id.btn_announcement_manage).setVisibility(View.GONE); }用户管理界面通常是一个 RecyclerView 列表配合搜索框和弹窗表单。添加或编辑用户时需要提交的字段包括账号、密码、电话、用户类型、姓名、性别、学历。这里的“用户类型”实际上就是权限分组的依据我建议把用户类型做成下拉选择而不是自由输入避免脏数据影响后续判定。字段类型约束说明accountString唯一必填登录账号passwordString必填建议 MD5 后入库phoneString选填联系方式userTypeint必填1或21管理员2普通用户nameString必填真实姓名genderString选填男/女educationString选填学历信息修改用户信息时注意一个细节如果密码字段留空说明不修改密码后端要加这个判断否则每次编辑用户都会被强制重置密码。提示删除用户前一定要做二次确认弹窗并且判断当前操作者不能删除自己这是管理类系统最容易踩的坑。3. 公告管理模块的 CRUD 与列表刷新策略3.1 公告列表的加载与分页公告管理模块的操作对象是“标题、内容、发布时间”这三类信息功能是增删改查。从 Android 端角度看它比用户管理多了一个分页问题公告数量会随着时间增长如果一次性全量加载列表会越拉越长体验变差。常见的分页方案有两种一种是服务端做page和pageSize参数控制另一种是客户端拿到全量数据后自行截断。前者适合真实项目后者适合 demo。这里以前者为例// AnnouncementListActivity.java private void loadAnnouncements(int page, int pageSize) { String url /api/announcements?page page pageSize pageSize; HttpHelper.get(url, new HttpCallback() { Override public void onSuccess(String response) { ListAnnouncement list JSONParser.parseAnnouncementList(response); if (page 1) { adapter.setData(list); } else { adapter.addData(list); } } }); }这里page从 1 开始计数pageSize一般设 10 或 20。下拉刷新时重新加载第一页上拉加载更多时页码加一。要注意的是发布公告和编辑公告后应该回到列表页并刷新第一页数据而不是直接在本地修改列表里的一项否则可能出现服务端数据与本地显示不一致的情况。3.2 公告的新增与编辑共用表单公告的新增和编辑界面通常可以共用一个 Activity通过 Intent 传递一个announcementId来判断是“新增”还是“编辑”。这样做的好处是减少代码重复但要注意表单回填时的时机// AnnouncementEditActivity.java // 判断是新增还是编辑 long announcementId getIntent().getLongExtra(announcement_id, -1); if (announcementId ! -1) { // 编辑模式先根据 id 拉取详情并回填表单 loadAnnouncementDetail(announcementId); } else { // 新增模式发布时间默认取当前时间 tvPublishTime.setText(DateUtil.getCurrentTime()); }发布时间字段在这个设计里有个容易忽略的点新增时是服务端自动生成还是客户端提交如果是客户端提交要考虑用户修改系统时间导致的数据偏差我建议服务端在接收到新增请求时以服务器当前时间覆盖客户端提交的时间字段保证时间线一致。删除公告时执行删除请求后刷新列表。删除接口一般要传announcementId不要在列表项上只传 position因为列表经过筛选排序后position 对应数据不一定准确。4. 账单管理模块的字段设计与增删改查4.1 账单类型的选择与存储账单管理模块的字段比公告多类型、用途、金额、添加日期、备注。这里的“类型”在这个设计中是“收入”和“支出”两个大类下的细分比如餐饮、交通、工资、奖金等。Android 端用一个表结构或本地数据库来承载这些数据都可以如果项目是联网的通常把账单数据存储到服务端客户端展示。比较推荐的实现是服务端存一张 bill 表核心字段包括bill_id、user_id、bill_type、category、amount、bill_date、remark。其中user_id必须取自 token 解析出的当前登录用户不能由客户端自由上传否则用户可以随意伪造他人账单。CREATE TABLE bill ( bill_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, bill_type INTEGER NOT NULL DEFAULT 1, -- 1收入 2支出 category TEXT NOT NULL, amount REAL NOT NULL, bill_date TEXT NOT NULL, remark TEXT, create_time TEXT DEFAULT (datetime(now)) );bill_type用 1 和 2 代替中文“收入/支出”是为了减少存储空间和避免编码问题展示层再映射为汉字。4.2 账单列表的搜索与筛选账单列表页一般需要一个搜索框同时支持按日期范围筛选。常见的实现是客户端把关键字和日期范围作为查询参数传给服务端// BillListActivity.java String keyword etSearch.getText().toString().trim(); String startDate tvStartDate.getText().toString(); String endDate tvEndDate.getText().toString(); String url /api/bills?keyword URLEncoder.encode(keyword) startDate startDate endDate endDate; HttpHelper.get(url, callback);keyword可以模糊匹配用途、备注、类型名称startDate和endDate默认给一个较大的跨度比如今年第一天到今天。这里要注意字符串格式统一建议全链路使用yyyy-MM-dd格式避免因日期格式不一致查不到数据。账单编辑和删除的细节与公告模块类似不再重复。需要特别提醒的是金额字段的处理服务端不要用float存钱使用DECIMAL(10,2)或客户端用BigDecimal否则多次累加会出现精度丢失。Android 端展示金额时也要避免直接fload amount Float.parseFloat(...)后做运算。4.3 bill 类型与统计的联动账单类型和统计模块是联动的。统计页要按类型汇总金额客户端展示时需要按bill_type分组求和。如果账单存的是“餐饮”“交通”这类细分分类那么统计页要能同时支持“按大类汇总”和“按细分分类汇总”这需要在服务端写一个GROUP BY查询SELECT bill_type, SUM(amount) AS total_amount FROM bill WHERE user_id ? AND bill_date BETWEEN ? AND ? GROUP BY bill_type;这个查询返回的结果就是“我的收入总金额”和“我的支出总金额”的数据来源。在此基础上再增加category字段的分组就能得到每个细分分类的占比。5. 账单统计模块的图表展示与明细钻取5.1 收支统计的聚合接口账单统计管理模块描述中提到由“我的收入、我的支出、收支统计”组成这里的核心动作是“聚合”和“下发”。客户端要拿到“按日期/按类型汇总”的数据需要一个专门的统计接口。// BillStatisticsActivity.java private void loadStatistics(String startDate, String endDate) { String url /api/bills/statistics?startDate startDate endDate endDate; HttpHelper.get(url, new HttpCallback() { Override public void onSuccess(String response) { // 返回数据结构: { totalIncome: 1234.5, totalExpense: 567.8, // incomeDetail: [...], expenseDetail: [...] } StatisticsData data JSONParser.parseStatistics(response); tvIncome.setText(¥ data.totalIncome); tvExpense.setText(¥ data.totalExpense); // 初始化图表控件传入聚合数据 initPieChart(data.incomeDetail, data.expenseDetail); } }); }统计接口返回的数据里incomeDetail和expenseDetail是分类汇总数组通常包含每个分类的名称和金额。这样做的好处是一次请求拿到全部需要的数据避免客户端多次循环调用普通账单列表接口再自行聚合。5.2 饼图与柱状图的适配Android 端实现图表最常用的是 MPAndroidChart 这个开源库。在build.gradle中依赖implementation com.github.PhilJay:MPAndroidChart:v3.1.0初始化饼图的方式private void initPieChart(ListCategoryAmount incomeData) { PieChart pieChart findViewById(R.id.pie_chart); ArrayListPieEntry entries new ArrayList(); for (CategoryAmount item : incomeData) { entries.add(new PieEntry(item.amount.floatValue(), item.categoryName)); } PieDataSet dataSet new PieDataSet(entries, 收入构成); dataSet.setColors(ColorTemplate.COLORFUL_COLORS); PieData data new PieData(dataSet); data.setValueFormatter(new PercentFormatter(pieChart)); pieChart.setData(data); pieChart.setUsePercentValues(true); pieChart.setEntryLabelTextSize(11f); pieChart.invalidate(); }PieEntry的第一个参数是数值第二个参数是显示标签setUsePercentValues(true)会让饼图显示百分比而不是具体金额适合看各分类占比。如果数据里有金额为 0 的分类最后会显示一个零占比建议在设置数据前过滤掉。5.3 点击扇形区域钻取明细统计界面还有一个常见的交互点击某个扇形区域跳到对应分类的账单明细列表。MPAndroidChart 提供了OnChartValueSelectedListenerpieChart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() { Override public void onValueSelected(Entry e, Highlight h) { // 这里的 label 就是分类名 String categoryName ((PieEntry) e).getLabel(); Intent intent new Intent(StatisticsActivity.this, BillListActivity.class); intent.putExtra(category, categoryName); intent.putExtra(billType, currentBillType); startActivity(intent); } Override public void onNothingSelected() { // 不做处理 } });跳转到 BillListActivity 后列表页读取category参数加在查询条件上就能展示该分类下的所有账单。这里要注意拼接 URL 时分类名要做 URL 编码否则中文参数会被截断或乱码。6. 个人中心密码修改与列表性能优化细节6.1 修改密码的校验逻辑个人中心管理模块按要求是修改密码输入原密码、新密码、确认密码。这个功能的实现并不复杂真正容易出错的是“原密码校验”的位置。如果本地保存了密码的 MD5可以在本地比对原密码如果没有保存就必须把原密码透传给服务端校验。// ChangePasswordActivity.java private void doChangePassword(String oldPwd, String newPwd, String confirmPwd) { if (!newPwd.equals(confirmPwd)) { showToast(两次输入的新密码不一致); return; } if (newPwd.length() 6 || newPwd.length() 20) { showToast(密码长度需在6-20位之间); return; } JSONObject body new JSONObject(); try { body.put(oldPassword, MD5Util.md5(oldPwd)); body.put(newPassword, MD5Util.md5(newPwd)); } catch (JSONException e) { e.printStackTrace(); return; } // 提交给服务端由服务端判断原密码是否正确 HttpHelper.post(/api/user/changePassword, body.toString(), new HttpCallback() { Override public void onSuccess(String response) { showToast(密码修改成功请重新登录); SharedPreferencesUtil.clear(); navigateToLogin(); } Override public void onFailure(int code, String msg) { showToast(修改失败: msg); } }); }有个细节值得注意每次输错新密码旧密码页面不要清空只清空两个新密码输入框即可这样用户体验更好。修改成功后清理本地 token 并强制回到登录页避免旧 token 继续使用。6.2 RecyclerView 列表的性能优化整个项目里用户管理、公告管理、账单管理都要用列表展示数据RecyclerView 的性能直接影响体验。几个实用的优化点// BillListAdapter.java Override public BillViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { // 1. 避免每次都重新 inflate 布局 View view LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_bill, parent, false); return new BillViewHolder(view); } Override public void onBindViewHolder(BillViewHolder holder, int position) { BillItem item getItem(position); // 2. 使用 setText 而不是每次创建新 String holder.tvAmount.setText(¥ item.amount); // 3. 如果列表项很多开启 setHasFixedSize }如果你不确定RecyclerView的布局是否固定可以mRecyclerView.setHasFixedSize(true)这样当列表项内容变化时不会触发重新布局。配合onCreateViewHolder的复用机制滚动卡顿的现象会明显改善。6.3 发布前要检查的三个边界不管这个项目是用于答辩还是自己学习发布前建议对照下面几个边界条件过一遍第一所有网络请求都要设置超时时间。OkHttp 默认连接超时是 10 秒读超时也是 10 秒如果你的服务端响应慢客户端会出现 ANR 或长时间白屏。在 OkHttpClient 构建时设置connectTimeout(10, TimeUnit.SECONDS)和readTimeout(10, TimeUnit.SECONDS)是基本要求。OkHttpClient client new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .build();第二AndroidManifest.xml中如果 targetSdkVersion 是 28 及以上默认禁止明文 HTTP 访问需要在res/xml/network_security_config.xml里配置允许特定网段或域名使用明文传输。很多同学在真机调试时遇到 “CLEARTEXT communication not permitted” 就是卡在这里。!-- res/xml/network_security_config.xml -- network-security-config domain-config cleartextTrafficPermittedtrue domain includeSubdomainstrue192.168.1.100/domain /domain-config /network-security-config第三退出登录时要调用服务端的注销接口让 token 失效而不仅仅是清空本地数据。否则账号已经在别处修改密码旧 token 如果还在有效期仍然可以访问部分接口这样权限控制就是失效的。本文还有配套的精品资源点击获取
返回列表