企业解决方案十一-各类小程序定制开发
│ 微信小程序客户端 ││▼ HTTPS│ Nginx反向代理 ││ (SSL证书 + 负载均衡) ││▼│ Spring Boot 后端服务 ││ │ Controller层 │ Service层 │ Mapper层 │ ││ │ JWT拦截器 │ 异常处理器 │ 配置类 │ ││ │ │▼ ▼ ▼│ MySQL │ │ Redis │ │ 阿里云OSS ││ 数据库服务 │ │ 缓存服务 │
随着人工智能技术的快速发展,AI图像处理已成为移动互联网领域的热门应用方向。本文将详细介绍一个基于Spring Boot后端框架和uni-app前端框架开发的AI图像处理小程序从架构设计到功能实现的全过程。文章涵盖了Spring Boot 2.7微服务架构、MyBatis-Plus ORM框架集成、JWT无状态认证机制、阿里云OSS文件存储服务、阿里云通义万相AI图像处理接口对接、uni-app跨平台开发等核心技术的实际应用。通过本文的学习,读者可以掌握构建一套完整的"AI图像处理小程序"技术方案,同时了解小程序定制开发的相关服务。
关键词:Spring Boot;uni-app;MyBatis-Plus;JWT认证;阿里云OSS;AI图像处理;微信小程序;定制开发
一、项目背景与需求分析
1.1 项目背景
在当今数字化时代,图像处理需求呈现出爆发式增长态势。从社交媒体用户对照片美化的需求,到电商平台对商品图片批量处理的需求,再到创意工作者对AI辅助设计工具的渴望,AI图像处理技术正在深刻改变着我们的工作和生活方式。特别是在微信小程序生态中,轻量化、即用即走的AI图像处理工具受到了广大用户的热烈欢迎。
本文所介绍的项目正是在这样的背景下诞生的。该项目是一款专注于AI图像处理的微信小程序,用户可以通过该小程序上传图片,并结合文本描述对图片进行智能编辑和创作。小程序后端采用成熟的Spring Boot框架构建,前端则采用支持多端运行的uni-app框架开发,实现了高性能、高可用、易扩展的技术架构。
1.2 核心功能需求
通过对市场需求的深入调研和分析,项目确定了以下核心功能需求:
用户管理模块是整个系统的基础模块。系统采用邮箱验证码的登录方式,用户无需注册复杂的账号密码,只需输入邮箱地址即可快速完成登录。首次登录的用户会自动创建账号,并获得一定次数的免费使用额度。用户可以在个人中心查看自己的使用记录、剩余次数,并进行联系客服等操作。
图片上传模块负责处理用户上传的图片文件。系统需要支持常见的图片格式,包括JPG、PNG等,同时对上传文件的大小进行限制,确保服务器资源的高效利用。系统还实现了基于MD5值的文件去重功能,对于相同内容的图片可以避免重复存储,有效节约阿里云OSS的存储成本。
AI图像处理模块是项目的核心功能模块。系统对接了阿里云通义万相AI服务,用户可以通过输入文字描述来指导AI对图片进行修改和创作。例如,用户可以输入"将背景替换为蓝天白云"、"添加卡通特效"等描述,AI模型会根据描述对图片进行智能处理,并返回处理后的结果图片。
文件管理模块提供了完整的文件生命周期管理功能。用户可以查看自己上传的所有图片文件,可以对文件进行重命名、删除等操作。系统采用软删除机制,删除的文件会标记为已删除状态,便于后续的数据恢复和审计。
1.3 技术选型原则
在技术选型过程中,项目团队遵循了以下核心原则:
成熟稳定优先:选择经过大量项目验证的技术框架和组件,确保系统的稳定性和可靠性。Spring Boot作为Java生态中最成熟的微服务框架之一,拥有活跃的社区支持和丰富的生态组件,非常适合作为企业级后端服务的开发框架。
开发效率至上:充分利用框架提供的自动化配置和开箱即用的功能,减少重复代码的编写。MyBatis-Plus作为MyBatis的增强框架,提供了丰富的CRUD操作封装和分页插件,可以显著提升数据库操作的开发效率。
跨平台兼容:前端采用uni-app框架,一套代码可以编译输出到微信小程序、H5页面、iOS App、Android App等多个平台,最大化代码复用价值。
云服务集成:充分利用阿里云提供的云服务能力,包括OSS对象存储服务、通义万相AI服务等,快速构建可扩展的云端应用。
二、系统架构设计
2.1 整体架构概述
系统采用经典的MVC三层架构设计,整体架构图如下所示:
┌─────────────────────────────────────────────────────────────────┐
│ 微信小程序客户端 │
│ (uni-app + Vue 2) │
└─────────────────────────────────────────────────────────────────┘
│
▼ HTTPS
┌─────────────────────────────────────────────────────────────────┐
│ Nginx反向代理 │
│ (SSL证书 + 负载均衡) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Spring Boot 后端服务 │
│ ┌──────────────┬──────────────┬──────────────┐ │
│ │ Controller层 │ Service层 │ Mapper层 │ │
│ └──────────────┴──────────────┴──────────────┘ │
│ ┌──────────────┬──────────────┬──────────────┐ │
│ │ JWT拦截器 │ 异常处理器 │ 配置类 │ │
│ └──────────────┴──────────────┴──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ MySQL │ │ Redis │ │ 阿里云OSS │
│ 数据库服务 │ │ 缓存服务 │ │ 文件存储 │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ 通义万相API │
│ AI图像处理 │
└─────────────┘
2.2 后端技术架构
后端服务基于Spring Boot 2.7.9版本构建,采用Maven进行项目依赖管理。以下是pom.xml中的核心依赖配置:
Spring Boot核心框架是整个后端服务的基础。项目采用Spring Boot 2.7.9版本,这是一个长期支持版本(LTS),具有以下技术特性:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.9</version>
<relativePath/>
</parent>
Web开发支持通过spring-boot-starter-web依赖引入,该依赖包含了Spring MVC、Tomcat嵌入式服务器等核心组件,是构建RESTful API服务的基础。
数据库访问层采用MyBatis-Plus 3.5.1版本,这是一个强大的MyBatis增强工具包。MyBatis-Plus在MyBatis的基础上提供了以下增强功能:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
首先,MyBatis-Plus内置了通用的Mapper接口,开发者无需编写基本的CRUD SQL语句,通过继承BaseMapper接口即可获得增删改查的完整能力。其次,MyBatis-Plus提供了强大的分页插件,只需简单配置即可实现高性能的分页查询。此外,MyBatis-Plus还支持条件构造器、主动查询等功能,可以优雅地构建复杂的SQL查询语句。
身份认证采用JWT(JSON Web Token)技术,使用com.auth0:java-jwt:3.10.3依赖实现:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
JWT是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。JWT由三部分组成:Header(头部)、Payload(负载)和Signature(签名)。相比传统的Session认证方式,JWT具有无状态、可扩展、支持跨域等优点,非常适合分布式系统和移动端应用。
云服务集成方面,项目集成了阿里云OSS文件存储服务:
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.16.1</version>
</dependency>
阿里云OSS(Object Storage Service)是阿里云提供的海量、安全、低成本的云存储服务。通过OSS,开发者可以存储和访问任意类型的文件,非常适合用于存储用户上传的图片文件。
AI服务对接方面,项目通过HTTP请求调用阿里云通义万相API:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
通义万相是阿里云推出的大模型服务,提供了图像生成、图像编辑等多种AI能力。项目通过HTTP请求与通义万相API进行交互,实现AI图像处理功能。
2.3 前端技术架构
前端采用uni-app框架开发,这是一个基于Vue.js的跨平台应用开发框架。uni-app的核心优势在于"一套代码,多端发布",开发者只需编写一套Vue代码,即可发布到iOS、Android、H5、以及各种小程序平台。
项目采用Vue 2作为前端框架,主要原因如下:
第一,Vue 2拥有成熟的生态系统,社区资源丰富,技术文档完善,开发过程中遇到的问题可以很容易地找到解决方案。
第二,uni-app对Vue 2的支持最为完善,组件库和插件生态最为丰富,可以满足各种复杂的业务需求。
第三,相比Vue 3,Vue 2在微信小程序环境下的兼容性和性能表现更为稳定。
前端项目的核心依赖包括:
uni-ui组件库:这是DCloud官方提供的 uni-app 组件库,包含了表单组件(uni-forms、uni-easyinput)、图标组件(uni-icons)、弹窗组件(uni-popup)、过渡动画组件(uni-transition)等常用组件。这些组件针对移动端场景进行了优化,具有良好的用户体验和性能表现。
uni-file-picker组件:用于实现图片选择功能,支持从相册选择或拍照获取图片,并支持图片压缩处理。
axios封装:项目对uni.request进行了Promise封装,提供了统一的请求拦截、响应处理、错误处理等功能。
2.4 数据库设计
系统采用MySQL 5.7作为关系型数据库,根据业务需求设计了以下核心数据表:
用户表(user)是系统的核心实体表之一,主要字段设计如下:
| 字段名 | 数据类型 | 说明 |
|---|---|---|
| id | INT | 主键,自增 |
| VARCHAR(255) | 用户邮箱,唯一标识 | |
| code | VARCHAR(255) | 邮箱验证码 |
| send_time | DATETIME | 验证码发送时间 |
| avatar | VARCHAR(255) | 用户头像URL |
| balance | VARCHAR(255) | 用户剩余次数 |
| use_time | DATETIME | 上一次使用时间 |
| login_time | DATETIME | 上次成功登录时间 |
| password | VARCHAR(255) | 密码(备用字段) |
用户表的设计考虑了以下因素:邮箱作为用户的唯一标识,相比手机号更加轻量化,适合不需要强制实名认证的场景;balance字段采用VARCHAR类型存储用户剩余次数,便于灵活调整;use_time和login_time字段用于记录用户行为,便于后续的用户画像和运营分析。
文档表(document)用于存储用户上传的文件信息:
| 字段名 | 数据类型 | 说明 |
|---|---|---|
| id | INT | 主键,自增 |
| name | VARCHAR(255) | 文件名 |
| type | VARCHAR(255) | 文件类型/扩展名 |
| size | BIGINT | 文件大小,单位KB |
| preview | VARCHAR(255) | 预览链接 |
| url | VARCHAR(255) | 文件存储URL |
| remove | BOOLEAN | 是否删除(软删除标记) |
| enable | BOOLEAN | 是否启用 |
| md5 | VARCHAR(255) | 文件MD5值(用于去重) |
文档表的设计采用了软删除机制,删除操作只是将remove字段标记为true,而不是真正从数据库中删除记录。这种设计可以保留数据便于审计和恢复,同时也不会影响正常的业务查询。
MD5字段的设计实现了文件去重功能。当用户上传文件时,系统会计算文件的MD5值并存储到数据库中。如果数据库中已存在相同MD5的记录,说明该文件内容已被存储过,系统可以直接返回已有文件的URL,避免重复上传和存储。
三、后端核心功能实现
3.1 JWT无状态认证机制
用户身份认证是系统安全的基础模块。项目采用JWT实现无状态认证,用户登录成功后服务器会生成一个Token返回给客户端,后续客户端的请求都需要携带这个Token进行身份验证。
Token生成是认证流程的第一步。在用户登录验证成功后,系统会调用Token工具类生成JWT Token:
public class Token {
/**
* 生成用户Token
* @param user 用户实体
* @return JWT Token字符串
*/
public static String createToken(User user) {
// 创建Token过期时间,设置为8小时后过期
Date expireDate = DateUtil.offsetHour(new Date(), 8);
// 使用HMAC256算法,以用户密码作为密钥进行签名
// 这样即使Token被截获,没有密码也无法伪造有效Token
return JWT.create()
.withAudience(String.valueOf(user.getId())) // 设置Token载荷(用户ID)
.withExpiresAt(expireDate) // 设置过期时间
.sign(Algorithm.HMAC256(user.getPassword())); // 使用密码签名
}
}
上述代码的关键设计点在于使用用户密码作为JWT签名的密钥。这种设计的好处是:即使攻击者截获了Token,由于不知道用户密码,也无法伪造有效的Token。同时,当用户修改密码后,原有的Token将自动失效,进一步提高了安全性。
Token验证是通过Spring MVC拦截器实现的。系统在WebMvcConfig中注册了JWTInterceptor拦截器,对需要认证的请求进行Token验证:
@Component
public class JWTInterceptor implements HandlerInterceptor {
@Autowired
private UserMapper userMapper;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 从请求Header中获取Token
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)) {
response.setStatus(401);
return false;
}
try {
// 验证Token有效性和完整性
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
// 获取Token中存储的用户ID
String userId = decodedJWT.getAudience().get(0);
// 查询用户是否存在
User user = userMapper.selectById(userId);
if (user == null) {
response.setStatus(401);
return false;
}
// 使用用户密码重新验证Token签名
// 这是防止Token被篡改的关键步骤
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
verifier.verify(token);
// 将用户信息存入请求属性,供后续处理使用
request.setAttribute("user", user);
return true;
} catch (JWTVerificationException e) {
response.setStatus(401);
return false;
}
}
}
拦截器的验证逻辑包含三个层次:首先验证Token的基本格式和有效性;然后验证用户是否存在;最后使用用户密码重新验证Token签名,确保Token没有被篡改。只有通过全部验证,请求才会被放行。
统一响应封装是RESTful API设计的重要规范。系统定义了Res工具类来统一API的响应格式:
public class Res {
// 成功响应,携带数据
public static Map<String, Object> ok(Object object) {
Map<String, Object> result = new HashMap<>();
result.put("code", "200");
result.put("message", "操作成功");
result.put("object", object);
return result;
}
// 成功响应,无数据
public static Map<String, Object> ok() {
return ok(null);
}
// 失败响应
public static Map<String, Object> error(String message) {
Map<String, Object> result = new HashMap<>();
result.put("code", "500");
result.put("message", message);
return result;
}
// 自定义状态码响应
public static Map<String, Object> result(String code, String message, Object object) {
Map<String, Object> result = new HashMap<>();
result.put("code", code);
result.put("message", message);
result.put("object", object);
return result;
}
}
这种统一的响应格式使得前端可以方便地进行统一处理,同时也便于进行接口监控和日志记录。
3.2 邮箱验证码登录流程
邮箱验证码登录是一种轻量级且安全的认证方式,特别适合小程序场景。用户无需记忆复杂的密码,只需拥有一个邮箱即可完成登录。
验证码发送接口的实现如下:
@PostMapping("email_code")
public Res sendEmailCode(@RequestParam("email") String email) {
// 校验邮箱格式
if (!email.matches("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$")) {
return Res.error("邮箱格式不正确");
}
// 生成4位数字验证码
String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000));
// 发送邮件(使用Hutool工具类)
try {
MailUtil.send(email, "您的验证码", "您的验证码是:" + code + ",5分钟内有效。");
} catch (Exception e) {
return Res.error("邮件发送失败,请检查邮箱地址");
}
// 将验证码存入数据库,设置5分钟有效期
User user = userMapper.selectOne(new QueryWrapper<User>().eq("email", email));
if (user == null) {
// 新用户:创建账号
user = new User();
user.setEmail(email);
user.setCode(code);
user.setSendTime(new Date());
user.setAvatar(Constants.DEFAULT_AVATAR);
user.setBalance(Constants.DEFAULT_BALANCE);
user.setPassword("123"); // 默认密码
userMapper.insert(user);
} else {
// 老用户:更新验证码
user.setCode(code);
user.setSendTime(new Date());
userMapper.updateById(user);
}
return Res.ok("验证码已发送");
}
验证码的有效期控制在5分钟内,通过send_time字段记录发送时间,后续验证时检查时间差即可。验证码存储在数据库中而非Session中,支持分布式部署场景。
登录验证接口的实现如下:
@PostMapping("login")
public Res login(@RequestParam("email") String email,
@RequestParam("code") String code) {
// 查询用户
User user = userMapper.selectOne(new QueryWrapper<User>().eq("email", email));
if (user == null) {
return Res.error("用户不存在");
}
// 检查验证码是否正确
if (!code.equals(user.getCode())) {
return Res.error("验证码错误");
}
// 检查验证码是否过期(5分钟)
long diff = System.currentTimeMillis() - user.getSendTime().getTime();
if (diff > 5 * 60 * 1000) {
return Res.error("验证码已过期");
}
// 生成Token
String token = Token.createToken(user);
// 更新登录时间
user.setLoginTime(new Date());
userMapper.updateById(user);
// 返回用户信息和Token
user.setToken(token);
user.setCode(null); // 不返回验证码
return Res.ok(user);
}
登录成功后,系统会清除用户表中的验证码字段,防止验证码被重复使用。同时记录login_time便于追踪用户登录行为。
3.3 AI图像处理集成
AI图像处理是项目的核心功能模块。系统对接了阿里云通义万相API,实现了图片的智能编辑和创作功能。
异步任务处理机制是AI图像处理的核心设计。AI图像生成是一个耗时操作,无法在同步请求中完成,因此采用了异步轮询的方案:
public class DealImage {
// 通义万相API配置
private static final String API_KEY = Constants.DASH_SCOPE_KEY;
private static final String BASE_URL = "https://dashscope.aliy榜.com/api/v1/services/aigc/text2image/image-synthesis";
/**
* 执行AI图像处理
* @param imageUrl 原图URL
* @param prompt 用户描述
* @param negativePrompt 负向提示词
* @return 处理后的图片URL
*/
public static String doWork(String imageUrl, String prompt, String negativePrompt) {
try {
// 步骤1:提交图像生成任务
String taskId = submitTask(imageUrl, prompt, negativePrompt);
// 步骤2:轮询任务状态(最多等待90秒)
String resultUrl = pollTaskResult(taskId);
return resultUrl;
} catch (Exception e) {
throw new ServiceException("AI处理失败:" + e.getMessage());
}
}
/**
* 提交图像生成任务
*/
private static String submitTask(String imageUrl, String prompt, String negativePrompt)
throws Exception {
// 构建请求体
Map<String, Object> requestBody = new HashMap<>();
Map<String, Object> model = new HashMap<>();
model.put("name", "wan2.5-i2i-preview"); // 使用图像编辑模型
requestBody.put("model", model);
Map<String, Object> input = new HashMap<>();
input.put("prompt", prompt);
input.put("negative_prompt", negativePrompt);
input.put("image_url", imageUrl);
requestBody.put("input", input);
// 发送HTTP POST请求
HttpURLConnection conn = (HttpURLConnection)
new URL(BASE_URL).openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Authorization", "Bearer " + API_KEY);
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
// 写入请求体
OutputStream os = conn.getOutputStream();
os.write(new ObjectMapper().writeValueAsBytes(requestBody));
os.flush();
os.close();
// 解析响应获取task_id
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String response = reader.readLine();
reader.close();
// 解析JSON获取任务ID
Map<String, Object> respMap = new ObjectMapper().readValue(response, Map.class);
return (String) ((Map<String, Object>) respMap.get("output")).get("task_id");
}
/**
* 轮询任务结果
*/
private static String pollTaskResult(String taskId) throws Exception {
String statusUrl = "https://dashscope.ali榜.com/api/v1/tasks/" + taskId;
// 最多轮询30次,每次间隔3秒
for (int i = 0; i < 30; i++) {
Thread.sleep(3000);
// 查询任务状态
HttpURLConnection conn = (HttpURLConnection)
new URL(statusUrl).openConnection();
conn.setRequestProperty("Authorization", "Bearer " + API_KEY);
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String response = reader.readLine();
reader.close();
// 解析响应
Map<String, Object> respMap = new ObjectMapper().readValue(response, Map.class);
Map<String, Object> output = (Map<String, Object>) respMap.get("output");
String status = (String) output.get("task_status");
if ("SUCCEEDED".equals(status)) {
// 任务成功,返回结果图片URL
return (String) ((Map<String, Object>) output.get("results"))
.get("image_url");
} else if ("FAILED".equals(status)) {
throw new ServiceException("AI处理任务失败");
}
// 否则继续轮询
}
throw new ServiceException("AI处理超时");
}
}
异步任务处理机制的工作流程如下:第一步,用户提交处理请求后,系统调用通义万相API创建图像生成任务,并获取任务ID;第二步,系统进入轮询循环,每隔3秒查询一次任务状态;第三步,当任务状态变为"SUCCEEDED"时,获取处理结果图片URL并返回;第四步,如果超过90秒任务仍未完成,则返回超时错误。
图像处理接口是用户调用的入口:
@PostMapping("modify_photo")
public Res modifyPhoto(HttpServletRequest request,
@RequestParam("prompt") String prompt) {
// 从请求属性中获取当前用户(由拦截器设置)
User user = (User) request.getAttribute("user");
// 检查用户余额
int balance = Integer.parseInt(user.getBalance());
if (balance <= 0) {
return Res.error("余额不足,请联系客服充值");
}
// 获取用户上传的原图URL
String imageUrl = user.getOriginImageUrl();
if (StringUtils.isEmpty(imageUrl)) {
return Res.error("请先上传图片");
}
// 调用AI处理
String resultUrl = DealImage.doWork(imageUrl, prompt, "模糊、低质量、变形");
// 扣减余额
user.setBalance(String.valueOf(balance - 1));
user.setUseTime(new Date());
userMapper.updateById(user);
return Res.ok(resultUrl);
}
3.4 文件上传与MD5去重
文件上传是AI图像处理的前置功能。系统实现了完整的文件上传流程,并基于MD5实现了文件去重功能。
文件上传接口的实现如下:
@PostMapping("upload")
public Res upload(HttpServletRequest request, @RequestParam("file") MultipartFile file) {
// 校验文件是否为空
if (file.isEmpty()) {
return Res.error("请选择文件");
}
// 校验文件大小(限制100MB)
if (file.getSize() > 100 * 1024 * 1024) {
return Res.error("文件大小不能超过100MB");
}
// 校验文件类型
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();
if (!Arrays.asList(".jpg", ".jpeg", ".png").contains(suffix)) {
return Res.error("仅支持JPG、PNG格式图片");
}
try {
// 计算文件MD5
String md5 = DigestUtil.md5Hex(file.getBytes());
// 查询是否已存在相同MD5的文件
Document existDoc = documentMapper.selectOne(
new QueryWrapper<Document>()
.eq("md5", md5)
.eq("remove", false)
);
if (existDoc != null) {
// 文件已存在,直接返回已有URL(秒传逻辑)
return Res.ok(existDoc.getUrl());
}
// 生成唯一文件名
String uuid = UUID.randomUUID().toString().replace("-", "");
String newFilename = uuid + suffix;
// 保存到本地服务器
String localPath = "uploads/" + newFilename;
File localFile = new File(Constants.UPLOAD_PATH, newFilename);
file.transferTo(localFile);
// 上传到阿里云OSS
String ossUrl = OSSUploader.upload(localFile, "wx_clue_pic/" + newFilename);
// 保存文件信息到数据库
Document document = new Document();
document.setName(originalFilename);
document.setType(suffix);
document.setSize(file.getSize() / 1024); // 转换为KB
document.setUrl(ossUrl);
document.setPreview(ossUrl);
document.setMd5(md5);
document.setEnable(true);
document.setRemove(false);
documentMapper.insert(document);
// 删除本地临时文件
localFile.delete();
return Res.ok(ossUrl);
} catch (Exception e) {
return Res.error("上传失败:" + e.getMessage());
}
}
文件上传流程的关键设计点包括:MD5去重机制可以有效节约存储空间,提升用户体验(秒传效果);文件首先保存到本地,然后异步上传到OSS,保证上传的可靠性;上传成功后删除本地临时文件,释放磁盘空间。
四、前端核心功能实现
4.1 uni-app项目结构
uni-app项目采用标准的前端项目结构,主要目录和文件说明如下:
pages.json 是uni-app项目的页面路由配置文件,定义了应用中所有页面的路径、样式和导航栏配置:
{
"pages": [
{
"path": "pages/begin_login/begin_login",
"style": {
"navigationBarTitleText": "登录",
"navigationStyle": "custom"
}
},
{
"path": "pages/left_home/left_home",
"style": {
"navigationBarTitleText": "AI图片修改",
"navigationBarBackgroundColor": "#00BFFF"
}
},
{
"path": "pages/right_my/right_my",
"style": {
"navigationBarTitleText": "我的"
}
}
],
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#00BFFF",
"borderStyle": "black",
"backgroundColor": "#FFFFFF",
"list": [
{
"pagePath": "pages/left_home/left_home",
"text": "首页",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png"
},
{
"pagePath": "pages/right_my/right_my",
"text": "我的",
"iconPath": "static/tabbar/my.png",
"selectedIconPath": "static/tabbar/my-active.png"
}
]
}
}
manifest.json 是uni-app项目的应用配置文件,包含多端配置、权限声明、图标配置等信息。对于微信小程序,需要配置正确的AppID:
{
"mp-weixin": {
"appid": "wx6d7daa5dfcc90260",
"setting": {
"urlCheck": false,
"es6": true,
"minified": true
},
"usingComponents": true
}
}
4.2 HTTP请求封装
为了统一管理API请求,项目对uni.request进行了Promise封装,创建了axios.js工具类:
const baseUrl = 'https://wdfgdzx.xyz';
const $http = (options = {}) => {
return new Promise((resolve, reject) => {
uni.request({
url: baseUrl + options.url,
method: options.method || 'GET',
data: options.data || {},
header: {
"Content-Type": "application/json",
"token": uni.getStorageSync('user')?.token || ''
},
success: (res) => {
// 统一处理401未授权
if (res.data.code === '401') {
uni.navigateTo({
url: '/pages/begin_login/begin_login'
});
return;
}
resolve(res.data);
},
fail: (err) => {
uni.showToast({
title: '网络请求失败',
icon: 'none'
});
reject(err);
}
});
});
};
// 导出便捷方法
const get = (url, data) => $http({ url, method: 'GET', data });
const post = (url, data) => $http({ url, method: 'POST', data });
export default { $http, get, post };
HTTP请求封装的核心功能包括:自动携带Token,从本地存储中读取用户Token并添加到请求Header中;统一响应处理,对于401未授权响应自动跳转到登录页面;错误处理,对于网络请求失败显示统一的错误提示。
4.3 登录页面实现
登录页面是用户进入小程序的第一个页面,采用邮箱验证码的登录方式。页面核心代码如下:
<template>
<view class="login-container">
<view class="login-box">
<view class="title">AI图片处理助手</view>
<view class="subtitle">智能修改,一键完成</view>
<uni-forms :modelValue="formData" :rules="rules" ref="formRef">
<uni-forms-item name="email">
<uni-easyinput
v-model="formData.email"
placeholder="请输入邮箱地址"
:disabled="codeSent"
/>
</uni-forms-item>
<uni-forms-item name="code">
<view class="code-row">
<uni-easyinput
v-model="formData.code"
placeholder="请输入验证码"
maxlength="6"
/>
<button
class="code-btn"
:disabled="countdown > 0"
@click="sendCode"
>
{{ countdown > 0 ? countdown + 's' : '获取验证码' }}
</button>
</view>
</uni-forms-item>
</uni-forms>
<button class="login-btn" @click="handleLogin">登录</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
formData: {
email: '',
code: ''
},
codeSent: false,
countdown: 0,
timer: null
};
},
methods: {
// 发送验证码
async sendCode() {
if (!this.formData.email) {
uni.showToast({ title: '请输入邮箱', icon: 'none' });
return;
}
const res = await this.$post('/big/email_code', {
email: this.formData.email
});
if (res.code === '200') {
this.codeSent = true;
this.countdown = 60;
this.timer = setInterval(() => {
this.countdown--;
if (this.countdown <= 0) {
clearInterval(this.timer);
}
}, 1000);
uni.showToast({ title: '验证码已发送', icon: 'success' });
}
},
// 登录
async handleLogin() {
const res = await this.$post('/big/login', this.formData);
if (res.code === '200') {
// 保存用户信息
uni.setStorageSync('user', res.object);
uni.switchTab({ url: '/pages/left_home/left_home' });
} else {
uni.showToast({ title: res.message, icon: 'none' });
}
}
},
beforeDestroy() {
// 组件销毁时清除定时器
if (this.timer) {
clearInterval(this.timer);
}
}
};
</script>
登录页面的设计考虑了以下用户体验要素:验证码按钮带有倒计时功能,防止用户频繁点击发送验证码;输入框在发送验证码后会被禁用,避免用户修改邮箱地址;登录按钮使用了渐变色设计,提升视觉吸引力;错误提示使用轻提示(Toast)方式,不打断用户操作流程。
4.4 首页图片处理实现
首页是小程序的核心功能页面,实现了图片上传、AI处理、结果展示等功能。页面核心代码如下:
<template>
<view class="home-container">
<!-- 用户余额显示 -->
<view class="balance-bar">
<text>剩余次数:{{ user.balance }} 次</text>
<text class="refresh" @click="refreshUser">刷新</text>
</view>
<!-- 图片选择区域 -->
<view class="upload-section">
<view
class="upload-box"
@click="chooseImage"
>
<image
v-if="originImage"
:src="originImage"
mode="aspectFit"
class="preview-image"
/>
<view v-else class="upload-hint">
<uni-icons type="camera" size="50" color="#999"></uni-icons>
<text>点击选择图片</text>
</view>
</view>
</view>
<!-- 修改描述输入 -->
<view class="prompt-section">
<textarea
v-model="prompt"
placeholder="请输入图片修改要求,如:'将背景替换为蓝天白云'"
class="prompt-input"
/>
</view>
<!-- 处理按钮 -->
<button
class="process-btn"
:disabled="processing"
@click="processImage"
>
{{ processing ? '处理中...' : '立刻改图' }}
</button>
<!-- 结果展示 -->
<view v-if="resultImage" class="result-section">
<text class="section-title">处理结果</text>
<image
:src="resultImage"
mode="aspectFit"
class="result-image"
@click="previewImage(resultImage)"
/>
<view class="result-actions">
<button @click="saveImage">保存到相册</button>
<button @click="previewImage(resultImage)">预览大图</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
user: {},
originImage: '',
prompt: '',
processing: false,
resultImage: ''
};
},
onShow() {
this.loadUserInfo();
},
methods: {
// 加载用户信息
loadUserInfo() {
const user = uni.getStorageSync('user');
if (user) {
this.user = user;
this.originImage = user.originImageUrl || '';
}
},
// 选择图片
chooseImage() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: async (res) => {
this.originImage = res.tempFilePaths[0];
// 上传到服务器
uni.showLoading({ title: '上传中...' });
const uploadRes = await this.uploadFile(res.tempFilePaths[0]);
uni.hideLoading();
if (uploadRes.code === '200') {
// 保存原图URL到用户信息
this.user.originImageUrl = uploadRes.object;
uni.setStorageSync('user', this.user);
}
}
});
},
// 上传文件
uploadFile(filePath) {
return new Promise((resolve) => {
uni.uploadFile({
url: this.$baseUrl + '/document/upload',
filePath: filePath,
name: 'file',
header: {
"token": uni.getStorageSync('user')?.token || ''
},
success: (res) => {
resolve(JSON.parse(res.data));
},
fail: () => {
uni.showToast({ title: '上传失败', icon: 'none' });
resolve({ code: '500' });
}
});
});
},
// 处理图片
async processImage() {
if (!this.originImage) {
uni.showToast({ title: '请先选择图片', icon: 'none' });
return;
}
if (!this.prompt) {
uni.showToast({ title: '请输入修改要求', icon: 'none' });
return;
}
this.processing = true;
uni.showLoading({ title: 'AI处理中...' });
try {
const res = await this.$post('/user/modify_photo', {
prompt: this.prompt
});
uni.hideLoading();
if (res.code === '200') {
this.resultImage = res.object;
// 更新用户余额
this.user.balance = parseInt(this.user.balance) - 1;
uni.setStorageSync('user', this.user);
uni.showToast({ title: '处理成功', icon: 'success' });
} else {
uni.showToast({ title: res.message, icon: 'none' });
}
} catch (e) {
uni.hideLoading();
uni.showToast({ title: '处理失败', icon: 'none' });
} finally {
this.processing = false;
}
},
// 保存图片到相册
async saveImage() {
try {
// 下载图片
const downloadRes = await uni.downloadFile({
url: this.resultImage
});
// 保存到相册
await uni.saveImageToPhotosAlbum({
filePath: downloadRes.tempFilePath
});
uni.showToast({ title: '保存成功', icon: 'success' });
} catch (e) {
// 权限被拒绝
if (e.errMsg.includes('auth deny')) {
uni.showModal({
title: '提示',
content: '需要您授权保存图片到相册',
success: (res) => {
if (res.confirm) {
uni.openSetting();
}
}
});
}
}
},
// 预览大图
previewImage(url) {
uni.previewImage({
urls: [url],
current: url
});
}
}
};
</script>
首页功能实现的关键技术点包括:使用uni.chooseImage组件选择图片,支持从相册选择或拍照;使用uni.uploadFile上传文件,注意需要设置正确的content-type为multipart/form-data;图片下载和保存相册功能需要处理权限申请,对于权限被拒绝的情况引导用户去设置页面开启权限;使用uni.previewImage实现图片大图预览功能。
五、项目部署与运维
5.1 后端服务部署
后端服务部署在Linux服务器上,使用Docker容器化部署方案。主要配置如下:
Dockerfile定义了服务的容器镜像:
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD target/black-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Nginx反向代理配置实现了HTTPS访问和请求转发:
server {
listen 443 ssl;
server_name wdfgdzx.xyz;
ssl_certificate /etc/nginx/ssl/wdfgdzx.xyz.pfx;
ssl_certificate_key /etc/nginx/ssl/wdfgdzx.xyz.key;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
5.2 阿里云OSS配置
项目使用阿里云OSS存储用户上传的图片文件。OSS配置信息如下:
OSS客户端初始化:
@Configuration
public class OSSConfig {
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.accessKeyId}")
private String accessKeyId;
@Value("${aliyun.oss.accessKeySecret}")
private String accessKeySecret;
@Value("${aliyun.oss.bucketName}")
private String bucketName;
@Bean
public OSS ossClient() {
return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
}
}
文件上传工具类:
@Component
public class OSSUploader {
@Autowired
private OSS ossClient;
@Value("${aliyun.oss.bucketName}")
private String bucketName;
/**
* 上传文件到OSS
* @param file 本地文件
* @param objectName OSS对象名
* @return OSS访问URL
*/
public static String upload(File file, String objectName) {
// 创建OSSClient实例
OSS ossClient = new OSSClientBuilder().build(
endpoint, accessKeyId, accessKeySecret);
// 上传文件
ossClient.putObject(bucketName, objectName, file);
// 返回访问URL
String url = "https://" + bucketName + ".oss-cn-beijing.aliyuncs.com/" + objectName;
// 关闭OSSClient
ossClient.shutdown();
return url;
}
}
六、小程序定制开发服务
6.1 为什么选择小程序定制开发
随着移动互联网的快速发展,微信小程序已成为企业触达用户的重要渠道。相比原生App,小程序具有以下显著优势:
即用即走的便捷性:用户无需下载安装,通过扫码或搜索即可使用,极大地降低了用户的使用门槛。对于AI图像处理这类轻量级工具类应用,小程序的便捷性尤为重要。
微信生态的流量红利:依托微信超过10亿的月活用户,小程序可以充分利用微信的社交关系链进行传播,获客成本相对较低。
开发成本的优势:相比iOS和Android双平台原生开发,小程序只需要开发一套代码即可覆盖多端场景,开发效率提升约50%。
完善的支付能力:小程序可以无缝集成微信支付,为商业化变现提供了便利条件。
6.2 定制开发服务内容
我们提供专业的小程序定制开发服务,服务内容包括但不限于:
需求分析与方案设计:深入了解客户的业务需求和目标用户,输出详细的需求文档和技术方案。我们会根据客户的行业特点和业务场景,提供针对性的解决方案。
UI/UX设计:提供专业的界面设计和交互体验优化。我们的设计团队会结合品牌调性和用户习惯,打造既美观又易用的产品界面。
功能开发与集成:完成小程序前后端的开发工作,并集成第三方服务(如支付、地图、AI等)。我们采用Spring Boot + uni-app的主流技术栈,确保项目的技术先进性和可维护性。
测试与上线:提供全面的功能测试、兼容性测试和性能优化服务,协助客户完成小程序的上线和审核工作。
运维支持:提供持续的运维支持和功能迭代服务,确保小程序的稳定运行和持续优化。
6.3 技术服务优势
选择我们的定制开发服务,您将获得以下技术优势:
成熟的架构设计:我们采用经过大量项目验证的成熟架构方案,包括Spring Boot微服务框架、MyBatis-Plus ORM框架、JWT身份认证、Redis缓存等,确保系统的稳定性和可扩展性。
丰富的组件积累:我们积累了大量可复用的业务组件和工具类,包括文件上传组件、分页查询组件、HTTP请求封装、统一的响应处理等,可以显著缩短开发周期。
完善的文档支持:每个项目都会提供完整的技术文档和操作手册,方便后续的维护和迭代工作。
持续的售后服务:项目交付后会提供一定期限的免费维保服务,对于客户反馈的问题会在第一时间响应和处理。
七、总结与展望
7.1 项目总结
本文详细介绍了一个基于Spring Boot + uni-app技术栈的AI图像处理小程序的完整开发过程。通过本文的学习,读者可以掌握以下核心技术的实际应用:
Spring Boot微服务开发:从项目结构设计、核心配置到业务逻辑实现,读者可以学习到如何使用Spring Boot快速构建企业级后端服务。
MyBatis-Plus ORM框架应用:通过实际案例展示了MyBatis-Plus的CRUD操作、分页查询、条件构造等核心功能的使用方法。
JWT无状态认证机制:详细讲解了JWT Token的生成、验证和拦截机制,帮助读者理解现代Web应用的认证体系。
阿里云OSS文件存储:展示了如何对接阿里云OSS实现文件上传、存储和访问,掌握云存储服务的集成方法。
AI图像处理接口对接:通过通义万相API的对接案例,读者可以学习到如何将第三方AI能力集成到自己的应用中。
uni-app跨平台开发:从项目结构、组件使用到业务逻辑实现,全面介绍了uni-app小程序的开发流程。
7.2 技术亮点回顾
本项目的技术亮点包括:
异步任务处理机制:AI图像生成是耗时操作,系统采用异步轮询方案处理,既保证了用户体验,又避免了请求超时问题。
MD5文件去重:通过计算和存储文件MD5值,实现了文件秒传功能,既节约了存储空间,又提升了用户体验。
JWT密码绑定签名:Token使用用户密码作为签名密钥,即使Token被截获也无法被伪造,大大提高了安全性。
统一响应封装:所有API采用统一的响应格式,便于前端处理和接口监控。
权限处理机制:图片保存相册功能完整处理了权限申请和拒绝场景,提供了良好的用户体验。
更多推荐



所有评论(0)