听说你毕业很多年了?那么来做题吧🦶

序言

自别学宫,岁月如狗,撒腿狂奔,不知昔日学渣今何在?

左持键盘,右捏鼠标,微仰其首,竟在屏幕镜中显容颜!

心中微叹,曾几何时,提笔杀题,犹如天上人间太岁神。

知你想念,故此今日,鄙人不才,出题小侠登场献丑了。

起因

在这一篇《从 0 到上架:用 Flutter 一天做一款功德木鱼》文章中,我的 木鱼APP 最终陨落了,究其原因就是这种 APP 在  商店中太,如果你要想成功上架,无异于要脱胎换骨。

后面有时间了,我打算将其重铸为 修仙敲木鱼,通过积攒鱼力,突破秩序枷锁,成就 无上木鱼大道


因此,我吸取失败的教训,着力于开发一款比较 独特的APP ,结合这个AI大时代的背景,这款AI智能 出题侠 就应运而生了。最后总算是不辜负我的努力,成功上架了。

接下来就向大家说说 它的故事 吧。

实践

一. 准备阶段

1.流程设计

flowchart TD %% 启动与登录 A[启动页] --> B[无感登录] B --> C[进入导航页] %% 主壳导航 C --> H[首页] C --> R[记录] C --> T[统计] C --> P[我的] %% 首页出题 → 记录 H --> H1[输入主题/高级设置] H1 --> H2[生成题目] H2 --> H3[提示后台生成] H3 --> R %% 记录 → 答题/详情 R --> R1{记录状态} R1 -->|进行中| Q[进入答题页] R1 -->|已完成| RD[记录详情] RD --> E[秒懂百科] %% 答题流程 Q --> Q1[作答 / 提交] Q1 --> Q2[保存成绩] Q2 --> R %% 统计页 T --> T1[刷新统计数据] %% 我的页 P --> P1[设置/关于] P --> P2[隐私政策] P --> P3[注销] P3 --> |确认后| P4[清除 token / 返回未登录状态]

2. 素材获取

App的 logo 和其中的 插图 ,我都是用的 Doubao-Seedream-4.0 生成的,一次效果不行就多生成几次,最终还是能得到相对满意的结果。

到我写文章的时候,已经有了 Doubao-Seedream-4.5,大家可以去体验体验。

二. 开发阶段

1. 前端

前端毫无争议的使用的是 Flutter,毕竟要是以后发行 Android 也是非常方便的,无需重新开发。再结合 Trae,我只需要在口头上指点指点,那是开发的又快又稳,非常的轻松加愉快。

无须多言,这就是赛博口嗨程序员!🫡

2. 后端

后端就是,世界上最好的编程语言 JAVA 了,毕竟 SpringBoot 可太香了,我也是亲自上手。

2.1 依赖概览
xml 复制代码
<!-- ✅ 核心 LangChain4j 依赖 -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j</artifactId>
    <version>${langchain4j.version}</version>
</dependency>

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
   <version>4.12.0</version>
</dependency>

<!-- Validation -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!-- Spring Aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- HuTool -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>${hutool.version}</version>
</dependency>

<!-- MyBatis-Plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>${mybatis-plus.version}</version>
</dependency>

<!-- MyBatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>${mybatis-spring-boot-starter.version}</version>
</dependency>

<!-- MyBatis-PageHelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>${pagehelper.version}</version>
</dependency>

<!-- Sa-Token -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot3-starter</artifactId>
    <version>${sa-token.version}</version>
</dependency>

<!-- Sa-Token 整合 RedisTemplate -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-redis-template</artifactId>
    <version>1.42.0</version>
</dependency>

<!-- 提供 Redis 连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

<!-- Lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

<!-- Knife4j -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.8.6</version>
</dependency>
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
    <version>4.4.0</version>
</dependency>

<!-- 短信验证码 -->
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-auth</artifactId>
    <version>0.2.0-beta</version>
</dependency>

<!-- 阿里云短信服务 SDK -->
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>dysmsapi20170525</artifactId>
    <version>2.0.24</version> <!-- 使用最新版 -->
</dependency>

      ......

这依赖一添加,满满的安全感:

  • 数据库:我有MyBatis。
  • AI:我有LangChain4j。
  • 登录鉴权:我有Sa-Token。
  • ......
2.2 接口限流

要说到项目中最需要重点关注的部分,接口限流 无疑排在首位。无论是短信发送接口,还是调用 AI 的接口,一旦被恶意刷取或滥用,都可能导致资源耗尽、费用爆炸💥。

因此,本项目采用 注解 + AOP + Redis 的方式,构建了一套 轻量级、可配置、低侵入 的接口限流方案,在不影响业务代码结构的前提下,对高风险接口进行有效保护,确保系统在高并发场景下依然稳定可控。


代码示例:

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {

    /**
     * 限流 key 的前缀(唯一标识一个限流维度)
     */
    String key();

    /**
     * 时间窗口,单位秒
     */
    long window() default 60;

    /**
     * 时间窗口内允许的最大次数
     */
    int limit() default 10;

    /**
     * 是否按 IP 维度区分限流
     */
    boolean perIp() default false;

    /**
     * 是否按用户维度区分限流
     */
    boolean perUser() default false;

    /**
     * 自定义提示信息
     */
    String message() default "请求过于频繁,请稍后再试";
}
java 复制代码
@Slf4j
@Aspect
@Component
public class RateLimitAspect {

    @Resource
    private RateLimitRedisUtil rateLimitRedisUtil;

    @Around("@annotation(org.dxs.problemman.annotation.RateLimit)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RateLimit rateLimit = method.getAnnotation(RateLimit.class);

        String key = buildKey(rateLimit);

        boolean allowed = rateLimitRedisUtil.tryAcquire(
                key, rateLimit.limit(), rateLimit.window());

        if (!allowed) {
            log.warn("限流触发:key={}, limit={}, window={}s", key, rateLimit.limit(), rateLimit.window());
            throw new RateLimitException(rateLimit.message());
        }

        return joinPoint.proceed();
    }

    private String buildKey(RateLimit rateLimit) {
        StringBuilder key = new StringBuilder("ratelimit:").append(rateLimit.key());
        if (rateLimit.perIp()) {
            String ip = IpUtils.getIpAddress();
            key.append(":").append(ip);
        }

        if (rateLimit.perUser()) {
            String userId = StpUtil.getLoginIdAsString();
            key.append(":").append(userId);
        }
        return key.toString();
    }
}

在使用上,开发者只需在需要保护的接口方法上添加 @RateLimit 注解,即可声明该接口的限流规则。通过 key 区分不同业务场景,并可按需开启 IP 维度用户维度 的限流控制,从而精确限制单一来源或单一用户的访问频率。

java 复制代码
@NotLogin
@PostMapping("/sms")
@RateLimit(key = "sms", limit = 200, window = 3600, message = "短信调用太频繁,请1小时后再试")
public AjaxResult<String> sms(@Validated @RequestBody PhoneDTO dto) {
    return AjaxResult.success(loginService.sms(dto));

}
java 复制代码
@PostMapping("/generate")
@RateLimit(key = "generate", limit = 3, perUser = true, window = 3600*24, message = "每人每天仅可体验三次!")
@Operation(summary = "依据条件,生成题目")
public AjaxResult<Object> generate(@Validated @RequestBody GenerateRequestDTO dto) throws IOException, InterruptedException {
    questionService.generate(dto);
    return AjaxResult.success();
}

请求进入时,AOP 切面会拦截带有 @RateLimit 注解的方法,根据注解配置动态构建限流 Key,并交由 Redis 进行原子计数校验;若在指定时间窗口内超过访问上限,则直接中断请求并返回友好的限流提示,同时记录告警日志,便于后续排查与监控。

限流 Key 的结构统一为:

css 复制代码
ratelimit:{业务key}:{ip}:{userId}

通过 Redis 过期机制自然形成时间窗口,既保证了并发场景下的准确性,也避免了额外的清理成本。

三. 上架备案

1.前提

想要备案上架,域名和服务器是必不可少的。

  • 域名:你是在手机上,其实不需要啥好域名,因为大家根本看不见,十几块钱一年就行了。
  • 服务器:花了三四百买个轻量级服务器就行了。

2. 阿里云备案

💡 小建议

像这里阿里云备案和获取管局审核,可以先行一步,在app开发完之前就可以提交了。

因为管局审核是要2-3周的,有可能我们的小APP开发好了,备案号都没有下来。

3. 苹果商店上架

信息这里按部就班,按照提示,一点点填写完成就行了,没啥特别的。

踩坑总结

  • 测试账号:你的APP中只要有登录模块,就一定要提供测试账号,就算你纯手机号登录也不行,必须提供测试账号。
  • 注销功能 :苹果商店硬性要求,必须要有 注销功能,但其实也没那么严格,你只要UI显示是那么回事就行,就当 退出登录 功能去做就行了。

4. 预览图制作推荐

注意是需要订阅付费的,要是有什么更好的,希望评论告知。😂

展望

在后续规划的新功能中,将以大学期末考试复习作为典型应用场景进行设计。通常在期末阶段,老师都会给出明确的考试范围、复习大纲以及相关资料文档,而临阵磨枪的学生往往面临资料繁多、重点分散、不知从何下手的问题。

针对这一痛点,用户可以将老师提供的复习文档直接导入 App,系统会基于 AI 对内容进行自动解析与归纳,将零散的文本信息整理为思维导图形式的知识图谱,清晰呈现各章节与知识点之间的层级与关联关系。

在此基础上,用户可围绕任意知识节点一键生成对应题目,用于针对性复习与自测,做到哪里薄弱练哪里 。通过文档 → 知识图谱 → 题目练习 的闭环方式,帮助用户更高效地理解重点内容,提升期末复习的针对性与整体效率。

😭 作为大学毕业生的深彻感悟。

支持

AppStore 搜索 出题侠 即可,每个用户每天可免费使用三次。

感谢大家的支持与反馈。🙏

相关推荐
neuHenry2 小时前
探索 Flutter 事件机制
flutter
程序员老刘2 小时前
Flutter凉不了:它是Google年入3000亿美元的胶水
flutter·google·客户端
明君879973 小时前
Flutter横向树形选择器实现方案
android·ios
CrazyQ13 小时前
flutter_easy_refresh在3.38.3配合NestedScrollView的注意要点。
android·flutter·dart
爱吃大芒果3 小时前
从零开始学 Flutter:状态管理入门之 setState 与 Provider
开发语言·javascript·flutter
无痕melody4 小时前
苹果ios手机ipad安装配置ish终端shell工具
ios·智能手机·ipad
庄雨山4 小时前
Flutter+开源鸿蒙实战:cached_network_image 图片加载体验优化全指南
flutter·openharmonyos
mike10235 小时前
swiftUI状态管理
ios·swiftui
克喵的水银蛇6 小时前
Flutter 通用标签组件:TagWidget 一键实现多风格标签展示
flutter