Xuan's blog - 后端相关
type: channel
link: https://blog.ybyq.wang/category/back-end/
items: 基于Spring Boot + Vue.js的GPT AI聊天应用：从开发到部署的完整实践
items: Springboot实现后台图片上传
items: Springboot 手机号短信验证码注册登录实现
items: Springboot 邮箱验证码注册登录实现
items: Springboot中 MyBatis-Flex TableDef 的使用
items: 智慧零工平台后端开发实战：SpringBoot + MyBatis-Flex 现代化架构
items: 使用Gradle打包springboot项目为JAR包教程
items: 云函数 vs. 传统后端：微信小程序开发该怎么选？
items: Django API 响应格式：一个新手踩坑记
items: 基于Node.js + Express + MySQL的健身会员管理系统后端

基于Spring Boot + Vue.js的GPT AI聊天应用：从开发到部署的完整实践
type: item
date: 2025-09-23T21:57:00+08:00
description: 前言一个人一个下午做成的，为了应对周四的作业答辩。🚀 项目概述本文将详细介绍一个基于Spring Boot后端和Vue.js前端的GPT AI聊天应用的完整开发流程。该项目集成了OpenAI、阿里云通义千问、DeepSeek、豆包、Kimi等多个主流AI平台，支持文本对话和图像生成功能，并实现了用户认证、会话管理、管理员面板、用户统计等完整的业务功能。体验地址：https://gpt.xuan.cyou/技术栈后端: Spring Boot 3.5.5, Java 17, SQLite, JPA/Hibernate前端: Vue.js 3, Vite, Pinia, JavaScript ES6+AI集成: OpenAI GPT-4, 阿里云通义千问, DeepSeek, 豆包, Kimi部署: 宝塔面板, Nginx, SSL证书📁 项目架构GPT-AI-Project/ ├── gpt-ai-java/ # Spring Boot 后端 │ ├── src/main/java/ │ │ ├── controller/ # REST API控制器 (用户, AI, 管理员等) │ │ ├── service/ # 业务逻辑层 (多AI平台服务, 用户统计等) │ │ ├── entity/ # 数据实体 (用户, 对话历史, 统计数据等) │ │ ├── config/ # 配置类 │ │ └── adopter/ # AI平台适配器 (OpenAI, Ali, DeepSeek, Kimi等) │ ├── src/main/resources/ │ │ └── application.yaml # 应用配置 │ └── pom.xml # Maven依赖配置 └── frontend/ # Vue.js 前端 ├── src/ │ ├── components/ # Vue组件 │ ├── views/ # 页面视图 (聊天, 图像, 管理面板, 用户后台等) │ ├── api/ # API调用封装 │ └── store/ # 状态管理 (Pinia) ├── public/ # 静态资源 └── package.json # NPM依赖配置🔧 核心功能实现1. 后端核心架构1.1 多AI平台集成项目支持OpenAI和阿里云通义千问两个AI平台，通过适配器模式实现：// AI控制器统一入口 @RestController @RequestMapping("/api/ai") public class AiController { @GetMapping("/chat/text/stream") public SseEmitter streamChat( @RequestParam String platform, @RequestParam String model, @RequestParam String message ) { // 根据平台选择对应的AI服务 (示例) if ("openai".equals(platform)) { return openAiController.sendTextStream(model, message); } else if ("ali".equals(platform)) { return aliAiController.sendTextStream(model, message); } // ... 此处添加对 deepseek, doubao, kimi 等平台的路由逻辑 return null; // or throw an exception } }1.2 流式响应实现使用Server-Sent Events (SSE) 实现实时流式对话：@PostMapping("/chat/text/stream") public SseEmitter sendTextStream(String model, String message) { SseEmitter emitter = new SseEmitter(0L); CompletableFuture.runAsync(() -> { try { // 流式读取AI响应并实时推送 openAIService.sendText(model, message, true, delta -> { emitter.send(SseEmitter.event().name("delta").data(delta)); }); emitter.send(SseEmitter.event().name("done").data("[DONE]")); emitter.complete(); } catch (Exception e…

Springboot实现后台图片上传
type: item
date: 2025-07-02T20:05:00+08:00
description: 本文将深入探讨一个标准的Web应用后台管理系统中，图片上传功能的完整实现流程。我们将从前端用户交互开始，一直到后端的文件处理、存储和最终的URL返回。整体流程概览图片上传功能的核心流程可以分为以下几个步骤：前端交互：管理员在前端页面选择图片文件。文件上传：前端将图片文件通过HTTP POST请求发送到后端指定的API端点。后端接收：后端控制器（Controller）接收到上传的文件。文件处理：后端服务对文件进行处理，如生成唯一文件名、校验文件类型和大小等。文件存储：将处理后的文件保存到服务器的指定目录中。返回结果：后端向前端返回一个包含图片可访问URL的成功响应。前端展示：前端接收到URL后，将其用于图片预览或表单提交。下面是这个流程的可视化图表：后端实现细节后端采用Spring Boot框架，下面是关键代码的解析。1. 文件上传控制器 (UploadController.java)这个控制器是处理文件上传请求的入口。@RestController @RequestMapping("/api/admin/upload") @CrossOrigin(origins = "*") public class UploadController { private static final Logger logger = LoggerFactory.getLogger(UploadController.class); /** * 上传图片 */ @PostMapping("/image") public Result<?> uploadImage(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return Result.error("上传失败，请选择文件"); } try { // 定义存储目录 String resourcePath = "src/main/resources/static/images"; File directory = new File(resourcePath); if (!directory.exists()) { directory.mkdirs(); } // 生成唯一文件名，防止重名覆盖 String originalFilename = file.getOriginalFilename(); String extension = originalFilename.substring(originalFilename.lastIndexOf(".")); String filename = UUID.randomUUID().toString() + extension; // 保存文件 Path filePath = Paths.get(directory.getAbsolutePath(), filename); Files.write(filePath, file.getBytes()); // 构建返回给前端的数据 Map<String, String> data = new HashMap<>(); data.put("url", "/images/" + filename); // 返回相对路径 return Result.success(data); } catch (IOException e) { logger.error("上传图片失败", e); return Result.error("上传图片失败: " + e.getMessage()); } } }关键点：@PostMapping("/image")：定义了处理POST请求的API端点。@RequestParam("file") MultipartFile file：通过Spring MVC接收上传的文件。唯一文件名：使用 UUID.randomUUID() 来生成一个全球唯一的文件名，避免了用户上传同名文件时互相覆盖的问题。文件存储：文件被保存在 src/main/resources/static/images/ 目录下。这是一个标准的Maven项目静态资源目录。返回URL：向前端返回的是一个相对URL，如 /images/xxxx.png，而不是完整的文件系统路径。2. 静态资源服务控制器 (StaticResourceController.java)文件上传到服务器后，我们还需要一个机制让浏览器能够通过URL访问到这些图片。StaticResourceController 就是为此而生。@Controller @RequestMapping("/images") public class StaticResourceControlle…

Springboot 手机号短信验证码注册登录实现
type: item
date: 2025-06-24T22:52:00+08:00
description: 本文将介绍如何在Spring Boot应用中实现短信验证码注册和登录功能，为用户提供便捷的移动端认证体验。1. 功能概述我们将实现以下功能：手机号+验证码注册手机号+验证码登录2. 技术栈Spring Boot 3.2.0互亿无线短信平台MyBatis FlexJWT3. 实现步骤3.1 短信服务集成首先，需要在application.properties中配置短信平台信息：# 短信配置 sms.account=C09251523 sms.apikey=826efdf7322e3c49355428a523c10eec3.2 创建短信工具类创建SmsUtil工具类处理短信发送：@Component public class SmsUtil { private static final Logger logger = LoggerFactory.getLogger(SmsUtil.class); // 短信发送API地址 private static final String SMS_API_URL = "http://106.ihuyi.com/webservice/sms.php?method=Submit"; // API账号和密码 @Value("${sms.account:C09***523}") private String account; @Value("${sms.apikey:826efdf7322e3c49355***a523c10eec}") private String apiKey; /** * 发送短信验证码 * @param phone 手机号 * @param code 验证码 * @return 发送结果，包含code和msg */ public Map<String, String> sendVerificationCode(String phone, String code) { Map<String, String> result = new HashMap<>(); try { // 短信内容 String content = "您的验证码是：" + code + "。请不要把验证码泄露给其他人。"; // 发送请求 String response = sendSmsRequest(phone, content); // 解析结果 result = parseXmlResponse(response); // 记录日志 if ("2".equals(result.get("code"))) { logger.info("短信发送成功，手机号: {}, 验证码: {}", phone, code); } else { logger.error("短信发送失败，手机号: {}, 错误信息: {}", phone, result.get("msg")); } } catch (Exception e) { logger.error("发送短信验证码异常", e); result.put("code", "0"); result.put("msg", "系统异常，短信发送失败"); } return result; } // 省略发送请求和解析响应的方法... }3.3 验证码生成和存储在UserServiceImpl中实现验证码管理：// 在UserServiceImpl中 private final Map<String, String> smsCodeCache = new HashMap<>(); @Override public boolean sendSmsCode(String phone, String type) { try { // 校验手机号格式 if (!isValidPhoneNumber(phone)) { return false; } // 生成6位随机验证码 String code = generateRandomCode(6); // 将验证码存入缓存，实际项目中应使用Redis等缓存服务 smsCodeCache.put(phone + ":" + type, code); // 发送短信 Map<String, String> result = smsUtil.sendVerificationCode(phone, code); // 检查发送结果 return "2".equals(result.get("code")); } catch (Exception e) { logger.error("发送短信验证码失败", e); return false; } } @Override public String getSmsCode(String pho…

Springboot 邮箱验证码注册登录实现
type: item
date: 2025-06-24T22:48:00+08:00
description: 课设需要做一个宠物商店APP，在登录时我加了一个邮箱注册登录。本文将详细介绍如何在Spring Boot应用中实现邮箱验证码注册和登录功能。1. 功能概述我们将实现以下功能：邮箱+验证码注册邮箱+验证码登录邮箱+密码登录（传统登录方式）这些功能涵盖了用户注册和登录的主要场景，为用户提供了灵活的选择。效果预览2. 技术栈Spring Boot 3.2.0Spring SecuritySpring MailThymeleaf (邮件模板)MyBatis FlexJWT (认证)3. 实现步骤3.1 邮件服务配置首先，我们需要配置邮件服务。在application.properties文件中添加：# 邮件服务配置 spring.mail.host=smtp.qq.com spring.mail.port=587 spring.mail.username=your-email@qq.com spring.mail.password=your-authorization-code spring.mail.default-encoding=UTF-8 spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true[scode type="blue"]对于QQ邮箱，密码不是邮箱密码，而是授权码，需要在QQ邮箱设置中获取。[/scode]3.2 创建邮件服务创建EmailService接口和实现类：public interface EmailService { boolean sendVerificationEmail(String to, String subject, String code, String type); }@Service public class EmailServiceImpl implements EmailService { private static final Logger logger = LoggerFactory.getLogger(EmailServiceImpl.class); private final JavaMailSender mailSender; private final TemplateEngine templateEngine; @Value("${spring.mail.username}") private String from; public EmailServiceImpl(JavaMailSender mailSender, TemplateEngine templateEngine) { this.mailSender = mailSender; this.templateEngine = templateEngine; } @Override public boolean sendVerificationEmail(String to, String subject, String code, String type) { try { // 准备Thymeleaf模板上下文 Context context = new Context(); context.setVariable("code", code); context.setVariable("type", type); context.setVariable("time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); // 处理模板生成HTML内容 String htmlContent = templateEngine.process("email/verification-code", context); // 创建MIME消息 MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); helper.setFrom(from); helper.setTo(to); helper.setSubject(subject); helper.setText(htmlContent, true); // 发送邮件 mailSender.send(message); logger.info("邮件发送成功：{}", to); return true; } catch (Excep…

Springboot中 MyBatis-Flex TableDef 的使用
type: item
date: 2025-06-11T14:08:37+08:00
description: 什么是 TableDef？TableDef 是 MyBatis-Flex 框架提供的一个强大特性，用于定义数据库表的元数据。它提供了一种类型安全的方式来引用数据库表和列，避免了直接使用字符串字面量可能带来的问题。为什么需要 TableDef？在传统的 MyBatis 开发中，我们经常需要写这样的代码：QueryWrapper query = QueryWrapper.create() .where("user_id = ?", userId) .and("status = ?", status);这种方式存在以下问题：容易拼写错误重构困难没有代码提示类型不安全TableDef 的优势使用 TableDef 后，代码变成了这样：QueryWrapper query = QueryWrapper.create() .where(USER.USER_ID.eq(userId)) .and(USER.STATUS.eq(status));主要优势：类型安全，编译时就能发现错误代码提示，IDE 可以自动补全重构方便，如果表名或字段名改变，只需要修改一处代码更清晰，易于维护实际项目中的应用让我们通过宠物商店项目中的实际例子来看看 TableDef 的使用：1. 定义 TableDef首先，我们需要为每个表创建一个 TableDef 类：public class PetTableDef extends TableDef { public static final PetTableDef PET = new PetTableDef(); public final QueryColumn ID = new QueryColumn(this, "id"); public final QueryColumn USER_ID = new QueryColumn(this, "user_id"); public final QueryColumn NAME = new QueryColumn(this, "name"); public final QueryColumn BREED = new QueryColumn(this, "breed"); public final QueryColumn AVATAR = new QueryColumn(this, "avatar"); public PetTableDef() { super("pet", "p"); } }2. 在查询中使用在 Service 层中使用 TableDef 进行查询：@Service public class PetServiceImpl implements PetService { @Autowired private PetMapper petMapper; @Override public List<Pet> getPetsByUserId(Integer userId) { return petMapper.selectListByQuery( QueryWrapper.create() .where(PET.USER_ID.eq(userId)) ); } }3. 复杂查询示例使用 TableDef 可以轻松构建复杂的查询：// 多表联查 QueryWrapper query = QueryWrapper.create() .select( PET.NAME, PET.BREED, USER.NICKNAME.as("ownerName") ) .from(PET) .leftJoin(USER).on(PET.USER_ID.eq(USER.ID)) .where(PET.USER_ID.eq(userId)); // 条件查询 QueryWrapper query = QueryWrapper.create() .where(PET.NAME.like(keyword)) .and(PET.BREED.eq(breed)) .orderBy(PET.ID.desc()); // 分页查询 Page<Pet> page = petMapper.paginate( new Page<>(pageNum, pageSize), QueryWrapper.create() .where(PET.USER_ID.eq(userId)) );4. 动态 SQLTableDef 也支持动态 SQL 的构建：QueryWrapper query = QueryWrapper.create() .where(PET.USER_ID.eq(userId)); if (StringUtils.isNotBlank(keyword)) { query.and(PET.NAME.lik…

智慧零工平台后端开发实战：SpringBoot + MyBatis-Flex 现代化架构
type: item
date: 2025-06-02T17:46:00+08:00
description: 本文将详细分享我使用Spring Boot 3.3.5 + MyBatis-Flex + SQLite构建零工平台后端系统的完整经验，包含架构设计、技术选型、核心功能实现和生产部署。项目背景随着零工经济的快速发展，我决定开发一个现代化的零工服务平台。后端系统需要支持工作发布、申请匹配、用户管理、支付结算等复杂业务场景，同时要保证高性能、高可用和易扩展。项目概览项目名称: 智慧零工平台后端系统技术栈: Spring Boot 3.3.5 + Java 21 + MyBatis-Flex + SQLite项目地址: GitHub - Gigplatform_backendAPI文档: 24个业务模块，100+接口部署方式: 宝塔面板 + PM2 + Nginx技术选型分析为什么选择Spring Boot 3？Java 21支持: 享受最新的语言特性和性能优化原生镜像: 支持GraalVM原生编译，启动更快响应式编程: 内置WebFlux支持可观测性: 增强的监控和链路追踪安全性: 升级的Spring SecurityMyBatis-Flex vs MyBatis-Plus经过对比，我选择了MyBatis-Flex：// MyBatis-Flex 优势示例 // 1. 类型安全的查询 List<Job> jobs = queryWrapper .select(JOB.ALL_COLUMNS) .from(JOB) .where(JOB.STATUS.eq("ACTIVE")) .and(JOB.SALARY.ge(5000)) .orderBy(JOB.CREATE_TIME.desc()) .limit(10) .list(); // 2. 智能字段映射 @Table("t_job") public class Job { @Id(keyType = KeyType.Auto) private Long id; @Column("job_title") private String title; // 自动处理驼峰命名 private String companyName; // -> company_name }SQLite的选择对于中小型项目，SQLite具有独特优势：零配置: 无需独立数据库服务器高性能: 本地文件访问，减少网络开销事务支持: 完整的ACID特性跨平台: 易于开发和部署成本低: 降低运维复杂度架构设计整体架构┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 前端应用 │ │ API网关 │ │ 负载均衡 │ │ (uni-app) │◄───│ (Nginx) │◄───│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ ▼ ┌─────────────────────────┐ │ Spring Boot │ │ (业务逻辑层) │ └─────────────────────────┘ │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 认证授权 │ │ 业务服务 │ │ 数据访问 │ │ (JWT) │ │ (Service) │ │ (MyBatis) │ └─────────────┘ └─────────────┘ └─────────────┘ │ ▼ ┌─────────────────┐ │ SQLite 数据库 │ └─────────────────┘项目模块结构com.example.backend/ ├── controller/ # 控制器层 │ ├── AuthController # 认证授权 │ ├── JobsController # 工作管理 │ ├── UsersController # 用户管理 │ └── ... # 其他业务控制器 ├── service/ # 业务逻辑层 │ ├── impl/ # 实现类 │ └── interfaces/ # 接口定义 ├── mapper/ # 数据访问层 ├── entity/ # 实体类 ├── config/ # 配置类 ├── common/ # 公共组件 │ ├── Result.java # 统一返回结果 │ ├── JwtUtil.java # JWT工具 │ └── JwtFilter.java # JWT过滤器 └── BackendApplication.java # 启动类核心功能实现1. JWT认证体系@Componen…

使用Gradle打包springboot项目为JAR包教程
type: item
date: 2025-05-22T23:33:00+08:00
description: 本文将详细演示如何在IDEA中使用 Gradle 构建工具将 SpringBoot 项目打包为可执行的 JAR 文件。操作步骤步骤 1：配置项目构建工件打开项目结构，在工件（Artifacts）中添加新的 JAR 配置。步骤 2：选择模块配置系统默认选择所有模块，确认配置无误后点击确定。步骤 3：执行 Gradle 构建打开右侧的 Gradle 工具窗口展开 build 任务组双击执行 bootjar 任务步骤 4：获取生成的 JAR 文件构建完成后，打开项目根目录下的 build/libs/ 文件夹，即可看到生成的 .jar 文件。重要提示⚠️ 注意：在提交作业或部署项目时，请选择第一个 JAR 文件（通常是不带 -plain 后缀的文件），不要选择第二个 plain JAR 文件。可执行 JAR：包含所有依赖项，可以直接运行Plain JAR：仅包含项目代码，缺少依赖项，无法独立运行运行 JAR 文件生成的可执行 JAR 文件可以通过以下命令运行：java -jar your-project-name.jar

云函数 vs. 传统后端：微信小程序开发该怎么选？
type: item
date: 2025-05-08T13:16:00+08:00
description: 当你要为微信小程序添加后台功能时，比如处理用户数据、响应用户操作，你可能会遇到两种主要的选择：云函数（也叫Serverless函数）和传统后端服务（比如用Node.js、Java等语言开发和自己搭建的服务器）。它们都能帮你实现业务逻辑，但在工作方式、开发和维护上却有很大不同。下面我们来详细对比一下。一、核心区别：它们是如何工作的？对比维度云函数 (Serverless)传统后端运行方式按需启动：只有在需要时（如有用户请求）才运行，用完即停。一直在线：服务器持续运行，时刻准备处理请求。服务器管理无需操心：你只管写代码，平台会自动调整资源应对用户多少，不用你买和管理服务器。自己负责：你需要购买云主机、配置服务器环境、考虑如何应对访问高峰（如负载均衡）。花钱方式用多少付多少：按实际运行次数和消耗的资源计费，如果用得少就很省钱。包时段付费：通常按服务器运行时间计费，不管有没有人用，服务器开着就要钱。首次响应速度可能稍慢：如果一个云函数很久没被调用，第一次用它时可能会有短暂延迟（叫做"冷启动"，一般零点几秒到1秒）。一直很快：因为服务一直在线，所以没有冷启动问题。代码组织小而专：把功能拆成一个个独立的"小函数"，每个函数负责一件小事。大而全：通常是一个完整的"大应用"，包含很多功能和接口。二、实现业务逻辑：具体有什么不一样？1. 开发体验云函数独立开发，事件触发：每个函数可以单独开发和部署。它们通常由某个"事件"触发，比如用户在小程序里点了个按钮（HTTP请求）、数据库里的数据变了，或者到了预设的时间。例子：用户下单 → 触发一个云函数 → 这个函数负责生成订单 → 然后把订单信息写入数据库。代码特点：代码通常比较简单，每个函数专注于单一任务（比如一个叫 payOrder 的函数专门处理支付，一个叫 sendEmail 的函数专门发邮件）。传统后端构建完整应用：你需要搭建一个完整的应用程序框架（比如用Node.js的Express框架，或Java的Spring Boot框架）。这包括定义请求如何被处理（路由）、一些公共功能（中间件）、以及如何连接和管理数据库等。例子：你启动一个Node.js服务，用户下单时，请求会发送到服务器上类似 /api/order 这样的地址，然后由服务器上的代码处理订单逻辑。代码特点：整体代码结构可能更复杂，但好处是方便集中管理多个功能接口和一些需要多个步骤一起完成的复杂操作。2. 操作数据库云函数在微信小程序里，有时前端可以直接通过微信提供的工具包（SDK）来读写云数据库（前提是要配置好安全规则，防止数据泄露）。但对于复杂的操作，比如需要保证好几步数据库操作要么都成功要么都失败（这叫"事务"），或者需要从好几个数据表里查数据，还是得在云函数里完成。传统后端数据库完全由后端服务器控制，小程序前端不能直接访问，必须通过后端提供的接口（API）来间接操作。这样做的好处是更灵活，可以用各种数据库工具（比如ORM简化数据库代码）、写复杂的SQL查询语句，还能优化数据库连接等。3. 安全性云函数 (特指微信云开发中的云函数)自带微信安全特性：能比较容易地获得用户的微信身份信息（比如 OPENID）。敏感操作更安全：像处理支付、调用一些有密钥的敏感服务，都必须放在云函数里，这样可以避免在小程序前端暴露核心逻辑和密钥，更安全。传统后端安全靠自己：你需要自己实现用户身份验证（比如用JWT、OAuth等技术），还要防范常见的网络攻击（比如SQL注入、XSS攻击等）。三、用在什么场景更合适？场景云函数优势传统后端优势快速做一个小程序试试水（MVP）快：不用管服务器，可能几小时或一天就能让核心功能跑起来。慢：得买服务器、配域名、弄HTTPS证书，比较折腾。不常用的功能（如半夜定时跑个小任务）省钱：只在运行时花钱，平时不用不花钱。浪费：服务器一直开着，就算没事干也耗钱。用户量突然暴增（如搞秒杀活动）能扛住：平台会自动增加资源应对，不容易崩。可能崩：得提前估计流量，手动预防，不然可能顶不住。需要几步操作绝对可靠（如银行转账）有点难：跨多个函数保证所有步骤都成功比较麻烦。更可靠：可以用数据库的"事务"功能保证操作要么全成功要么全失败。需要长时间运行的任务（如视频转换）有时间限制：比如微信云函数一次最多运行几十秒或几分钟。没问题：可以自己控制程序运行多久。注：MVP (Minimum Viable Product)，指最小可行产品，用最核心的功能快速验证想法，学过项目管理的应该都知道四、举个例子：用户下单支付用云函数怎么做？小程序前端：用户点击"下单"，就调用一个叫 createOrder 的云函数，告诉它买了什么商品。createOrder 云函数：检查用户信息、生成订单号、然后调用微信支付。支付成功后：微信支付系统会通知另一个叫 upd…

Django API 响应格式：一个新手踩坑记
type: item
date: 2025-04-25T02:25:00+08:00
description: 问题描述前几天参与了一个AI+智慧教学项目，前端vue后端用Django。在开发 API 接口时遇到了一个令我困惑的问题：明明已经正确实现了 API 的逻辑，但是前端没有接收到数据，所以我使用了在HTTP客户端生成请求：GET http://localhost:8000/ai/courses/ ,结果返回了一个404页面，没有这个路径！然后搜了一天也没解决，最后我观察了一下项目原有的视图文件，发现少了一个前台返回格式 。问题原因在 Django 中，API 的响应格式需要遵循一定的规范。如果直接返回数据而不包装成标准的响应格式，可能会导致前端无法正确解析数据。这是因为前端通常期望接收到的数据具有统一的格式结构。解决方案在视图函数中添加标准的响应格式，问题得到了解决。标准的响应格式如下：# 使用标准响应格式 ret = { "data": data, "meta": { "status": 200, "message": "success" } }这个响应格式包含两个主要部分：data：用于存放实际的业务数据meta：用于存放元数据，包括状态码和消息为什么需要标准响应格式？统一性：确保所有 API 返回的数据格式一致，便于前端处理可扩展性：可以方便地添加额外的元数据，如分页信息、错误详情等错误处理：通过 meta 信息可以清晰地传递错误状态和消息前端友好：前端可以统一处理响应数据，提高开发效率最佳实践始终使用统一的响应格式在 data 字段中放置业务数据使用 meta 字段传递状态信息保持响应格式的一致性总结在 Django API 开发中，响应格式的规范化是非常重要的。通过使用标准的响应格式，不仅可以解决数据传递的问题，还能提高代码的可维护性和可扩展性。希望这个经验分享能够帮助到其他正在学习 Django 的开发者。参考资料Django REST framework 文档Django 官方文档 - JsonResponseRESTful API 设计规范Django 视图函数文档

基于Node.js + Express + MySQL的健身会员管理系统后端
type: item
date: 2025-04-17T13:40:00+08:00
description: 一个健身会员管理系统的后端开发实践项目背景得知需要做一个人员管理的后端，第一个想到使用springboot（毕竟上学期刚学完），在我看了一些文章后，我得知了一个云操作系统，然后就使用这个做了后端和数据库。项目简介这是一个基于 Node.js + Express + MySQL 开发的健身会员管理系统后端。主要功能包括会员管理、课程管理、管理员系统等。作为一个大三学生的练手项目，它涵盖了后端开发中的许多基础知识点，非常适合想要学习后端开发的同学参考。项目部署说明本项目使用 Sealos 进行部署，这是一个开源的云操作系统，提供了完整的云原生应用部署和管理能力。使用 Sealos 的好处是：简单易用：不需要自己搭建服务器不需要配置复杂的网络环境提供了完整的开发环境数据库部署：使用 Sealos 提供的 MySQL 服务数据库连接地址：****-db-mysql.ns-umwit6**.svc默认端口：3306后端部署：使用 Sealos 的容器服务自动配置网络和域名支持 HTTPS 访问开发体验：提供了完整的开发环境支持代码热更新可以方便地进行调试技术栈后端框架：Node.js + Express数据库：MySQL身份认证：JWT (JSON Web Token)跨域处理：CORS数据库连接：mysql2项目结构详解在开始之前，我们先来了解一下这个项目的基本结构。这种结构是 Express 框架的常见组织方式，也是很多项目的基础架构：backend/ ├── config/ # 配置文件目录 │ ├── database.js # 数据库配置 │ ├── init.sql # 数据库初始化脚本 │ └── init-db.js # 数据库初始化程序 ├── controllers/ # 控制器目录 │ └── admin.js # 管理员相关控制器 ├── models/ # 数据模型目录 │ └── admin.js # 管理员数据模型 ├── routes/ # 路由目录 │ └── admin.js # 管理员相关路由 ├── middlewares/ # 中间件目录 │ └── auth.js # 认证中间件 ├── utils/ # 工具函数目录 │ └── logger.js # 日志工具 ├── app.js # 应用入口文件 └── package.json # 项目依赖配置各目录功能说明config/：存放配置文件database.js：数据库连接配置init.sql：数据库表结构初始化脚本init-db.js：执行数据库初始化的程序controllers/：控制器目录处理具体的业务逻辑接收请求参数调用模型层处理数据返回响应结果models/：数据模型目录定义数据结构和关系封装数据库操作处理数据验证routes/：路由目录定义 API 接口路径将请求映射到对应的控制器处理请求参数验证middlewares/：中间件目录处理请求预处理实现认证授权处理错误utils/：工具函数目录封装通用功能提供辅助函数处理日志记录数据库连接详解1. 本项目数据库连接（Sealos 环境）本项目使用 Sealos 提供的 MySQL 服务，配置如下：// config/database.js const mysql = require('mysql2/promise'); const pool = mysql.createPool({ host: '****-db-mysql.ns-umwit6**.svc', // Sealos 提供的数据库地址 user: 'xxxx', // 数据库用户名 password: 'xxxx', // 数据库密码 database: 'fitness', // 数据库名 port: 3306, // MySQL 默认端口 charset: 'utf8mb4', // 字符集，支持中文 waitForConnections: true, connectionLimit: 10 });2. 传统数据库连接方式如果你没有使用 Sealos，而是使用传统的数据库部署方式，可以参考以下配置：2.1 本地开发环境// config/database.js const mysql = require('mysql2/promise'); const pool = mysql.createPool({ host: 'localhost', // 本地数据库地址 user: 'root', // 数据库用户名 password: '123456', // 数据库密码 database: 'fitness', // 数据库名 port: 3306, // MySQL 默认端口 charset: 'utf8mb4', …