ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpringBoot+Android大学生勤工助学管理系统开发实战

SpringBoot+Android大学生勤工助学管理系统开发实战 1. 这个系统到底要解决什么问题先说结论大学生勤工助学管理系统本质上就是个多端协同的业务系统。学生要找岗位、要打卡工时、要看工资老师要发岗位、要审批申请、要核对结算管理员要管用户、管数据、管整个流程。三个角色在一个系统里协作牵扯到信息发布、流程审批、数据统计、权限隔离——这些东西如果靠纸质表格或微信群基本就是灾难。我当时接到这个项目的时候第一反应是这不就是一个“校园版BOSS直聘考勤打卡工资结算”的结合体吗想清楚这一点后面的技术选型和功能设计就顺了。整个项目的核心需求拆出来就三条信息流的匹配岗位发布→学生浏览→投递申请→老师审核这是主干流程工时的闭环上岗签到→工时登记→考勤确认→工资核算这是数据底座权限的分层学生、教师/用工单位、系统管理员三类角色各自只能看到和自己相关的数据适合谁参考两条线一是要做毕业设计的学生这个题目的完整度和技术栈覆盖面刚好卡在“能体现工作量、又不至于失控”的范围二是学校里真的要做勤工助学管理信息化的老师或技术团队本文的架构和部分设计可以直接抄作业。而我自己在做这个项目时最深刻的体会是勤工助学系统的难点不在技术而在“流程状态”的建模。申请、审批、上岗、签到、结算每个节点都有状态流转状态之间还有边界情况——比如学生申请了岗位但没去报到老师发布了岗位但一直没人申请这些都要在系统里兜住。状态建模做得清楚代码写起来就是水到渠成状态建模模糊后续每个功能都会返工。2. 技术选型为什么是SpringBootAndroid原生2.1 后端选型的核心考量后端选择SpringBoot这个不用多说Java生态里做管理类系统最成熟、资料最多的框架没有之一。但要注意的是版本选择——我见过太多人一上来就拉最新的SpringBoot 3.x结果各种兼容问题。SpringBoot 3.x要求JDK 17如果你的部署环境是JDK 8就直接劝退。我当时用的是SpringBoot 2.7.x JDK 8 MyBatis-Plus 3.5.x这个组合经过了大量生产验证网上搜问题一搜一个准踩坑成本极低。有同学问为什么不直接用SpringCloud那一套答案很简单勤工助学系统属于典型的单体应用用户量级撑死几千人同时在线分布式架构带来的复杂度远超收益。单体应用配合合理的模块划分开发效率最高部署也最简单。ORM选型我直接用了MyBatis-Plus。为什么不用JPA说实话JPA的自动建表和对象关系映射在简单场景下确实省事但勤工助学系统里有大量SQL涉及多表关联、分组聚合统计——比如“按月统计各岗位工时汇总”“按学院统计学生参与人数”这种报表类查询用MP的条件构造器加自定义SQL写起来比JPA的Specification和QueryDSL顺手多了。2.2 Android端为什么选择原生开发客户端这块标题里直接写了Android那就要考虑是原生还是跨平台。我当时纠结过UniApp因为一套代码能同时出安卓和iOS想省事。但最后还是选了Android原生原因有三第一这系统涉及大量原生交互拍照上传证明材料、扫码签到、定位打卡、文件下载导入导出Excel这些功能在UniApp里要么需要写原生插件要么性能有损耗绕了一圈还是绕回原生。第二音视频、摄像头、定位、文件读写这些底层能力的调试Android Studio原生环境下最直观有问题直接看日志就能定位。第三这个项目是教学和毕设场景原生Android更能体现你的技术深度——Activity/Fragment生命周期、Adapter的复用机制、权限动态申请、网络框架封装这些在面试时都是加分项。Android端技术栈Java Retrofit2 OkHttp3 Gson RxJava后来换成协程的同学可以平替 Glide LitePal或Room。不用太花哨够用就行。2.3 数据库设计先想清楚表结构再写代码勤工助学系统的表结构核心就这八张t_user用户表角色字段区分学生/教师/管理员t_position岗位表关联用工单位教师、岗位类型、招聘人数、薪资标准t_application申请表记录学生投递状态待审核/通过/拒绝t_work_log工时表记录学生每次打卡的起止时间和时长t_salary结算表按月汇总每个学生的工时和工资t_leave请假表学生请假信息t_notice公告表系统通知和岗位公告t_file附件表保存学生上传的证明材料其中最关键的是t_work_log的设计。我见过不少系统把工时表做得特别简单就记一个日期和一个时长结果到月底对账的时候根本算不清——学生哪天该来没来迟到早退怎么扣加班时长怎么算这些在表结构里必须有字段承接。我的设计里有work_date、start_time、end_time、actual_hours、status正常/迟到/缺勤再加一个audit_status待确认/已确认由用工老师在月末统一确认考勤确认后工时进入工资结算流程。岗位表也一样要留字段记录上下限min_hours和max_hours表示这个岗位每周要求的最低和最高工时。申请通过之后学生的工时记录必须落在这个范围内超出的部分不结算或者按加班价结算——这个规则在业务层判断。数据库设计这事一定要在写代码之前想清楚。你后端Controller写得再好表结构有问题也是白搭。我当时就是先画ER图、再敲定字段、最后才开工写代码的后面开发过程中几乎没有因为改表导致的大重构。3. 后端SpringBoot核心功能实现3.1 登录认证与权限控制的落地勤工助学系统有三个角色权限模型不复杂但一定要做干净。我用的是JWT 拦截器的方式没有引入Spring Security那套重家伙——说实话三个角色的简单权限控制引入Security反而让学习成本变高配置一堆东西就为了几个接口的放行和拦截划不来。JWT流程是这样的用户登录成功后后端生成Token里面塞入userId和role两个关键信息客户端每次请求在Header里带上Authorization: Bearer token后端拦截器解析Token把userId和role塞进ThreadLocal供Controller和Service层使用拦截器里做三件事放行登录接口和静态资源路径校验Token合法性校验接口角色权限。我用的是自定义注解RequireRole标注在Controller方法上拦截器里读取注解并和Token里的角色比对不匹配直接返回403。注意JWT的过期时间不要设太长。我一开始设了7天后来发现有学生手机丢了Token被别人拿去乱用出了点小麻烦。后面改成2小时过期、客户端维护一个refreshToken做无感刷新的方案才算踏实。密码存储用BCrypt加密不要用MD5——MD5彩虹表破解成本太低这在生产环境上是硬伤。// 登录认证核心代码简化版 public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } // 放行登录接口 if (request.getRequestURI().contains(/api/auth/)) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { response.setStatus(401); return false; } Claims claims JwtUtil.parseToken(token.replace(Bearer , )); // 校验角色权限 HandlerMethod handlerMethod (HandlerMethod) handler; RequireRole requireRole handlerMethod.getMethodAnnotation(RequireRole.class); if (requireRole ! null !requireRole.value().equals(claims.get(role))) { response.setStatus(403); return false; } UserContext.set(claims); return true; } }3.2 岗位发布、申请审批的状态机设计岗位的整个生命周期状态流转是核心。我定义了六个状态草稿0老师创建但未发布招聘中1已发布学生可见可申请已满员2招聘人数够了停止接收申请进行中3学生已经上岗正在执行已结束4岗位到期或完成已取消5异常情况终止状态流转不是乱来的必须遵守规则。比如“招聘中”才能接收申请“已满员”不能转成“进行中”除非有人退出。我把这些规则封装在PositionService里用状态机模式管理避免在每个业务方法里写if-else判断导致逻辑分散。学生申请岗位的状态则是四个待审核0已通过1已拒绝2已撤回3这里有一个容易被忽略的业务点学生申请通过后如果最终没有上岗比如没去报到后台要有个“失效”动作把申请记录标记掉否则列表里会残留一条“已通过但并未上岗”的脏数据影响统计报表。审批流程我用了异步消息通知——学生提交申请、老师审核通过这两个节点都触发站内消息通知。最开始用的是同步推送结果高峰期老师批量审核几十个申请的时候接口响应明显变慢。后来换成了SpringBoot自带的Async异步处理体验大幅改善。3.3 工时打卡与工资结算的算法设计工时打卡是学生端用得最多的功能。学生到达用工地点后点击“签到”按钮后端记录当前时间和定位信息可选。离开时点击“签退”后端根据起止时间计算出工时。这里有个细节计算工时要考虑最小时间单位。比如某个岗位规定“不足30分钟不计入工时”那你在计算的时候要做溢出处理。我的实现是public double calculateHours(Date startTime, Date endTime) { long diffMillis endTime.getTime() - startTime.getTime(); double hours diffMillis / 3600000.0; // 不足30分钟按30分钟计 BigDecimal bd new BigDecimal(hours); return bd.setScale(1, RoundingMode.FLOOR).doubleValue(); }工资结算按自然月进行。每月1号凌晨定时任务跑批查询上个月所有已确认考勤通过的工时记录按学生ID分组汇总总工时乘以岗位单价得到应发工资生成结算单状态为“待发放”管理员确认后状态变为“已发放”这里要特别注意结算逻辑务必做成“可重复执行”的幂等操作。因为定时任务可能重跑如果每次跑都新增结算单数据就翻倍了。我的方案是在t_salary表设置唯一索引student_id month插入时用ON DUPLICATE KEY UPDATE重跑任务只会更新数据不会新增记录。3.4 SpringBoot全局过滤器处理XSS攻击这个问题我在开发过程中碰到过——学生在“意见反馈”功能里输入了一段带script标签的内容结果页面直接弹窗了。这就是典型的XSS攻击。勤工助学系统里有大量文本输入场景个人简介、岗位描述、申请理由、公告内容。如果不做XSS防护恶意脚本一旦入库所有看到这个内容的用户都会中招——尤其是管理员端浏览器打开公告页面的时候一弹一个准。我采用的方案是全局过滤器在请求进入Controller之前统一处理public class XssFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest (HttpServletRequest) request; // 包装请求对Parameter、Header、Body中的内容进行转义 XssWrapper xssWrapper new XssWrapper(httpRequest); chain.doFilter(xssWrapper, response); } }核心思想继承HttpServletRequestWrapper重写getParameter、getHeader和getInputStream方法将script、javascript:等内容进行HTML转义。同时要注意白名单配置——富文本编辑器允许的标签如b、i、a不要一刀切转义否则老师发布岗位描述的时候加粗效果全没了。这里有一个“坑”要强调XSS防护只管入站数据如果数据库里已经有了脏数据过滤器是救不回来的。上线前务必写一个脚本把库里的script等危险字符串清洗一遍或者通过管理端增加一个“数据清洗”按钮一次性处理存量数据。3.5 MyBatis-Plus分页与多表关联查询列表页是管理系统的门面——岗位列表、学生列表、结算列表、申请列表清一色的分页查询。这里一定要用MyBatis-Plus的分页插件不要自己在SQL里写LIMITCOUNT两条语句来回传参那是上古时期的玩法了。分页插件的配置很简单Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }然后Service里直接PagePositionVo page new Page(pageNum, pageSize); LambdaQueryWrapperPosition wrapper new LambdaQueryWrapper(); wrapper.eq(Position::getStatus, 1).orderByDesc(Position::getCreateTime); IPagePositionVo result positionMapper.selectPositionPage(page, wrapper);特别提醒如果分页对象里需要关联其他表的数据比如岗位列表要显示用工单位名称建议在Mapper里写自定义SQL用LEFT JOIN一次性查出来然后用Select注解或XML映射到VO对象。不要用循环查询——那样数据量一上来就爆发N1问题接口直接卡死。4. Android客户端的核心实现与踩坑记录4.1 项目环境准备与基本结构Android端我用Android Studio开发Gradle版本选了个兼容性良好的组合Android Studio 4.2新版下载装好就行中文设置在两年前的版本里集成在设置里现在新用户一般装默认英文关系不大 Gradle 6.7.1 编译SDK 30 最低支持SDK 21Android 5.0。这个组合覆盖了绝大多数在校学生的手机不用考虑新版本适配Android 16适配那是后话等系统真正普及再说——但目标SDK已经要求31上架应用商店时记得把targetSdkVersion调高。项目分包结构activity启动页、登录页、主界面、岗位列表、岗位详情、个人中心adapter各类RecyclerView的Adapter注意使用ListAdapter或DiffUtil优化刷新效率apiRetrofit请求接口定义、统一响应体、拦截器model实体类和数据Beanutils工具类日期、加密、存储view自定义ViewBanner轮播、进度条等4.2 Retrofit封装与统一请求处理Android端网络请求我直接选了Retrofit2 OkHttp。这套组合在Java开发Android的圈子里属于标配资料多、坑少、扩展性强。关键的封装逻辑是统一处理三类情况网络异常超时、断网Toast提示“网络连接失败”不崩溃业务异常业务码非200弹出后端返回的错误消息Token失效HTTP 401拦截器里清除本地Token跳转登录页public class TokenInterceptor implements Interceptor { Override public Response intercept(Chain chain) throws IOException { Request original chain.request(); String token SPUtils.getToken(); Request request original.newBuilder() .header(Authorization, Bearer token) .build(); Response response chain.proceed(request); if (response.code() 401) { // 清除Token 重新登录 SPUtils.clear(); } return response; } }BaseUrl的切换也要注意。开发时用局域网地址比如http://192.168.1.100:8080测试时用模拟器地址10.0.2.2上线时用域名。我建议把BaseUrl写在一个Constants类里并且在BuildConfig里区分debug和release——这样切换环境只需要改Gradle配置不用动代码。关于定位打卡的权限动态申请Android 6.0以上都需要运行时权限。勤工助学系统用到的主要权限有定位权限签到打卡、相机权限拍证明材料、存储权限下载附件、通知权限推送公告。建议针对每个权限做理由说明的弹窗不要一上来直接申请一大坨——有学生拒绝后整个功能用不了排查了大半天。4.3 下拉刷新、加载更多与进度条岗位列表页是学生使用频率最高的页面。这里有两个体验细节非常重要一个是下拉刷新。用SwipeRefreshLayout包住RecyclerView刷新的时候重新拉取第一页数据。要注意的是下拉刷新的网络请求和滚动加载更多的请求不能冲突——我的做法是设置一个isLoading布尔值请求没完成前禁止再次触发刷新或加载。另一个是“加载更多”的滑动分页。滚动到底部时自动加载下一页同时显示一个“正在加载更多”的底部View。这里有个细节当加载完成后数据为空时要显示“没有更多了”而不是继续无限请求。我的处理方式是当返回的list.size() pageSize时判定为没有更多数据把底部的状态切换到“没有更多了”。类似这种列表页面的加载状态Android上有StatefulLayout开源库可以直接用——加载中、空数据、错误、正常内容四种状态。手动写虽然也就几十行但状态切换极容易漏——边界情况一多最容易出Bug。用现成的库不丢人。4.4 协调布局Banner首页聚合展示的实现细节首页不建议做成一堆文字列表硬堆。我的方案是CoordinatorLayout AppBarLayout 顶部Banner轮播图配合TabLayout切换多个Tab页最新岗位、热门企业、公告整体观感清爽信息层次也清晰。Banner轮播图这里有两个容易踩的坑第一图片加载用Glide不要在RecyclerView的Adapter里直接用Glide.with(context).load(url).into(holder.imageView)完事——要处理图片加载失败时的占位图和圆角裁剪。占位图很重要否则弱网环境下图片加载慢用户盯着空白卡片体验很差。第二轮播图自动轮播的定时器用Handler.postDelayed实现时一定记得在onPause生命周期中移除回调否则页面不可见时还在切图不仅浪费资源还有可能引发内存泄漏。4.5 客户端缓存与离线数据保留勤工助学系统的学生用户经常在宿舍、教室、食堂不同WiFi环境下切换网络并不是时刻稳定。我的方案是给岗位列表和公告列表做本地缓存用SQLiteRoom存最近一次拉取的数据页面加载时先读缓存展示再发网络请求更新用SharedPreferences存用户的登录状态和基本信息用File存储下载的附件Excel工资表、岗位说明书等这个设计在弱网环境下效果显著实测下来页面打开速度从“转圈3秒”变成“先展示再刷新”体感流畅很多。4.6 拍照、选择图片与文件Uri适配学生申请岗位时往往需要提交证明材料——比如家庭经济困难证明的拍照件。Android端需要调用相机或相册这里在Android 7.0必须用FileProvider否则拍完照直接闪退。FileProvider配置示例在AndroidManifest里注册provider在xml里配置pathsprovider android:nameandroidx.core.content.FileProvider android:authorities${applicationId}.fileprovider android:exportedfalse android:grantUriPermissionstrue meta-data android:nameandroid.support.FILE_PROVIDER_PATHS android:resourcexml/file_paths / /provider注意authorities的值要写${applicationId}.fileprovider不同渠道包会自动替换成对应的包名。有同学在这里踩过坑包名改了没有同步改provider配置导致相册选图后闪退。另外从相册选择图片时不同机型返回的Uri格式不一样——有的返回content://media/...有的返回content://com.tencent.wework.fileprovider/...。写兼容代码时要统一走ContentResolver读取流不要直接拼文件路径否则老版本的Android会直接崩。4.7 Android项目移植与自定义混淆配置Android项目在不同机器上协作开发的时候最怕Gradle版本不一致导致的构建问题。搜索“移植android studio项目”时常见的问题场景是从GitLab拉下来代码sync的时候Gradle版本对不上或者SDK版本不对。我的建议是项目里的gradle-wrapper.properties指定Gradle版本distributionUrlbuild.gradle里指定SDK编译版本这两处确认清楚基本不会有大问题。关于混淆——如果要发布Release包ProGuard或R8的混淆规则要配置好。有两个关键点第一混淆字典设置不了时检查build.gradle里是否配置了proguardFiles和optimizationPasses。如果自定义-obfuscationdictionary失效多半是因为你的minifyEnabled没有开——这就是搜索“android自定义混淆字典无效”这个问题的标准答案。第二要保留一些类和成员的映射关系——凡是Retrofit的接口、数据Bean模型类、使用了Gson的字段都要加Keep注解或配置keep规则否则Release包运行时反射拿不到字段直接抛异常。写完代码千万别忘了跑一遍Release包的回归测试——我第一版混淆规则漏了模型类的keep发布后学生端登录就闪退排查了一天才发现是Gson反序列化所有字段变成了null。5. 部署上线与项目管理实战5.1 数据库脚本与初始化数据处理上线前有一件事特别容易漏初始化数据准备不充分。勤工助学系统至少需要以下初始数据管理员账号默认1个学院列表用于学生注册时选择岗位类型字典家教类、行政助理类、实验室助理类、图书管理员类等薪资标准字典按小时计价或按月计价这些数据的插入脚本建议写在schema.sql的后面一段用INSERT INTO语句。不要跟我一样系统快上线了才想起来没有学院基础数据注册页面下拉框空空如也。5.2 SpringBoot的Docker化部署后端服务我最后用Docker部署在了学校的服务器上。写一个DockerfileFROM openjdk:8-jre-alpine WORKDIR /app COPY target/hardwork-system.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar, --spring.profiles.activeprod]这里有一个我踩过的大坑SpringBoot的配置文件application.yml不要打进镜像里用-v挂载外部配置文件。这样后续调整数据库连接、调整端口不需要重新构建镜像直接改宿主机的配置文件再重启容器就行。Docker启动命令docker run -d --name hardwork-server \ -p 8080:8080 \ -v /opt/hardwork/config/application.yml:/app/application.yml \ -v /opt/hardwork/logs:/app/logs \ --restartalways \ hardwork-system:1.0.0这样部署的优势是回滚快旧版本镜像还在资源隔离好迁移服务器只要把镜像导过去就行。5.3 Android APK的持续构建与签名Android客户端发布时直接导出Release APK签名用的是JKS文件。签名这事要特别注意项目里要有一个keystore.properties文件保存签名信息并且这个文件必须从版本库里排除加进.gitignore。我见过有同学把签名文件和密码直接推到GitHub公共仓库等于把自家大门的钥匙挂在了大街上。输出路径建议直接命名成hardwork-system-v1.0.0-release.apk并在文件名里带版本号。打出来的包交给学院信息中心的老师安装测试之前先在模拟器上跑一遍最小回归登录→刷列表→看详情→提交申请→退出登录确保主流程没有明显问题。5.4 日志、监控与后期运维上线不是终点运维才是重头戏。这里分享一下我的实操配置日志框架用Logback输出到文件按天滚动。关键日志要分三个文件error.log只有ERROR级别报警用biz.log业务操作日志谁在什么时候申请了什么岗位access.log接口访问日志哪个IP在什么时间调用了哪些接口监控方面SpringBoot接入Spring Boot Admin能够直观看到内存、线程、HTTP接口的调用情况。小型项目不需要搞什么链路追踪和Prometheus那套扛不住也不需要。学生用户反馈的问题里最常见的是“App更新了但手机没收到新版本提示”。这里建议做一个简单的版本更新机制后端维护一个版本号接口客户端启动时拉取对比不一致时弹窗提示去下载新APK。有了这个再也不用靠辅导员在群里发安装包链接了。6. 常见问题排查与操作细节记录6.1 后端高频问题速查问题现象可能原因解决方案前端请求一直转圈日志无输出端口未开放/防火墙拦截检查服务器防火墙和安全组规则放行8080端口接口报错“Bad SQL Grammar”SQL语法错误或字段名不匹配打开MyBatis-Plus的SQL日志输出检查生成的SQL语句数据库连接超时MySQL连接被空闲回收配置HikariCP的connection-timeout和max-lifetime参数定时任务不执行未开启EnableScheduling检查启动类是否添加了EnableScheduling注解中文乱码数据库连接URL缺少字符集参数JDBC URL加上characterEncodingutf8跨域请求被拦截CORS配置缺失全局配置CorsFilter允许指定来源这里重点说说“接口请求404但Controller存在”这个诡异问题。有一次我后端接口明明写在Controller里但Android端一直请求404排查了两个小时发现是路径映射问题——Controller类上的RequestMapping写的是/api/admin方法上的PostMapping写的是/createPosition但前端Retrofit里拼接的URL多了一个斜杠变成了/api/admin//createPositionSpringBoot把双斜杠路径认为是不存在的资源直接404。解决方案统一在Retrofit的BaseUrl末尾不加斜杠接口注解里路径开头也不加斜杠两边保持一致并且用工具类做路径拼接检查。6.2 Android端高频问题速查问题现象可能原因解决方案Android Studio构建卡在Gradle syncGradle版本不匹配/网络问题检查gradle-wrapper.properties换成稳定版本配置阿里云镜像仓库相机拍照后闪退没有配置FileProviderAndroidManifest里注册FileProvider按上面示例配置xml路径请求一直报401Token过期/Token未携带检查拦截器是否在请求头中加了Authorization字段图片加载不出来后台返回的图片地址是局域网IP外网不可达部署时把图片域名配置成正式域名客户端BaseUrl统一切换点击“加载更多”没反应pageNum和pageSize参数传递有误检查请求参数是否每页都传了正确的页码返回数据是否为空列表时需要终止加载6.3 打Release包后一些“反直觉”的坑Release包和Debug包行为不一样这是Android开发的经典现象。具体到本项目第一个坑网络请求在Release包下全部失败。原因是Android 9.0API 28开始默认禁止明文HTTP流量。Debug包还能访问Release包直接拦截。解决方案在AndroidManifest.xml里声明usesCleartextTraffictrue或者在网络安全配置里单独允许某个域名使用明文HTTP。我当时就是因为这个发布出去的包在真机上一整个模块都打不开后来查了半天的logcat才定位到是网络安全策略的问题。第二个坑混淆把数据类字段名改了。Gson的反序列化靠反射读取字段名如果你的数据Bean没有配置SerializedName注解混淆之后字段名全变了JSON解析全部失败。此事无解只能靠KEEP规则解决——配置文件里把model包下的所有类keep住。第三个坑日志系统级别在Release包默认是INFO以上你Debug时输出的日志在Release包里全看不见。遇到线上问题排查时要么把日志级别调到DEBUG不推荐性能损耗要么靠Upload后的崩溃日志定位Crash位置。6.4 涉及移动端文件访问的“恐怖”问题搜索热词里那个content://com.tencent.wework.fileprovider/external_path/android/data/com...的报错大家如果正在做“用App读取下载目录或者分享文件”的功能大概率会遇到。这个问题本质上是Android 11API 30的包可见性和数据目录访问限制导致的。当你在App里使用系统的文件选择器选择文件时有些第三方的文件选择器会向你返回一个content://格式的Uri但这个Uri的来源是其他应用比如企业微信、QQ的内容提供者。你拿着这个Uri去访问文件时如果没有对应的授权或你的App没有权限访问该ContentProvider就会直接报SecurityException。解决思路很简单不要自己去解析Uri获取文件路径而是统一用ContentResolver.openInputStream(uri)读取流。同时把清单文件里queries标签加上对常见文件选择器的声明或者直接申请系统全部文件的访问权限MANAGE_EXTERNAL_STORAGE但是这个权限审核很严格慎用。7. 写在最后的个人经验总结这个项目从头到尾做下来我最大的收获是技术选型永远不要赶时髦能解决问题、能稳定运行、能维护得了的技术栈才是好技术栈。SpringBoot 2.7 Android原生Java MySQL 8.0这套组合放到今天依然非常扎实。另外分享几个实际运行中摸索出来的细节经验供你们参考。第一勤工助学系统的工资结算模块一定要做成“可分账期、可追溯、可重跑”的设计。学校财务老师会反复核对数据如果结算数据不能重新生成后续调整岗位单价或者补录工时时会很被动。第二Android端的全局异常处理要做好。网络请求的回调里只要是失败状态——业务码非200、HTTP错误、解析异常——都要有兜底逻辑否则用户看到的不是“数据加载失败请下拉重试”而是“App已停止运行”这对口碑的伤害是致命的。第三代码写好之后一定要抽时间把项目的架构图和数据库ER图整理出来。这不仅是毕业答辩的必备材料也是后续你自己接手维护的最大捷径。我后来看自己写过的代码经常需要先看图再进代码不然真的会迷路。最后关于勤工助学系统后续如果想继续扩展可以考虑的方向有微信公众号对接通知、钉钉工作通知、数据大屏实时展示统计结果、移动端人脸识别打卡以及Excel工资表自动导出。骨架搭好了扩展只是时间和精力的问题。以上是我的全部实操记录希望对正在做这个项目或者类似管理系统的人有帮助。如果你们在过程中遇到代码层面说不清楚的问题欢迎在评论区聊。
返回列表