uniapp调用后台接口Spring Boot - Java
·
整体流程概览
- 后端(Spring Boot):编写API接口,处理业务逻辑,操作数据库,并返回JSON数据。
- 前端(uni-app):在页面中使用
uni.request发起HTTP请求,调用后端API。 - 数据交互:前后端通过JSON格式进行数据通信。
- 跨域问题:解决开发过程中前端与后端不同端口访问的问题。
第一部分:Spring Boot 后端开发
步骤 1:创建项目与基础配置
- 使用 Spring Initializr 创建一个新的Spring Boot项目。
-
- Project: Maven
- Language: Java
- Spring Boot: 选择最新稳定版
- Dependencies: 至少添加
Spring Web和Lombok(简化代码)。
配置端口(可选,默认8080)。
在 src/main/resources/application.properties 中:
- server.port=8080
步骤 2:创建数据模型(Model)
创建一个简单的用户类。
src/main/java/com/hz/xxx/pojo/User.java
import lombok.Data;
@Data // Lombok 注解,自动生成 getter, setter, toString 等
public class User {
private Long id;
private String username;
private String email;
// 无参构造函数
public User() {
}
// 有参构造函数
public User(Long id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
}
}
步骤 3:创建控制器(Controller)
这是提供API接口的核心部分。
src/main/java/com/hz/xxx/controller/UserController.java
package org.example.springaa.controller;
import org.example.springaa.model.User;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@RestController // 表明这是一个RESTful控制器,返回数据而非视图
@RequestMapping("/api/user") // 定义API的基础路径
@CrossOrigin(origins = "*") // 允许所有来源的跨域请求,解决开发时跨域问题
public class UserController {
// 模拟一个内存数据库,用于演示
private List<User> userList = new ArrayList<>();
private Long nextId = 1L;
// 构造函数,初始化一些测试数据
public UserController() {
userList.add(new User(nextId++, "张三", "zhangsan@example.com"));
userList.add(new User(nextId++, "李四", "lisi@example.com")); // 修正了邮箱地址
}
// 1. 获取所有用户 - GET /api/user
@GetMapping
public List<User> getAllUsers() {
return userList;
}
// 2. 根据ID获取用户 - GET /api/user/{id}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userList.stream()
.filter(user -> user.getId().equals(id))
.findFirst()
.orElse(null); // 找不到返回null
}
// 3. 创建新用户 - POST /api/user
@PostMapping
public User createUser(@RequestBody User user) { // @RequestBody 将请求体的JSON绑定到User对象
user.setId(nextId++);
userList.add(user);
return user; // 返回创建成功的用户信息(包含新生成的ID)
}
// 4. 更新用户 - PUT /api/user/{id}
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User userDetails) {
User existingUser = getUserById(id);
if (existingUser != null) {
existingUser.setUsername(userDetails.getUsername());
existingUser.setEmail(userDetails.getEmail());
return existingUser;
}
return null; // 用户不存在
}
// 5. 删除用户 - DELETE /api/user/{id}
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Long id) {
User userToRemove = getUserById(id);
if (userToRemove != null) {
userList.remove(userToRemove);
return "用户删除成功";
}
return "用户不存在";
}
}
关键注解解释:
@RestController:组合了@Controller和@ResponseBody,直接返回对象数据,而不是视图名称。@RequestMapping:映射HTTP请求到处理器方法。@GetMapping,@PostMapping,@PutMapping,@DeleteMapping:分别映射GET、POST、PUT、DELETE请求。@PathVariable:将URL中的模板变量绑定到方法参数。@RequestBody:将HTTP请求体(JSON)绑定到方法参数对象。@CrossOrigin:处理跨域请求。生产环境建议指定具体的前端域名,而不是"*"。
步骤 4:运行与测试
运行 xxxApplication.java 中的main方法。后端服务将在 http://localhost:8080 启动。
可以使用 Postman 或 Apifox 等工具测试API是否正常工作。
GET http://localhost:8080/api/userPOST http://localhost:8080/api/user(Body: raw, JSON)
{
"username": "王五",
"email": "wangwu@example.com"
}
第二部分:uni-app 前端开发
步骤 1:发起网络请求 (uni.request)
uni-app 提供了 uni.request 方法用于发起HTTP请求。它是对原生XMLHttpRequest的封装,用法类似微信小程序的wx.request。
基本语法:
uni.request({
url: 'http://your-api-url',
method: 'GET', // 或 POST, PUT, DELETE
data: { ... }, // 请求参数(GET请求是Query,POST请求是Body)
header: {
'content-type': 'application/json' // 默认值,根据后端要求设置
},
success: (res) => {
// 请求成功回调,res.data 是服务器返回的数据
console.log(res.data);
},
fail: (err) => {
// 请求失败回调
console.error(err);
},
complete: () => {
// 请求完成回调(无论成功失败都会执行)
}
});
步骤 2:编写前端页面调用接口
假设我们在 pages/index/index.vue 中调用后端接口。
pages/index/index.vue
<template>
<view class="container">
<!-- 操作按钮区域 -->
<view class="action-section">
<button class="btn-primary" @click="toggleUserList">
{{ showUserList ? '隐藏用户列表' : '获取用户列表' }}
</button>
<button class="btn-secondary" @click="toggleCreateForm">
{{ showCreateForm ? '取消创建' : '创建新用户' }}
</button>
</view>
<!-- 用户列表区域 -->
<view v-if="showUserList" class="user-list-section">
<view class="section-header">
<text class="section-title">用户列表 ({{ userList.length }})</text>
<button class="btn-refresh" @click="getUsers" size="mini">
<text class="refresh-text">刷新</text>
</button>
</view>
<view class="user-list">
<view v-for="user in userList" :key="user.id" class="user-card">
<view class="user-info">
<text class="user-name">{{ user.username }}</text>
<text class="user-email">{{ user.email }}</text>
</view>
<button class="btn-delete" @click="deleteUser(user.id)" size="mini">删除</button>
</view>
<view v-if="userList.length === 0" class="empty-state">
<text class="empty-text">暂无用户数据</text>
<button class="btn-refresh" @click="getUsers">重新加载</button>
</view>
</view>
</view>
<!-- 新增用户表单区域 -->
<view v-if="showCreateForm" class="form-section">
<view class="section-header">
<text class="section-title">添加新用户</text>
<button class="btn-close" @click="closeCreateForm" size="mini">
<text class="close-text">关闭</text>
</button>
</view>
<view class="form-card">
<view class="input-group">
<text class="input-label">用户名</text>
<input
v-model="newUser.username"
placeholder="请输入用户名"
class="input-field"
placeholder-class="placeholder"
focus
/>
</view>
<view class="input-group">
<text class="input-label">邮箱地址</text>
<input
v-model="newUser.email"
placeholder="请输入邮箱地址"
class="input-field"
placeholder-class="placeholder"
type="email"
/>
</view>
<view class="form-actions">
<button class="btn-cancel" @click="closeCreateForm">取消</button>
<button class="btn-submit" @click="submitUser" :disabled="!isFormValid">
{{ isSubmitting ? '提交中...' : '确认添加' }}
</button>
</view>
</view>
</view>
<!-- 默认提示 -->
<view v-if="!showUserList && !showCreateForm" class="welcome-section">
<text class="welcome-title">用户管理系统</text>
<text class="welcome-subtitle">请选择上方操作开始使用</text>
<view class="feature-cards">
<view class="feature-card">
<text class="feature-icon">👥</text>
<text class="feature-title">查看用户</text>
<text class="feature-desc">点击"获取用户列表"查看所有用户信息</text>
</view>
<view class="feature-card">
<text class="feature-icon">➕</text>
<text class="feature-title">添加用户</text>
<text class="feature-desc">点击"创建新用户"添加新的用户账号</text>
</view>
</view>
</view>
</view>
</template>
<script>
const BASE_URL = 'http://localhost:8080/api/user';
export default {
data() {
return {
userList: [],
newUser: {
username: '',
email: ''
},
isSubmitting: false,
showUserList: false,
showCreateForm: false
}
},
computed: {
isFormValid() {
return this.newUser.username.trim() &&
this.newUser.email.trim() &&
!this.isSubmitting;
}
},
methods: {
// 切换用户列表显示
toggleUserList() {
this.showCreateForm = false;
this.showUserList = !this.showUserList;
if (this.showUserList) {
this.getUsers();
}
},
// 切换创建表单显示
toggleCreateForm() {
this.showUserList = false;
this.showCreateForm = !this.showCreateForm;
if (!this.showCreateForm) {
this.resetForm();
}
},
// 关闭创建表单
closeCreateForm() {
this.showCreateForm = false;
this.resetForm();
},
// 重置表单
resetForm() {
this.newUser = { username: '', email: '' };
this.isSubmitting = false;
},
async getUsers() {
try {
uni.showLoading({
title: '加载中...',
mask: true
});
const result = await new Promise((resolve, reject) => {
uni.request({
url: BASE_URL,
method: 'GET',
success: (res) => resolve(res),
fail: (err) => reject(err)
});
});
uni.hideLoading();
if (result.statusCode === 200) {
this.userList = result.data;
uni.showToast({
title: `已加载 ${this.userList.length} 个用户`,
icon: 'success'
});
} else {
uni.showToast({
title: `获取失败: ${result.statusCode}`,
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
console.error('请求异常:', error);
uni.showToast({
title: '网络异常',
icon: 'none'
});
}
},
async createUser() {
if (!this.isFormValid) return;
this.isSubmitting = true;
try {
const result = await new Promise((resolve, reject) => {
uni.request({
url: BASE_URL,
method: 'POST',
data: {
username: this.newUser.username,
email: this.newUser.email
},
header: {
'Content-Type': 'application/json'
},
success: (res) => resolve(res),
fail: (err) => reject(err)
});
});
if (result.statusCode === 200) {
uni.showToast({
title: '用户创建成功',
icon: 'success'
});
this.resetForm();
this.showCreateForm = false;
} else {
uni.showToast({
title: '创建失败',
icon: 'none'
});
}
} catch (error) {
console.error('创建用户异常:', error);
uni.showToast({
title: '创建失败',
icon: 'none'
});
} finally {
this.isSubmitting = false;
}
},
async deleteUser(userId) {
uni.showModal({
title: '确认删除',
content: '确定要删除这个用户吗?此操作不可撤销。',
confirmColor: '#FF3B30',
success: async (res) => {
if (res.confirm) {
try {
const result = await new Promise((resolve, reject) => {
uni.request({
url: `${BASE_URL}/${userId}`,
method: 'DELETE',
success: (res) => resolve(res),
fail: (err) => reject(err)
});
});
if (result.statusCode === 200) {
uni.showToast({
title: '删除成功',
icon: 'success'
});
this.getUsers();
}
} catch (error) {
console.error('删除用户异常:', error);
uni.showToast({
title: '删除失败',
icon: 'none'
});
}
}
}
});
},
submitUser() {
this.createUser();
}
}
}
</script>
<style>
.container {
padding: 30rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.action-section {
display: flex;
gap: 20rpx;
margin-bottom: 40rpx;
}
.btn-primary {
flex: 1;
background: linear-gradient(45deg, #007AFF, #5856D6);
color: white;
border: none;
border-radius: 16rpx;
padding: 24rpx;
font-size: 32rpx;
font-weight: 600;
box-shadow: 0 8rpx 24rpx rgba(0, 122, 255, 0.3);
}
.btn-secondary {
flex: 1;
background: linear-gradient(45deg, #34C759, #30B04C);
color: white;
border: none;
border-radius: 16rpx;
padding: 24rpx;
font-size: 32rpx;
font-weight: 600;
box-shadow: 0 8rpx 24rpx rgba(52, 199, 89, 0.3);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
}
.section-title {
font-size: 36rpx;
font-weight: 700;
color: #1C1C1E;
}
.btn-refresh, .btn-close {
background: #8E8E93;
color: white;
border: none;
border-radius: 12rpx;
padding: 16rpx 24rpx;
font-size: 24rpx;
}
.refresh-text, .close-text {
color: white;
}
.user-list-section, .form-section {
background: white;
border-radius: 24rpx;
padding: 40rpx 30rpx;
margin-bottom: 40rpx;
box-shadow: 0 12rpx 48rpx rgba(0, 0, 0, 0.1);
animation: slideDown 0.3s ease;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.user-list {
max-height: 600rpx;
overflow-y: auto;
}
.user-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
background: #F8F9FA;
border-radius: 16rpx;
margin-bottom: 20rpx;
border-left: 8rpx solid #007AFF;
}
.user-info {
flex: 1;
}
.user-name {
display: block;
font-size: 32rpx;
font-weight: 600;
color: #1C1C1E;
margin-bottom: 8rpx;
}
.user-email {
display: block;
font-size: 28rpx;
color: #8E8E93;
}
.btn-delete {
background: linear-gradient(45deg, #FF3B30, #FF2D55);
color: white;
border: none;
border-radius: 12rpx;
padding: 16rpx 24rpx;
font-size: 24rpx;
font-weight: 500;
min-width: 120rpx;
}
.empty-state {
text-align: center;
padding: 80rpx 0;
}
.empty-text {
font-size: 32rpx;
color: #8E8E93;
display: block;
margin-bottom: 30rpx;
}
.form-card {
background: #F8F9FA;
border-radius: 20rpx;
padding: 40rpx 30rpx;
}
.input-group {
margin-bottom: 40rpx;
}
.input-label {
display: block;
font-size: 30rpx;
font-weight: 600;
color: #1C1C1E;
margin-bottom: 20rpx;
}
.input-field {
background: white;
border: 2rpx solid #E5E5EA;
border-radius: 16rpx;
padding: 28rpx 24rpx;
font-size: 32rpx;
color: #1C1C1E;
transition: all 0.3s ease;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
.input-field:focus {
border-color: #007AFF;
box-shadow: 0 4rpx 20rpx rgba(0, 122, 255, 0.2);
transform: translateY(-2rpx);
}
.placeholder {
color: #C7C7CC;
font-size: 32rpx;
}
.form-actions {
display: flex;
gap: 20rpx;
margin-top: 40rpx;
}
.btn-cancel {
flex: 1;
background: #8E8E93;
color: white;
border: none;
border-radius: 16rpx;
padding: 28rpx;
font-size: 34rpx;
font-weight: 600;
}
.btn-submit {
flex: 2;
background: linear-gradient(45deg, #007AFF, #5856D6);
color: white;
border: none;
border-radius: 16rpx;
padding: 28rpx;
font-size: 34rpx;
font-weight: 600;
box-shadow: 0 8rpx 24rpx rgba(0, 122, 255, 0.3);
}
.btn-submit:disabled {
background: linear-gradient(45deg, #C7C7CC, #AEAEB2);
box-shadow: none;
}
.welcome-section {
text-align: center;
padding: 100rpx 0;
}
.welcome-title {
display: block;
font-size: 48rpx;
font-weight: 700;
color: white;
margin-bottom: 20rpx;
}
.welcome-subtitle {
display: block;
font-size: 32rpx;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 60rpx;
}
.feature-cards {
display: flex;
flex-direction: column;
gap: 30rpx;
}
.feature-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20rpx;
padding: 40rpx 30rpx;
border: 1rpx solid rgba(255, 255, 255, 0.2);
}
.feature-icon {
display: block;
font-size: 60rpx;
margin-bottom: 20rpx;
}
.feature-title {
display: block;
font-size: 36rpx;
font-weight: 600;
color: white;
margin-bottom: 10rpx;
}
.feature-desc {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 8rpx;
}
::-webkit-scrollbar-track {
background: #F1F1F1;
border-radius: 8rpx;
}
::-webkit-scrollbar-thumb {
background: #C1C1C1;
border-radius: 8rpx;
}
</style>
第三部分:关键问题与优化
跨域问题(CORS)
- 问题:当uni-app运行在浏览器(H5)或小程序开发者工具上时,其地址(如
http://localhost:9000)与后端Spring Boot(http://localhost:8080)端口不同,浏览器会因同源策略阻止请求。 - 解决方案:
-
- 后端解决(推荐):使用Spring Boot的
@CrossOrigin注解(如上文代码所示)或配置一个全局的CORS过滤器。
- 后端解决(推荐):使用Spring Boot的
全局CORS配置(替代@CrossOrigin):
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 针对所有/api/开头的路径
.allowedOrigins("*") // 允许所有来源,生产环境应指定具体域名
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*");
}
}
前端代理(仅开发环境):在 manifest.json 的 H5 配置中设置代理,可以避免浏览器跨域。
"h5": {
"devServer": {
"proxy": {
"/api": {
"target": "http://localhost:8080",
"changeOrigin": true,
"pathRewrite": {
"^/api": "/api"
}
}
}
}
}
这样,前端请求 /api/user 会被代理到 http://localhost:8080/api/user。
更多推荐



所有评论(0)